mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-20 00:06:12 +02:00
Compare commits
54 Commits
v1.0.0-alp
...
http-v0.1.
Author | SHA1 | Date | |
---|---|---|---|
|
2986077a28 | ||
|
3744957804 | ||
|
420d3064c5 | ||
|
eb4f6b74fb | ||
|
a116c4c2c7 | ||
|
7f674febb1 | ||
|
14252f5ef2 | ||
|
7a28b32f6d | ||
|
09cdf1e302 | ||
|
1eebd47072 | ||
|
002c41a7ca | ||
|
ab4fda6084 | ||
|
f9078d41cd | ||
|
4cc2b38059 | ||
|
d7040dc303 | ||
|
6bc1a0c76b | ||
|
5bd5651faa | ||
|
32ac159ba2 | ||
|
ee33f52736 | ||
|
4f30fa9d46 | ||
|
043f6e77ae | ||
|
48518df883 | ||
|
1f2b15397d | ||
|
87167f6581 | ||
|
b4768a8f81 | ||
|
3fb7343e73 | ||
|
5cfba5ff16 | ||
|
67c34a5937 | ||
|
94d7a7f873 | ||
|
d86567fbdc | ||
|
d115b3b3ed | ||
|
6420a2fe1f | ||
|
0eed9e5257 | ||
|
7801fcb993 | ||
|
e55be4dba6 | ||
|
12e1dad42e | ||
|
7cd59c38d3 | ||
|
8dc4a88aa6 | ||
|
52aebb3bca | ||
|
6b42b2aaee | ||
|
6ab9838977 | ||
|
9d82d4dfb9 | ||
|
9bb40c249f | ||
|
046b7a1425 | ||
|
c22a3a71f2 | ||
|
9c9940d88d | ||
|
561f83d044 | ||
|
43d325a139 | ||
|
0a6dd0efdf | ||
|
b921abf18f | ||
|
9bcd5d6664 | ||
|
bc58dbb2f5 | ||
|
b1547bbbb6 | ||
|
a7fdac1043 |
@@ -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 &&
|
||||
|
49
CHANGES.md
49
CHANGES.md
@@ -1,5 +1,50 @@
|
||||
# Changes
|
||||
|
||||
## [1.0.0-beta.1] - 2019-04-xx
|
||||
|
||||
### Added
|
||||
|
||||
* Add helper functions for reading test response body,
|
||||
`test::read_response()` and test::read_response_json()`
|
||||
|
||||
* Add `.peer_addr()` #744
|
||||
|
||||
### Changed
|
||||
|
||||
* Rename `RouterConfig` to `ServiceConfig`
|
||||
|
||||
* Rename `test::call_success` to `test::call_service`
|
||||
|
||||
### 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
|
||||
|
||||
* Added async io `TestBuffer` for testing.
|
||||
|
||||
### Deleted
|
||||
|
||||
* Removed native-tls support
|
||||
|
||||
|
||||
## [1.0.0-alpha.4] - 2019-04-08
|
||||
|
||||
### Added
|
||||
@@ -18,6 +63,10 @@
|
||||
|
||||
* Move multipart support to actix-multipart crate
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix body propagation in Response::from_error. #760
|
||||
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-04-02
|
||||
|
||||
|
29
Cargo.toml
29
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "1.0.0-alpha.4"
|
||||
version = "1.0.0-alpha.6"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@@ -29,6 +29,7 @@ members = [
|
||||
"awc",
|
||||
"actix-http",
|
||||
"actix-files",
|
||||
"actix-framed",
|
||||
"actix-session",
|
||||
"actix-multipart",
|
||||
"actix-web-actors",
|
||||
@@ -37,7 +38,7 @@ members = [
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "tls", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
|
||||
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
|
||||
|
||||
[features]
|
||||
default = ["brotli", "flate2-zlib", "secure-cookies", "client"]
|
||||
@@ -57,9 +58,6 @@ flate2-rust = ["actix-http/flate2-rust"]
|
||||
# sessions feature, session require "ring" crate and c compiler
|
||||
secure-cookies = ["actix-http/secure-cookies"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls", "actix-server/ssl"]
|
||||
|
||||
# openssl
|
||||
ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
|
||||
|
||||
@@ -67,23 +65,23 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
|
||||
rust-tls = ["rustls", "actix-server/rust-tls"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.1.1"
|
||||
actix-codec = "0.1.2"
|
||||
actix-service = "0.3.6"
|
||||
actix-utils = "0.3.4"
|
||||
actix-router = "0.1.2"
|
||||
actix-rt = "0.2.2"
|
||||
actix-web-codegen = "0.1.0-alpha.1"
|
||||
actix-http = { version = "0.1.0-alpha.4", features=["fail"] }
|
||||
actix-server = "0.4.2"
|
||||
actix-server-config = "0.1.0"
|
||||
actix-web-codegen = "0.1.0-alpha.6"
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["fail"] }
|
||||
actix-server = "0.4.3"
|
||||
actix-server-config = "0.1.1"
|
||||
actix-threadpool = "0.1.0"
|
||||
awc = { version = "0.1.0-alpha.4", optional = true }
|
||||
awc = { version = "0.1.0-alpha.6", 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"
|
||||
@@ -91,18 +89,18 @@ parking_lot = "0.7"
|
||||
regex = "1.0"
|
||||
serde = { version = "1.0", features=["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_urlencoded = "^0.5.3"
|
||||
serde_urlencoded = "0.5.3"
|
||||
time = "0.1"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
|
||||
# ssl support
|
||||
native-tls = { version="0.2", optional = true }
|
||||
openssl = { version="0.10", optional = true }
|
||||
rustls = { version = "^0.15", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "0.1.0-alpha.4", features=["ssl", "brotli", "flate2-zlib"] }
|
||||
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-files = { version = "0.1.0-alpha.6" }
|
||||
rand = "0.6"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
@@ -116,7 +114,6 @@ opt-level = 3
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
actix = { git = "https://github.com/actix/actix.git" }
|
||||
actix-web = { path = "." }
|
||||
actix-http = { path = "actix-http" }
|
||||
actix-http-test = { path = "test-server" }
|
||||
|
161
MIGRATION.md
161
MIGRATION.md
@@ -1,8 +1,100 @@
|
||||
## 1.0
|
||||
|
||||
* Resource registration. 1.0 version uses generalized resource
|
||||
registration via `.service()` method.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
App.new().resource("/welcome", |r| r.f(welcome))
|
||||
```
|
||||
|
||||
use App's or Scope's `.service()` method. `.service()` method accepts
|
||||
object that implements `HttpServiceFactory` trait. By default
|
||||
actix-web provides `Resource` and `Scope` services.
|
||||
|
||||
```rust
|
||||
App.new().service(
|
||||
web::resource("/welcome")
|
||||
.route(web::get().to(welcome))
|
||||
.route(web::post().to(post_handler))
|
||||
```
|
||||
|
||||
* Scope registration.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
let app = App::new().scope("/{project_id}", |scope| {
|
||||
scope
|
||||
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||
.resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||
.resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||
});
|
||||
```
|
||||
|
||||
use `.service()` for registration and `web::scope()` as scope object factory.
|
||||
|
||||
```rust
|
||||
let app = App::new().service(
|
||||
web::scope("/{project_id}")
|
||||
.service(web::resource("/path1").to(|| HttpResponse::Ok()))
|
||||
.service(web::resource("/path2").to(|| HttpResponse::Ok()))
|
||||
.service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
|
||||
);
|
||||
```
|
||||
|
||||
* `.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.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
App.new().resource("/welcome", |r| r.f(welcome))
|
||||
```
|
||||
|
||||
use App's `to()` or `to_async()` methods
|
||||
|
||||
```rust
|
||||
App.new().service(web::resource("/welcome").to(welcome))
|
||||
```
|
||||
|
||||
* `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
|
||||
|
||||
@@ -36,7 +128,7 @@ HttpRequest's api.
|
||||
```
|
||||
|
||||
|
||||
* AsyncResponder is deprecated.
|
||||
* AsyncResponder is removed.
|
||||
|
||||
instead of
|
||||
|
||||
@@ -52,6 +144,69 @@ HttpRequest's api.
|
||||
.. simply omit AsyncResponder and the corresponding responder() finish method
|
||||
|
||||
|
||||
* Middleware
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
let app = App::new()
|
||||
.middleware(middleware::Logger::default())
|
||||
```
|
||||
|
||||
use `.wrap()` method
|
||||
|
||||
```rust
|
||||
let app = App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.route("/index.html", web::get().to(index));
|
||||
```
|
||||
|
||||
* `HttpRequest::body()`, `HttpRequest::urlencoded()`, `HttpRequest::json()`, `HttpRequest::multipart()`
|
||||
method have been removed. Use `Bytes`, `String`, `Form`, `Json`, `Multipart` extractors instead.
|
||||
|
||||
instead if
|
||||
|
||||
```rust
|
||||
fn index(req: &HttpRequest) -> Responder {
|
||||
req.body()
|
||||
.and_then(|body| {
|
||||
...
|
||||
})
|
||||
}
|
||||
|
||||
use
|
||||
|
||||
```rust
|
||||
fn index(body: Bytes) -> Responder {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
* `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`
|
||||
|
||||
use `use actix_files::Files`
|
||||
|
||||
instead of `use actix_web::fs::Namedfile`
|
||||
|
||||
use `use actix_files::NamedFile`
|
||||
|
||||
* Multipart has been move to separate create.
|
||||
|
||||
instead of `use actix_web::multipart::Multipart`
|
||||
|
||||
use `use actix_multipart::Multipart`
|
||||
|
||||
* Response compression is not enabled by default.
|
||||
To enable, use `Compress` middleware, `App::new().wrap(Compress::default())`.
|
||||
|
||||
* Session middleware moved to actix-session crate
|
||||
|
||||
* Actors support have been moved to `actix-web-actors` crate
|
||||
|
||||
|
||||
## 0.7.15
|
||||
|
||||
|
@@ -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,17 @@
|
||||
# Changes
|
||||
|
||||
## [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-alpha.6"
|
||||
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.4"
|
||||
actix-web = "1.0.0-alpha.6"
|
||||
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.4", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.6", 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,22 +397,22 @@ 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>,
|
||||
payload: Payload,
|
||||
) -> Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
@@ -430,8 +426,8 @@ impl<P> FilesService<P> {
|
||||
}
|
||||
}
|
||||
|
||||
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,7 +439,7 @@ impl<P> Service for FilesService<P> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
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()) {
|
||||
@@ -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;
|
||||
|
37
actix-framed/Cargo.toml
Normal file
37
actix-framed/Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "actix-framed"
|
||||
version = "0.1.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix framed app server"
|
||||
readme = "README.md"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-framed/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
"web-programming::websocket"]
|
||||
license = "MIT/Apache-2.0"
|
||||
edition = "2018"
|
||||
workspace =".."
|
||||
|
||||
[lib]
|
||||
name = "actix_framed"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.1.2"
|
||||
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"
|
||||
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
log = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
actix-connect = { version = "0.1.4", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
201
actix-framed/LICENSE-APACHE
Normal file
201
actix-framed/LICENSE-APACHE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017-NOW Nikolay Kim
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
25
actix-framed/LICENSE-MIT
Normal file
25
actix-framed/LICENSE-MIT
Normal file
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2017 Nikolay Kim
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
1
actix-framed/README.md
Normal file
1
actix-framed/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Framed app for actix web [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-framed) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
5
actix-framed/changes.md
Normal file
5
actix-framed/changes.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-04-12
|
||||
|
||||
* Initial release
|
215
actix-framed/src/app.rs
Normal file
215
actix-framed/src/app.rs
Normal file
@@ -0,0 +1,215 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_http::h1::{Codec, SendResponse};
|
||||
use actix_http::{Error, Request, Response};
|
||||
use actix_router::{Path, Router, Url};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
|
||||
use crate::request::FramedRequest;
|
||||
use crate::state::State;
|
||||
|
||||
type BoxedResponse = Box<Future<Item = (), Error = Error>>;
|
||||
|
||||
pub trait HttpServiceFactory {
|
||||
type Factory: NewService;
|
||||
|
||||
fn path(&self) -> &str;
|
||||
|
||||
fn create(self) -> Self::Factory;
|
||||
}
|
||||
|
||||
/// Application builder
|
||||
pub struct FramedApp<T, S = ()> {
|
||||
state: State<S>,
|
||||
services: Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>,
|
||||
}
|
||||
|
||||
impl<T: 'static> FramedApp<T, ()> {
|
||||
pub fn new() -> Self {
|
||||
FramedApp {
|
||||
state: State::new(()),
|
||||
services: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: 'static> FramedApp<T, S> {
|
||||
pub fn with(state: S) -> FramedApp<T, S> {
|
||||
FramedApp {
|
||||
services: Vec::new(),
|
||||
state: State::new(state),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn service<U>(mut self, factory: U) -> Self
|
||||
where
|
||||
U: HttpServiceFactory,
|
||||
U::Factory: NewService<
|
||||
Request = FramedRequest<T, S>,
|
||||
Response = (),
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
> + 'static,
|
||||
<U::Factory as NewService>::Future: 'static,
|
||||
<U::Factory as NewService>::Service: Service<
|
||||
Request = FramedRequest<T, S>,
|
||||
Response = (),
|
||||
Error = Error,
|
||||
Future = Box<Future<Item = (), Error = Error>>,
|
||||
>,
|
||||
{
|
||||
let path = factory.path().to_string();
|
||||
self.services
|
||||
.push((path, Box::new(HttpNewService::new(factory.create()))));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S> IntoNewService<FramedAppFactory<T, S>> for FramedApp<T, S>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
fn into_new_service(self) -> FramedAppFactory<T, S> {
|
||||
FramedAppFactory {
|
||||
state: self.state,
|
||||
services: Rc::new(self.services),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FramedAppFactory<T, S> {
|
||||
state: State<S>,
|
||||
services: Rc<Vec<(String, BoxedHttpNewService<FramedRequest<T, S>>)>>,
|
||||
}
|
||||
|
||||
impl<T, S, C> NewService<C> for FramedAppFactory<T, S>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = CloneableService<FramedAppService<T, S>>;
|
||||
type Future = CreateService<T, S>;
|
||||
|
||||
fn new_service(&self, _: &C) -> Self::Future {
|
||||
CreateService {
|
||||
fut: self
|
||||
.services
|
||||
.iter()
|
||||
.map(|(path, service)| {
|
||||
CreateServiceItem::Future(
|
||||
Some(path.clone()),
|
||||
service.new_service(&()),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
state: self.state.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct CreateService<T, S> {
|
||||
fut: Vec<CreateServiceItem<T, S>>,
|
||||
state: State<S>,
|
||||
}
|
||||
|
||||
enum CreateServiceItem<T, S> {
|
||||
Future(
|
||||
Option<String>,
|
||||
Box<Future<Item = BoxedHttpService<FramedRequest<T, S>>, Error = ()>>,
|
||||
),
|
||||
Service(String, BoxedHttpService<FramedRequest<T, S>>),
|
||||
}
|
||||
|
||||
impl<S: 'static, T: 'static> Future for CreateService<T, S>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Item = CloneableService<FramedAppService<T, S>>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let mut done = true;
|
||||
|
||||
// poll http services
|
||||
for item in &mut self.fut {
|
||||
let res = match item {
|
||||
CreateServiceItem::Future(ref mut path, ref mut fut) => {
|
||||
match fut.poll()? {
|
||||
Async::Ready(service) => Some((path.take().unwrap(), service)),
|
||||
Async::NotReady => {
|
||||
done = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateServiceItem::Service(_, _) => continue,
|
||||
};
|
||||
|
||||
if let Some((path, service)) = res {
|
||||
*item = CreateServiceItem::Service(path, service);
|
||||
}
|
||||
}
|
||||
|
||||
if done {
|
||||
let router = self
|
||||
.fut
|
||||
.drain(..)
|
||||
.fold(Router::build(), |mut router, item| {
|
||||
match item {
|
||||
CreateServiceItem::Service(path, service) => {
|
||||
router.path(&path, service);
|
||||
}
|
||||
CreateServiceItem::Future(_, _) => unreachable!(),
|
||||
}
|
||||
router
|
||||
});
|
||||
Ok(Async::Ready(CloneableService::new(FramedAppService {
|
||||
router: router.finish(),
|
||||
state: self.state.clone(),
|
||||
})))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedAppService<T, S> {
|
||||
state: State<S>,
|
||||
router: Router<BoxedHttpService<FramedRequest<T, S>>>,
|
||||
}
|
||||
|
||||
impl<S: 'static, T: 'static> Service for FramedAppService<T, S>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type Future = BoxedResponse;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
|
||||
let mut path = Path::new(Url::new(req.uri().clone()));
|
||||
|
||||
if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
|
||||
return srv.call(FramedRequest::new(req, framed, path, self.state.clone()));
|
||||
}
|
||||
Box::new(
|
||||
SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())),
|
||||
)
|
||||
}
|
||||
}
|
88
actix-framed/src/helpers.rs
Normal file
88
actix-framed/src/helpers.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use actix_http::Error;
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::{Future, Poll};
|
||||
|
||||
pub(crate) type BoxedHttpService<Req> = Box<
|
||||
Service<
|
||||
Request = Req,
|
||||
Response = (),
|
||||
Error = Error,
|
||||
Future = Box<Future<Item = (), Error = Error>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
pub(crate) type BoxedHttpNewService<Req> = Box<
|
||||
NewService<
|
||||
Request = Req,
|
||||
Response = (),
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
Service = BoxedHttpService<Req>,
|
||||
Future = Box<Future<Item = BoxedHttpService<Req>, Error = ()>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
pub(crate) struct HttpNewService<T: NewService>(T);
|
||||
|
||||
impl<T> HttpNewService<T>
|
||||
where
|
||||
T: NewService<Response = (), Error = Error>,
|
||||
T::Response: 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: Service<Future = Box<Future<Item = (), Error = Error>>> + 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
pub fn new(service: T) -> Self {
|
||||
HttpNewService(service)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NewService for HttpNewService<T>
|
||||
where
|
||||
T: NewService<Response = (), Error = Error>,
|
||||
T::Request: 'static,
|
||||
T::Future: 'static,
|
||||
T::Service: Service<Future = Box<Future<Item = (), Error = Error>>> + 'static,
|
||||
<T::Service as Service>::Future: 'static,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = BoxedHttpService<T::Request>;
|
||||
type Future = Box<Future<Item = Self::Service, Error = ()>>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| {
|
||||
let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service });
|
||||
Ok(service)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpServiceWrapper<T: Service> {
|
||||
service: T,
|
||||
}
|
||||
|
||||
impl<T> Service for HttpServiceWrapper<T>
|
||||
where
|
||||
T: Service<
|
||||
Response = (),
|
||||
Future = Box<Future<Item = (), Error = Error>>,
|
||||
Error = Error,
|
||||
>,
|
||||
T::Request: 'static,
|
||||
{
|
||||
type Request = T::Request;
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = (), Error = Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
self.service.call(req)
|
||||
}
|
||||
}
|
16
actix-framed/src/lib.rs
Normal file
16
actix-framed/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
mod app;
|
||||
mod helpers;
|
||||
mod request;
|
||||
mod route;
|
||||
mod service;
|
||||
mod state;
|
||||
pub mod test;
|
||||
|
||||
// re-export for convinience
|
||||
pub use actix_http::{http, Error, HttpMessage, Response, ResponseError};
|
||||
|
||||
pub use self::app::{FramedApp, FramedAppService};
|
||||
pub use self::request::FramedRequest;
|
||||
pub use self::route::FramedRoute;
|
||||
pub use self::service::{SendError, VerifyWebSockets};
|
||||
pub use self::state::State;
|
170
actix-framed/src/request.rs
Normal file
170
actix-framed/src/request.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::http::{HeaderMap, Method, Uri, Version};
|
||||
use actix_http::{h1::Codec, Extensions, Request, RequestHead};
|
||||
use actix_router::{Path, Url};
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
pub struct FramedRequest<Io, S = ()> {
|
||||
req: Request,
|
||||
framed: Framed<Io, Codec>,
|
||||
state: State<S>,
|
||||
pub(crate) path: Path<Url>,
|
||||
}
|
||||
|
||||
impl<Io, S> FramedRequest<Io, S> {
|
||||
pub fn new(
|
||||
req: Request,
|
||||
framed: Framed<Io, Codec>,
|
||||
path: Path<Url>,
|
||||
state: State<S>,
|
||||
) -> Self {
|
||||
Self {
|
||||
req,
|
||||
framed,
|
||||
state,
|
||||
path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io, S> FramedRequest<Io, S> {
|
||||
/// Split request into a parts
|
||||
pub fn into_parts(self) -> (Request, Framed<Io, Codec>, State<S>) {
|
||||
(self.req, self.framed, self.state)
|
||||
}
|
||||
|
||||
/// This method returns reference to the request head
|
||||
#[inline]
|
||||
pub fn head(&self) -> &RequestHead {
|
||||
self.req.head()
|
||||
}
|
||||
|
||||
/// This method returns muttable reference to the request head.
|
||||
/// panics if multiple references of http request exists.
|
||||
#[inline]
|
||||
pub fn head_mut(&mut self) -> &mut RequestHead {
|
||||
self.req.head_mut()
|
||||
}
|
||||
|
||||
/// Shared application state
|
||||
#[inline]
|
||||
pub fn state(&self) -> &S {
|
||||
self.state.get_ref()
|
||||
}
|
||||
|
||||
/// Request's uri.
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri {
|
||||
&self.head().uri
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
&self.head().method
|
||||
}
|
||||
|
||||
/// Read the Request Version.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.head().version
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns request's headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.head().headers
|
||||
}
|
||||
|
||||
/// The target path of this Request.
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
self.head().uri.path()
|
||||
}
|
||||
|
||||
/// The query string in the URL.
|
||||
///
|
||||
/// E.g., id=10
|
||||
#[inline]
|
||||
pub fn query_string(&self) -> &str {
|
||||
if let Some(query) = self.uri().query().as_ref() {
|
||||
query
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the Path parameters.
|
||||
///
|
||||
/// Params is a container for url parameters.
|
||||
/// A variable segment is specified in the form `{identifier}`,
|
||||
/// where the identifier can be used later in a request handler to
|
||||
/// access the matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Path<Url> {
|
||||
&self.path
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
self.head().extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.head().extensions_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom};
|
||||
use actix_http::test::{TestBuffer, TestRequest};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_reqest() {
|
||||
let buf = TestBuffer::empty();
|
||||
let framed = Framed::new(buf, Codec::default());
|
||||
let req = TestRequest::with_uri("/index.html?q=1")
|
||||
.header("content-type", "test")
|
||||
.finish();
|
||||
let path = Path::new(Url::new(req.uri().clone()));
|
||||
|
||||
let mut freq = FramedRequest::new(req, framed, path, State::new(10u8));
|
||||
assert_eq!(*freq.state(), 10);
|
||||
assert_eq!(freq.version(), Version::HTTP_11);
|
||||
assert_eq!(freq.method(), Method::GET);
|
||||
assert_eq!(freq.path(), "/index.html");
|
||||
assert_eq!(freq.query_string(), "q=1");
|
||||
assert_eq!(
|
||||
freq.headers()
|
||||
.get("content-type")
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"test"
|
||||
);
|
||||
|
||||
freq.head_mut().headers.insert(
|
||||
HeaderName::try_from("x-hdr").unwrap(),
|
||||
HeaderValue::from_static("test"),
|
||||
);
|
||||
assert_eq!(
|
||||
freq.headers().get("x-hdr").unwrap().to_str().unwrap(),
|
||||
"test"
|
||||
);
|
||||
|
||||
freq.extensions_mut().insert(100usize);
|
||||
assert_eq!(*freq.extensions().get::<usize>().unwrap(), 100usize);
|
||||
|
||||
let (_, _, state) = freq.into_parts();
|
||||
assert_eq!(*state, 10);
|
||||
}
|
||||
}
|
156
actix-framed/src/route.rs
Normal file
156
actix-framed/src/route.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_http::{http::Method, Error};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
use log::error;
|
||||
|
||||
use crate::app::HttpServiceFactory;
|
||||
use crate::request::FramedRequest;
|
||||
|
||||
/// Resource route definition
|
||||
///
|
||||
/// Route uses builder-like pattern for configuration.
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
pub struct FramedRoute<Io, S, F = (), R = ()> {
|
||||
handler: F,
|
||||
pattern: String,
|
||||
methods: Vec<Method>,
|
||||
state: PhantomData<(Io, S, R)>,
|
||||
}
|
||||
|
||||
impl<Io, S> FramedRoute<Io, S> {
|
||||
pub fn new(pattern: &str) -> Self {
|
||||
FramedRoute {
|
||||
handler: (),
|
||||
pattern: pattern.to_string(),
|
||||
methods: Vec::new(),
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(path: &str) -> FramedRoute<Io, S> {
|
||||
FramedRoute::new(path).method(Method::GET)
|
||||
}
|
||||
|
||||
pub fn post(path: &str) -> FramedRoute<Io, S> {
|
||||
FramedRoute::new(path).method(Method::POST)
|
||||
}
|
||||
|
||||
pub fn put(path: &str) -> FramedRoute<Io, S> {
|
||||
FramedRoute::new(path).method(Method::PUT)
|
||||
}
|
||||
|
||||
pub fn delete(path: &str) -> FramedRoute<Io, S> {
|
||||
FramedRoute::new(path).method(Method::DELETE)
|
||||
}
|
||||
|
||||
pub fn method(mut self, method: Method) -> Self {
|
||||
self.methods.push(method);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to<F, R>(self, handler: F) -> FramedRoute<Io, S, F, R>
|
||||
where
|
||||
F: FnMut(FramedRequest<Io, S>) -> R,
|
||||
R: IntoFuture<Item = ()>,
|
||||
R::Future: 'static,
|
||||
R::Error: fmt::Debug,
|
||||
{
|
||||
FramedRoute {
|
||||
handler,
|
||||
pattern: self.pattern,
|
||||
methods: self.methods,
|
||||
state: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io, S, F, R> HttpServiceFactory for FramedRoute<Io, S, F, R>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
|
||||
R: IntoFuture<Item = ()>,
|
||||
R::Future: 'static,
|
||||
R::Error: fmt::Display,
|
||||
{
|
||||
type Factory = FramedRouteFactory<Io, S, F, R>;
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.pattern
|
||||
}
|
||||
|
||||
fn create(self) -> Self::Factory {
|
||||
FramedRouteFactory {
|
||||
handler: self.handler,
|
||||
methods: self.methods,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedRouteFactory<Io, S, F, R> {
|
||||
handler: F,
|
||||
methods: Vec<Method>,
|
||||
_t: PhantomData<(Io, S, R)>,
|
||||
}
|
||||
|
||||
impl<Io, S, F, R> NewService for FramedRouteFactory<Io, S, F, R>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
|
||||
R: IntoFuture<Item = ()>,
|
||||
R::Future: 'static,
|
||||
R::Error: fmt::Display,
|
||||
{
|
||||
type Request = FramedRequest<Io, S>;
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = FramedRouteService<Io, S, F, R>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(FramedRouteService {
|
||||
handler: self.handler.clone(),
|
||||
methods: self.methods.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FramedRouteService<Io, S, F, R> {
|
||||
handler: F,
|
||||
methods: Vec<Method>,
|
||||
_t: PhantomData<(Io, S, R)>,
|
||||
}
|
||||
|
||||
impl<Io, S, F, R> Service for FramedRouteService<Io, S, F, R>
|
||||
where
|
||||
Io: AsyncRead + AsyncWrite + 'static,
|
||||
F: FnMut(FramedRequest<Io, S>) -> R + Clone,
|
||||
R: IntoFuture<Item = ()>,
|
||||
R::Future: 'static,
|
||||
R::Error: fmt::Display,
|
||||
{
|
||||
type Request = FramedRequest<Io, S>;
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = (), Error = Error>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: FramedRequest<Io, S>) -> Self::Future {
|
||||
Box::new((self.handler)(req).into_future().then(|res| {
|
||||
if let Err(e) = res {
|
||||
error!("Error in request handler: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
}
|
147
actix-framed/src/service.rs
Normal file
147
actix-framed/src/service.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_http::body::BodySize;
|
||||
use actix_http::error::ResponseError;
|
||||
use actix_http::h1::{Codec, Message};
|
||||
use actix_http::ws::{verify_handshake, HandshakeError};
|
||||
use actix_http::{Request, Response};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll, Sink};
|
||||
|
||||
/// Service that verifies incoming request if it is valid websocket
|
||||
/// upgrade request. In case of error returns `HandshakeError`
|
||||
pub struct VerifyWebSockets<T> {
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for VerifyWebSockets<T> {
|
||||
fn default() -> Self {
|
||||
VerifyWebSockets { _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> NewService<C> for VerifyWebSockets<T> {
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = (Request, Framed<T, Codec>);
|
||||
type Error = (HandshakeError, Framed<T, Codec>);
|
||||
type InitError = ();
|
||||
type Service = VerifyWebSockets<T>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &C) -> Self::Future {
|
||||
ok(VerifyWebSockets { _t: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Service for VerifyWebSockets<T> {
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = (Request, Framed<T, Codec>);
|
||||
type Error = (HandshakeError, Framed<T, Codec>);
|
||||
type Future = FutureResult<Self::Response, Self::Error>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
|
||||
match verify_handshake(req.head()) {
|
||||
Err(e) => Err((e, framed)).into_future(),
|
||||
Ok(_) => Ok((req, framed)).into_future(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send http/1 error response
|
||||
pub struct SendError<T, R, E>(PhantomData<(T, R, E)>);
|
||||
|
||||
impl<T, R, E> Default for SendError<T, R, E>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
E: ResponseError,
|
||||
{
|
||||
fn default() -> Self {
|
||||
SendError(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, E, C> NewService<C> for SendError<T, R, E>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
R: 'static,
|
||||
E: ResponseError + 'static,
|
||||
{
|
||||
type Request = Result<R, (E, Framed<T, Codec>)>;
|
||||
type Response = R;
|
||||
type Error = (E, Framed<T, Codec>);
|
||||
type InitError = ();
|
||||
type Service = SendError<T, R, E>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &C) -> Self::Future {
|
||||
ok(SendError(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, E> Service for SendError<T, R, E>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
R: 'static,
|
||||
E: ResponseError + 'static,
|
||||
{
|
||||
type Request = Result<R, (E, Framed<T, Codec>)>;
|
||||
type Response = R;
|
||||
type Error = (E, Framed<T, Codec>);
|
||||
type Future = Either<FutureResult<R, (E, Framed<T, Codec>)>, SendErrorFut<T, R, E>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Result<R, (E, Framed<T, Codec>)>) -> Self::Future {
|
||||
match req {
|
||||
Ok(r) => Either::A(ok(r)),
|
||||
Err((e, framed)) => {
|
||||
let res = e.error_response().drop_body();
|
||||
Either::B(SendErrorFut {
|
||||
framed: Some(framed),
|
||||
res: Some((res, BodySize::Empty).into()),
|
||||
err: Some(e),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SendErrorFut<T, R, E> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
err: Option<E>,
|
||||
_t: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<T, R, E> Future for SendErrorFut<T, R, E>
|
||||
where
|
||||
E: ResponseError,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Item = R;
|
||||
type Error = (E, Framed<T, Codec>);
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(res) = self.res.take() {
|
||||
if self.framed.as_mut().unwrap().force_send(res).is_err() {
|
||||
return Err((self.err.take().unwrap(), self.framed.take().unwrap()));
|
||||
}
|
||||
}
|
||||
match self.framed.as_mut().unwrap().poll_complete() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
Err((self.err.take().unwrap(), self.framed.take().unwrap()))
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())),
|
||||
}
|
||||
}
|
||||
}
|
29
actix-framed/src/state.rs
Normal file
29
actix-framed/src/state.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Application state
|
||||
pub struct State<S>(Arc<S>);
|
||||
|
||||
impl<S> State<S> {
|
||||
pub fn new(state: S) -> State<S> {
|
||||
State(Arc::new(state))
|
||||
}
|
||||
|
||||
pub fn get_ref(&self) -> &S {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Deref for State<S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &S {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Clone for State<S> {
|
||||
fn clone(&self) -> State<S> {
|
||||
State(self.0.clone())
|
||||
}
|
||||
}
|
153
actix-framed/src/test.rs
Normal file
153
actix-framed/src/test.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
//! Various helpers for Actix applications to use during testing.
|
||||
use actix_codec::Framed;
|
||||
use actix_http::h1::Codec;
|
||||
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
|
||||
use actix_http::http::{HttpTryFrom, Method, Uri, Version};
|
||||
use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest};
|
||||
use actix_router::{Path, Url};
|
||||
use actix_rt::Runtime;
|
||||
use futures::IntoFuture;
|
||||
|
||||
use crate::{FramedRequest, State};
|
||||
|
||||
/// Test `Request` builder.
|
||||
pub struct TestRequest<S = ()> {
|
||||
req: HttpTestRequest,
|
||||
path: Path<Url>,
|
||||
state: State<S>,
|
||||
}
|
||||
|
||||
impl Default for TestRequest<()> {
|
||||
fn default() -> TestRequest {
|
||||
TestRequest {
|
||||
req: HttpTestRequest::default(),
|
||||
path: Path::new(Url::new(Uri::default())),
|
||||
state: State::new(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRequest<()> {
|
||||
/// Create TestRequest and set request uri
|
||||
pub fn with_uri(path: &str) -> Self {
|
||||
Self::get().uri(path)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set header
|
||||
pub fn with_hdr<H: Header>(hdr: H) -> Self {
|
||||
Self::default().set(hdr)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set header
|
||||
pub fn with_header<K, V>(key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: HttpTryFrom<K>,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
Self::default().header(key, value)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set method to `Method::GET`
|
||||
pub fn get() -> Self {
|
||||
Self::default().method(Method::GET)
|
||||
}
|
||||
|
||||
/// Create TestRequest and set method to `Method::POST`
|
||||
pub fn post() -> Self {
|
||||
Self::default().method(Method::POST)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> TestRequest<S> {
|
||||
/// Create TestRequest and set request uri
|
||||
pub fn with_state(state: S) -> TestRequest<S> {
|
||||
let req = TestRequest::get();
|
||||
TestRequest {
|
||||
state: State::new(state),
|
||||
req: req.req,
|
||||
path: req.path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set HTTP version of this request
|
||||
pub fn version(mut self, ver: Version) -> Self {
|
||||
self.req.version(ver);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set HTTP method of this request
|
||||
pub fn method(mut self, meth: Method) -> Self {
|
||||
self.req.method(meth);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set HTTP Uri of this request
|
||||
pub fn uri(mut self, path: &str) -> Self {
|
||||
self.req.uri(path);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header
|
||||
pub fn set<H: Header>(mut self, hdr: H) -> Self {
|
||||
self.req.set(hdr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: HttpTryFrom<K>,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
self.req.header(key, value);
|
||||
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
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `Request` instance
|
||||
pub fn finish(mut self) -> FramedRequest<TestBuffer, S> {
|
||||
let req = self.req.finish();
|
||||
self.path.get_mut().update(req.uri());
|
||||
let framed = Framed::new(TestBuffer::empty(), Codec::default());
|
||||
FramedRequest::new(req, framed, self.path, self.state)
|
||||
}
|
||||
|
||||
/// This method generates `FramedRequest` instance and executes async handler
|
||||
pub fn run<F, R, I, E>(self, f: F) -> Result<I, E>
|
||||
where
|
||||
F: FnOnce(FramedRequest<TestBuffer, S>) -> R,
|
||||
R: IntoFuture<Item = I, Error = E>,
|
||||
{
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
rt.block_on(f(self.finish()).into_future())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let req = TestRequest::with_uri("/index.html")
|
||||
.header("x-test", "test")
|
||||
.param("test", "123")
|
||||
.finish();
|
||||
|
||||
assert_eq!(*req.state(), ());
|
||||
assert_eq!(req.version(), Version::HTTP_11);
|
||||
assert_eq!(req.method(), Method::GET);
|
||||
assert_eq!(req.path(), "/index.html");
|
||||
assert_eq!(req.query_string(), "");
|
||||
assert_eq!(
|
||||
req.headers().get("x-test").unwrap().to_str().unwrap(),
|
||||
"test"
|
||||
);
|
||||
assert_eq!(&req.match_info()["test"], "123");
|
||||
}
|
||||
}
|
136
actix-framed/tests/test_server.rs
Normal file
136
actix-framed/tests/test_server.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_http::{body, ws, Error, HttpService, Response};
|
||||
use actix_http_test::TestServer;
|
||||
use actix_service::{IntoNewService, NewService};
|
||||
use actix_utils::framed::FramedTransport;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::{self, ok};
|
||||
use futures::{Future, Sink, Stream};
|
||||
|
||||
use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets};
|
||||
|
||||
fn ws_service<T: AsyncRead + AsyncWrite>(
|
||||
req: FramedRequest<T>,
|
||||
) -> impl Future<Item = (), Error = Error> {
|
||||
let (req, framed, _) = req.into_parts();
|
||||
let res = ws::handshake(req.head()).unwrap().message_body(());
|
||||
|
||||
framed
|
||||
.send((res, body::BodySize::None).into())
|
||||
.map_err(|_| panic!())
|
||||
.and_then(|framed| {
|
||||
FramedTransport::new(framed.into_framed(ws::Codec::new()), service)
|
||||
.map_err(|_| panic!())
|
||||
})
|
||||
}
|
||||
|
||||
fn service(msg: ws::Frame) -> impl Future<Item = ws::Message, Error = Error> {
|
||||
let msg = match msg {
|
||||
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
|
||||
ws::Frame::Text(text) => {
|
||||
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string())
|
||||
}
|
||||
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()),
|
||||
ws::Frame::Close(reason) => ws::Message::Close(reason),
|
||||
_ => panic!(),
|
||||
};
|
||||
ok(msg)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::build()
|
||||
.upgrade(
|
||||
FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)),
|
||||
)
|
||||
.finish(|_| future::ok::<_, Error>(Response::NotFound()))
|
||||
});
|
||||
|
||||
assert!(srv.ws_at("/test").is_err());
|
||||
|
||||
// client service
|
||||
let framed = srv.ws_at("/index.html").unwrap();
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Text("text".to_string())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Binary("text".into())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
|
||||
);
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Ping("text".into())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
|
||||
.unwrap();
|
||||
|
||||
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
actix_http::h1::OneRequest::new().map_err(|_| ()).and_then(
|
||||
VerifyWebSockets::default()
|
||||
.then(SendError::default())
|
||||
.map_err(|_| ())
|
||||
.and_then(
|
||||
FramedApp::new()
|
||||
.service(FramedRoute::get("/index.html").to(ws_service))
|
||||
.into_new_service()
|
||||
.map_err(|_| ()),
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
assert!(srv.ws_at("/test").is_err());
|
||||
|
||||
// client service
|
||||
let framed = srv.ws_at("/index.html").unwrap();
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Text("text".to_string())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Binary("text".into())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
|
||||
);
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Ping("text".into())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
|
||||
.unwrap();
|
||||
|
||||
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
|
||||
);
|
||||
}
|
@@ -1,9 +1,39 @@
|
||||
# Changes
|
||||
|
||||
## [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
|
||||
|
||||
* Allow to use custom service for upgrade requests
|
||||
|
||||
* Added `h1::SendResponse` future.
|
||||
|
||||
### Changed
|
||||
|
||||
* MessageBody::length() renamed to MessageBody::size() for consistency
|
||||
|
||||
* ws handshake verification functions take RequestHead instead of Request
|
||||
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
### Added
|
||||
|
||||
* Allow to use custom `Expect` handler
|
||||
|
||||
* Add minimal `std::error::Error` impl for `Error`
|
||||
|
||||
### Changed
|
||||
|
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http primitives"
|
||||
readme = "README.md"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-http.git"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-http/"
|
||||
categories = ["network-programming", "asynchronous",
|
||||
"web-programming::http-server",
|
||||
@@ -18,10 +18,6 @@ workspace = ".."
|
||||
[package.metadata.docs.rs]
|
||||
features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "actix/actix-web", branch = "master" }
|
||||
codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
|
||||
|
||||
[lib]
|
||||
name = "actix_http"
|
||||
path = "src/lib.rs"
|
||||
@@ -50,9 +46,9 @@ secure-cookies = ["ring"]
|
||||
[dependencies]
|
||||
actix-service = "0.3.6"
|
||||
actix-codec = "0.1.2"
|
||||
actix-connect = "0.1.2"
|
||||
actix-connect = "0.1.4"
|
||||
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"
|
||||
@@ -64,9 +60,9 @@ derive_more = "0.14"
|
||||
either = "1.5.2"
|
||||
encoding = "0.2"
|
||||
futures = "0.1"
|
||||
hashbrown = "0.1.8"
|
||||
hashbrown = "0.2.2"
|
||||
h2 = "0.1.16"
|
||||
http = "0.1.16"
|
||||
http = "0.1.17"
|
||||
httparse = "1.3"
|
||||
indexmap = "1.0"
|
||||
lazy_static = "1.0"
|
||||
@@ -80,19 +76,19 @@ serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
sha1 = "0.6"
|
||||
slab = "0.4"
|
||||
serde_urlencoded = "0.5.3"
|
||||
serde_urlencoded = "0.5.5"
|
||||
time = "0.1"
|
||||
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.2", default-features = false }
|
||||
trust-dns-resolver = { version="0.11.0", default-features = false }
|
||||
|
||||
# for secure cookie
|
||||
ring = { version = "0.14.6", optional = true }
|
||||
|
||||
# compression
|
||||
brotli2 = { version="^0.3.2", optional = true }
|
||||
flate2 = { version="^1.0.2", optional = true, default-features = false }
|
||||
brotli2 = { version="0.3.2", optional = true }
|
||||
flate2 = { version="1.0.7", optional = true, default-features = false }
|
||||
|
||||
# optional deps
|
||||
failure = { version = "0.1.5", optional = true }
|
||||
@@ -100,8 +96,8 @@ openssl = { version="0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
actix-connect = { version = "0.1.0", 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"] }
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Actix http [](https://travis-ci.org/actix/actix-http) [](https://codecov.io/gh/actix/actix-http) [](https://crates.io/crates/actix-web) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
# Actix http [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-http) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Actix http
|
||||
|
||||
@@ -8,7 +8,7 @@ Actix http
|
||||
* [API Documentation](https://docs.rs/actix-http/)
|
||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||
* Cargo package: [actix-http](https://crates.io/crates/actix-http)
|
||||
* Minimum supported Rust version: 1.26 or later
|
||||
* Minimum supported Rust version: 1.31 or later
|
||||
|
||||
## Example
|
||||
|
||||
|
@@ -30,13 +30,13 @@ impl BodySize {
|
||||
|
||||
/// Type that provides this trait can be streamed to a peer.
|
||||
pub trait MessageBody {
|
||||
fn length(&self) -> BodySize;
|
||||
fn size(&self) -> BodySize;
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error>;
|
||||
}
|
||||
|
||||
impl MessageBody for () {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Empty
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ impl MessageBody for () {
|
||||
}
|
||||
|
||||
impl<T: MessageBody> MessageBody for Box<T> {
|
||||
fn length(&self) -> BodySize {
|
||||
self.as_ref().length()
|
||||
fn size(&self) -> BodySize {
|
||||
self.as_ref().size()
|
||||
}
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||
@@ -86,10 +86,10 @@ impl<B: MessageBody> ResponseBody<B> {
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for ResponseBody<B> {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
ResponseBody::Body(ref body) => body.length(),
|
||||
ResponseBody::Other(ref body) => body.length(),
|
||||
ResponseBody::Body(ref body) => body.size(),
|
||||
ResponseBody::Other(ref body) => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,12 +135,12 @@ impl Body {
|
||||
}
|
||||
|
||||
impl MessageBody for Body {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
Body::None => BodySize::None,
|
||||
Body::Empty => BodySize::Empty,
|
||||
Body::Bytes(ref bin) => BodySize::Sized(bin.len()),
|
||||
Body::Message(ref body) => body.length(),
|
||||
Body::Message(ref body) => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ impl fmt::Debug for Body {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Body::None => write!(f, "Body::None"),
|
||||
Body::Empty => write!(f, "Body::Zero"),
|
||||
Body::Empty => write!(f, "Body::Empty"),
|
||||
Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b),
|
||||
Body::Message(_) => write!(f, "Body::Message(_)"),
|
||||
}
|
||||
@@ -235,7 +235,7 @@ impl From<BytesMut> for Body {
|
||||
}
|
||||
|
||||
impl MessageBody for Bytes {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len())
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ impl MessageBody for Bytes {
|
||||
}
|
||||
|
||||
impl MessageBody for BytesMut {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len())
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ impl MessageBody for BytesMut {
|
||||
}
|
||||
|
||||
impl MessageBody for &'static str {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len())
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ impl MessageBody for &'static str {
|
||||
}
|
||||
|
||||
impl MessageBody for &'static [u8] {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len())
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ impl MessageBody for &'static [u8] {
|
||||
}
|
||||
|
||||
impl MessageBody for Vec<u8> {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len())
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ impl MessageBody for Vec<u8> {
|
||||
}
|
||||
|
||||
impl MessageBody for String {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len())
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ where
|
||||
S: Stream<Item = Bytes, Error = E>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Stream
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ impl<S> MessageBody for SizedStream<S>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = Error>,
|
||||
{
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.size)
|
||||
}
|
||||
|
||||
@@ -416,47 +416,117 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_static_str() {
|
||||
assert_eq!(Body::from("").length(), BodySize::Sized(0));
|
||||
assert_eq!(Body::from("test").length(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from("").size(), BodySize::Sized(0));
|
||||
assert_eq!(Body::from("test").size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from("test").get_ref(), b"test");
|
||||
|
||||
assert_eq!("test".size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
"test".poll_next().unwrap(),
|
||||
Async::Ready(Some(Bytes::from("test")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_bytes() {
|
||||
assert_eq!(Body::from(b"test".as_ref()).length(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test");
|
||||
assert_eq!(
|
||||
Body::from_slice(b"test".as_ref()).length(),
|
||||
Body::from_slice(b"test".as_ref()).size(),
|
||||
BodySize::Sized(4)
|
||||
);
|
||||
assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test");
|
||||
|
||||
assert_eq!((&b"test"[..]).size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
(&b"test"[..]).poll_next().unwrap(),
|
||||
Async::Ready(Some(Bytes::from("test")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec() {
|
||||
assert_eq!(Body::from(Vec::from("test")).length(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test");
|
||||
|
||||
assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
Vec::from("test").poll_next().unwrap(),
|
||||
Async::Ready(Some(Bytes::from("test")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
assert_eq!(Body::from(Bytes::from("test")).length(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(Bytes::from("test")).get_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let b = "test".to_owned();
|
||||
assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4));
|
||||
let mut b = Bytes::from("test");
|
||||
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(b.clone()).get_ref(), b"test");
|
||||
assert_eq!(Body::from(&b).length(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(&b).get_ref(), b"test");
|
||||
|
||||
assert_eq!(b.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
b.poll_next().unwrap(),
|
||||
Async::Ready(Some(Bytes::from("test")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes_mut() {
|
||||
let b = BytesMut::from("test");
|
||||
assert_eq!(Body::from(b.clone()).length(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(b).get_ref(), b"test");
|
||||
let mut b = BytesMut::from("test");
|
||||
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(b.clone()).get_ref(), b"test");
|
||||
|
||||
assert_eq!(b.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
b.poll_next().unwrap(),
|
||||
Async::Ready(Some(Bytes::from("test")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let mut b = "test".to_owned();
|
||||
assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(b.clone()).get_ref(), b"test");
|
||||
assert_eq!(Body::from(&b).size(), BodySize::Sized(4));
|
||||
assert_eq!(Body::from(&b).get_ref(), b"test");
|
||||
|
||||
assert_eq!(b.size(), BodySize::Sized(4));
|
||||
assert_eq!(
|
||||
b.poll_next().unwrap(),
|
||||
Async::Ready(Some(Bytes::from("test")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit() {
|
||||
assert_eq!(().size(), BodySize::Empty);
|
||||
assert_eq!(().poll_next().unwrap(), Async::Ready(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_box() {
|
||||
let mut val = Box::new(());
|
||||
assert_eq!(val.size(), BodySize::Empty);
|
||||
assert_eq!(val.poll_next().unwrap(), Async::Ready(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_body_eq() {
|
||||
assert!(Body::None == Body::None);
|
||||
assert!(Body::None != Body::Empty);
|
||||
assert!(Body::Empty == Body::Empty);
|
||||
assert!(Body::Empty != Body::None);
|
||||
assert!(
|
||||
Body::Bytes(Bytes::from_static(b"1"))
|
||||
== Body::Bytes(Bytes::from_static(b"1"))
|
||||
);
|
||||
assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_body_debug() {
|
||||
assert!(format!("{:?}", Body::None).contains("Body::None"));
|
||||
assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
|
||||
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1"));
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,14 @@
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_server_config::ServerConfig as SrvConfig;
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
|
||||
use crate::body::MessageBody;
|
||||
use crate::config::{KeepAlive, ServiceConfig};
|
||||
use crate::error::Error;
|
||||
use crate::h1::{ExpectHandler, H1Service};
|
||||
use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler};
|
||||
use crate::h2::H2Service;
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
@@ -17,15 +18,16 @@ use crate::service::HttpService;
|
||||
///
|
||||
/// This type can be used to construct an instance of `http service` through a
|
||||
/// builder-like pattern.
|
||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler> {
|
||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||
keep_alive: KeepAlive,
|
||||
client_timeout: u64,
|
||||
client_disconnect: u64,
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
_t: PhantomData<(T, S)>,
|
||||
}
|
||||
|
||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler>
|
||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler<T>>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -38,12 +40,13 @@ where
|
||||
client_timeout: 5000,
|
||||
client_disconnect: 0,
|
||||
expect: ExpectHandler,
|
||||
upgrade: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, X> HttpServiceBuilder<T, S, X>
|
||||
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -51,11 +54,14 @@ where
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Set server keep-alive setting.
|
||||
///
|
||||
/// By default keep alive is set to a 5 seconds.
|
||||
pub fn keep_alive<U: Into<KeepAlive>>(mut self, val: U) -> Self {
|
||||
pub fn keep_alive<W: Into<KeepAlive>>(mut self, val: W) -> Self {
|
||||
self.keep_alive = val.into();
|
||||
self
|
||||
}
|
||||
@@ -87,27 +93,51 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
// #[cfg(feature = "ssl")]
|
||||
// /// Configure alpn protocols for SslAcceptorBuilder.
|
||||
// pub fn configure_openssl(
|
||||
// builder: &mut openssl::ssl::SslAcceptorBuilder,
|
||||
// ) -> io::Result<()> {
|
||||
// let protos: &[u8] = b"\x02h2";
|
||||
// builder.set_alpn_select_callback(|_, protos| {
|
||||
// const H2: &[u8] = b"\x02h2";
|
||||
// if protos.windows(3).any(|window| window == H2) {
|
||||
// Ok(b"h2")
|
||||
// } else {
|
||||
// Err(openssl::ssl::AlpnError::NOACK)
|
||||
// }
|
||||
// });
|
||||
// builder.set_alpn_protos(&protos)?;
|
||||
/// Provide service for `EXPECT: 100-Continue` support.
|
||||
///
|
||||
/// Service get called with request that contains `EXPECT` header.
|
||||
/// Service must return request in case of success, in that case
|
||||
/// request will be forwarded to main service.
|
||||
pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
|
||||
where
|
||||
F: IntoNewService<X1>,
|
||||
X1: NewService<Request = Request, Response = Request>,
|
||||
X1::Error: Into<Error>,
|
||||
X1::InitError: fmt::Debug,
|
||||
{
|
||||
HttpServiceBuilder {
|
||||
keep_alive: self.keep_alive,
|
||||
client_timeout: self.client_timeout,
|
||||
client_disconnect: self.client_disconnect,
|
||||
expect: expect.into_new_service(),
|
||||
upgrade: self.upgrade,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
/// Provide service for custom `Connection: UPGRADE` support.
|
||||
///
|
||||
/// If service is provided then normal requests handling get halted
|
||||
/// and this service get called with original request and framed object.
|
||||
pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
|
||||
where
|
||||
F: IntoNewService<U1>,
|
||||
U1: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U1::Error: fmt::Display,
|
||||
U1::InitError: fmt::Debug,
|
||||
{
|
||||
HttpServiceBuilder {
|
||||
keep_alive: self.keep_alive,
|
||||
client_timeout: self.client_timeout,
|
||||
client_disconnect: self.client_disconnect,
|
||||
expect: self.expect,
|
||||
upgrade: Some(upgrade.into_new_service()),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish service configuration and create *http service* for HTTP/1 protocol.
|
||||
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X>
|
||||
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X, U>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
F: IntoNewService<S, SrvConfig>,
|
||||
@@ -120,7 +150,9 @@ where
|
||||
self.client_timeout,
|
||||
self.client_disconnect,
|
||||
);
|
||||
H1Service::with_config(cfg, service.into_new_service()).expect(self.expect)
|
||||
H1Service::with_config(cfg, service.into_new_service())
|
||||
.expect(self.expect)
|
||||
.upgrade(self.upgrade)
|
||||
}
|
||||
|
||||
/// Finish service configuration and create *http service* for HTTP/2 protocol.
|
||||
@@ -142,7 +174,7 @@ where
|
||||
}
|
||||
|
||||
/// Finish service configuration and create `HttpService` instance.
|
||||
pub fn finish<F, P, B>(self, service: F) -> HttpService<T, P, S, B>
|
||||
pub fn finish<F, P, B>(self, service: F) -> HttpService<T, P, S, B, X, U>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
F: IntoNewService<S, SrvConfig>,
|
||||
@@ -157,5 +189,7 @@ where
|
||||
self.client_disconnect,
|
||||
);
|
||||
HttpService::with_config(cfg, service.into_new_service())
|
||||
.expect(self.expect)
|
||||
.upgrade(self.upgrade)
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ use crate::message::{RequestHead, ResponseHead};
|
||||
use crate::payload::Payload;
|
||||
|
||||
use super::error::SendRequestError;
|
||||
use super::pool::Acquired;
|
||||
use super::pool::{Acquired, Protocol};
|
||||
use super::{h1proto, h2proto};
|
||||
|
||||
pub(crate) enum ConnectionType<Io> {
|
||||
@@ -24,6 +24,8 @@ pub trait Connection {
|
||||
type Io: AsyncRead + AsyncWrite;
|
||||
type Future: Future<Item = (ResponseHead, Payload), Error = SendRequestError>;
|
||||
|
||||
fn protocol(&self) -> Protocol;
|
||||
|
||||
/// Send request and body
|
||||
fn send_request<B: MessageBody + 'static>(
|
||||
self,
|
||||
@@ -94,6 +96,14 @@ where
|
||||
type Io = T;
|
||||
type Future = Box<Future<Item = (ResponseHead, Payload), Error = SendRequestError>>;
|
||||
|
||||
fn protocol(&self) -> Protocol {
|
||||
match self.io {
|
||||
Some(ConnectionType::H1(_)) => Protocol::Http1,
|
||||
Some(ConnectionType::H2(_)) => Protocol::Http2,
|
||||
None => Protocol::Http1,
|
||||
}
|
||||
}
|
||||
|
||||
fn send_request<B: MessageBody + 'static>(
|
||||
mut self,
|
||||
head: RequestHead,
|
||||
@@ -161,6 +171,13 @@ where
|
||||
type Io = EitherIo<A, B>;
|
||||
type Future = Box<Future<Item = (ResponseHead, Payload), Error = SendRequestError>>;
|
||||
|
||||
fn protocol(&self) -> Protocol {
|
||||
match self {
|
||||
EitherConnection::A(con) => con.protocol(),
|
||||
EitherConnection::B(con) => con.protocol(),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_request<RB: MessageBody + 'static>(
|
||||
self,
|
||||
head: RequestHead,
|
||||
|
@@ -57,8 +57,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
|
||||
|
@@ -1,12 +1,14 @@
|
||||
use std::io::Write;
|
||||
use std::{io, time};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use bytes::Bytes;
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::future::{ok, Either};
|
||||
use futures::{Async, Future, Poll, Sink, Stream};
|
||||
|
||||
use crate::error::PayloadError;
|
||||
use crate::h1;
|
||||
use crate::http::header::{IntoHeaderValue, HOST};
|
||||
use crate::message::{RequestHead, ResponseHead};
|
||||
use crate::payload::{Payload, PayloadStream};
|
||||
|
||||
@@ -17,7 +19,7 @@ use crate::body::{BodySize, MessageBody};
|
||||
|
||||
pub(crate) fn send_request<T, B>(
|
||||
io: T,
|
||||
head: RequestHead,
|
||||
mut head: RequestHead,
|
||||
body: B,
|
||||
created: time::Instant,
|
||||
pool: Option<Acquired<T>>,
|
||||
@@ -26,20 +28,41 @@ where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
B: MessageBody,
|
||||
{
|
||||
// set request host header
|
||||
if !head.headers.contains_key(HOST) {
|
||||
if let Some(host) = head.uri.host() {
|
||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||
|
||||
let _ = match 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) => {
|
||||
head.headers.insert(HOST, value);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Can not set HOST header {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let io = H1Connection {
|
||||
created,
|
||||
pool,
|
||||
io: Some(io),
|
||||
};
|
||||
|
||||
let len = body.length();
|
||||
let len = body.size();
|
||||
|
||||
// create Framed and send reqest
|
||||
Framed::new(io, h1::ClientCodec::default())
|
||||
.send((head, len).into())
|
||||
.from_err()
|
||||
// send request body
|
||||
.and_then(move |framed| match body.length() {
|
||||
.and_then(move |framed| match body.size() {
|
||||
BodySize::None | BodySize::Empty | BodySize::Sized(0) => {
|
||||
Either::A(ok(framed))
|
||||
}
|
||||
|
@@ -27,9 +27,9 @@ where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
B: MessageBody,
|
||||
{
|
||||
trace!("Sending client request: {:?} {:?}", head, body.length());
|
||||
trace!("Sending client request: {:?} {:?}", head, body.size());
|
||||
let head_req = head.method == Method::HEAD;
|
||||
let length = body.length();
|
||||
let length = body.size();
|
||||
let eof = match length {
|
||||
BodySize::None | BodySize::Empty | BodySize::Sized(0) => true,
|
||||
_ => false,
|
||||
@@ -107,7 +107,6 @@ where
|
||||
let mut head = ResponseHead::new(parts.status);
|
||||
head.version = parts.version;
|
||||
head.headers = parts.headers.into();
|
||||
|
||||
Ok((head, payload))
|
||||
})
|
||||
.from_err()
|
||||
|
@@ -9,3 +9,4 @@ mod pool;
|
||||
pub use self::connection::Connection;
|
||||
pub use self::connector::Connector;
|
||||
pub use self::error::{ConnectError, InvalidUrl, SendRequestError};
|
||||
pub use self::pool::Protocol;
|
||||
|
@@ -21,8 +21,8 @@ use tokio_timer::{sleep, Delay};
|
||||
use super::connection::{ConnectionType, IoConnection};
|
||||
use super::error::ConnectError;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
/// Protocol version
|
||||
pub enum Protocol {
|
||||
Http1,
|
||||
Http2,
|
||||
|
@@ -79,12 +79,12 @@ enum EncoderBody<B> {
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||
fn length(&self) -> BodySize {
|
||||
fn size(&self) -> BodySize {
|
||||
if self.encoder.is_none() {
|
||||
match self.body {
|
||||
EncoderBody::Bytes(ref b) => b.length(),
|
||||
EncoderBody::Stream(ref b) => b.length(),
|
||||
EncoderBody::BoxedStream(ref b) => b.length(),
|
||||
EncoderBody::Bytes(ref b) => b.size(),
|
||||
EncoderBody::Stream(ref b) => b.size(),
|
||||
EncoderBody::BoxedStream(ref b) => b.size(),
|
||||
}
|
||||
} else {
|
||||
BodySize::Stream
|
||||
|
@@ -1,11 +1,13 @@
|
||||
//! Error and Result module
|
||||
use std::cell::RefCell;
|
||||
use std::io::Write;
|
||||
use std::str::Utf8Error;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::{fmt, io, result};
|
||||
|
||||
pub use actix_threadpool::BlockingError;
|
||||
use actix_utils::timeout::TimeoutError;
|
||||
use bytes::BytesMut;
|
||||
use derive_more::{Display, From};
|
||||
use futures::Canceled;
|
||||
use http::uri::InvalidUri;
|
||||
@@ -17,7 +19,9 @@ use serde_urlencoded::ser::Error as FormError;
|
||||
use tokio_timer::Error as TimerError;
|
||||
|
||||
// re-export for convinience
|
||||
use crate::body::Body;
|
||||
pub use crate::cookie::ParseError as CookieParseError;
|
||||
use crate::helpers::Writer;
|
||||
use crate::response::Response;
|
||||
|
||||
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
||||
@@ -57,6 +61,18 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||
fn error_response(&self) -> Response {
|
||||
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
/// Constructs an error response
|
||||
fn render_response(&self) -> Response {
|
||||
let mut resp = self.error_response();
|
||||
let mut buf = BytesMut::new();
|
||||
let _ = write!(Writer(&mut buf), "{}", self);
|
||||
resp.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
);
|
||||
resp.set_body(Body::from(buf))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
@@ -137,6 +153,10 @@ impl ResponseError for TimerError {}
|
||||
/// `InternalServerError` for `SslError`
|
||||
impl ResponseError for openssl::ssl::Error {}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
/// `InternalServerError` for `SslError`
|
||||
impl ResponseError for openssl::ssl::HandshakeError<tokio_tcp::TcpStream> {}
|
||||
|
||||
/// Return `BAD_REQUEST` for `de::value::Error`
|
||||
impl ResponseError for DeError {
|
||||
fn error_response(&self) -> Response {
|
||||
@@ -350,6 +370,9 @@ pub enum DispatchError {
|
||||
/// Service error
|
||||
Service(Error),
|
||||
|
||||
/// Upgrade service error
|
||||
Upgrade,
|
||||
|
||||
/// An `io::Error` that occurred while trying to read or write to a network
|
||||
/// stream.
|
||||
#[display(fmt = "IO error: {}", _0)]
|
||||
@@ -474,7 +497,16 @@ where
|
||||
{
|
||||
fn error_response(&self) -> Response {
|
||||
match self.status {
|
||||
InternalErrorType::Status(st) => Response::new(st),
|
||||
InternalErrorType::Status(st) => {
|
||||
let mut res = Response::new(st);
|
||||
let mut buf = BytesMut::new();
|
||||
let _ = write!(Writer(&mut buf), "{}", self);
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("text/plain"),
|
||||
);
|
||||
res.set_body(Body::from(buf))
|
||||
}
|
||||
InternalErrorType::Response(ref resp) => {
|
||||
if let Some(resp) = resp.borrow_mut().take() {
|
||||
resp
|
||||
@@ -484,6 +516,11 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an error response
|
||||
fn render_response(&self) -> Response {
|
||||
self.error_response()
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Response to a Error
|
||||
|
@@ -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};
|
||||
use actix_codec::{Decoder, Encoder, Framed, FramedParts};
|
||||
use actix_server_config::IoStream;
|
||||
use actix_service::Service;
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bitflags::bitflags;
|
||||
@@ -34,33 +35,54 @@ bitflags! {
|
||||
const SHUTDOWN = 0b0000_1000;
|
||||
const READ_DISCONNECT = 0b0001_0000;
|
||||
const WRITE_DISCONNECT = 0b0010_0000;
|
||||
const DROPPING = 0b0100_0000;
|
||||
const UPGRADE = 0b0100_0000;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatcher for HTTP/1.1 protocol
|
||||
pub struct Dispatcher<T, S, B, X>
|
||||
pub struct Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
inner: Option<InnerDispatcher<T, S, B, X>>,
|
||||
inner: DispatcherState<T, S, B, X, U>,
|
||||
}
|
||||
|
||||
struct InnerDispatcher<T, S, B, X>
|
||||
enum DispatcherState<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
Normal(InnerDispatcher<T, S, B, X, U>),
|
||||
Upgrade(U::Future),
|
||||
None,
|
||||
}
|
||||
|
||||
struct InnerDispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
service: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
flags: Flags,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
error: Option<DispatchError>,
|
||||
|
||||
state: State<S, B, X>,
|
||||
@@ -78,6 +100,7 @@ where
|
||||
|
||||
enum DispatcherMessage {
|
||||
Item(Request),
|
||||
Upgrade(Request),
|
||||
Error(Response<()>),
|
||||
}
|
||||
|
||||
@@ -116,31 +139,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B, X> fmt::Debug for State<S, B, X>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
B: MessageBody,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
enum PollResponse {
|
||||
Upgrade(Request),
|
||||
DoNothing,
|
||||
DrainWriteBuf,
|
||||
}
|
||||
|
||||
impl PartialEq for PollResponse {
|
||||
fn eq(&self, other: &PollResponse) -> bool {
|
||||
match self {
|
||||
State::None => write!(f, "State::None"),
|
||||
State::ExpectCall(_) => write!(f, "State::ExceptCall"),
|
||||
State::ServiceCall(_) => write!(f, "State::ServiceCall"),
|
||||
State::SendPayload(_) => write!(f, "State::SendPayload"),
|
||||
PollResponse::DrainWriteBuf => match other {
|
||||
PollResponse::DrainWriteBuf => true,
|
||||
_ => false,
|
||||
},
|
||||
PollResponse::DoNothing => match other {
|
||||
PollResponse::DoNothing => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X> Dispatcher<T, S, B, X>
|
||||
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>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
/// Create http/1 dispatcher.
|
||||
pub fn new(
|
||||
@@ -148,6 +179,7 @@ where
|
||||
config: ServiceConfig,
|
||||
service: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
) -> Self {
|
||||
Dispatcher::with_timeout(
|
||||
stream,
|
||||
@@ -157,6 +189,7 @@ where
|
||||
None,
|
||||
service,
|
||||
expect,
|
||||
upgrade,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -169,6 +202,7 @@ where
|
||||
timeout: Option<Delay>,
|
||||
service: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
) -> Self {
|
||||
let keepalive = config.keep_alive_enabled();
|
||||
let flags = if keepalive {
|
||||
@@ -187,17 +221,19 @@ where
|
||||
};
|
||||
|
||||
Dispatcher {
|
||||
inner: Some(InnerDispatcher {
|
||||
io,
|
||||
codec,
|
||||
read_buf,
|
||||
inner: DispatcherState::Normal(InnerDispatcher {
|
||||
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,
|
||||
flags,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
@@ -206,18 +242,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X> InnerDispatcher<T, S, B, X>
|
||||
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>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
fn can_read(&self) -> bool {
|
||||
if self.flags.contains(Flags::READ_DISCONNECT) {
|
||||
if self
|
||||
.flags
|
||||
.intersects(Flags::READ_DISCONNECT | Flags::UPGRADE)
|
||||
{
|
||||
false
|
||||
} else if let Some(ref info) = self.payload {
|
||||
info.need_read() == PayloadStatus::Read
|
||||
@@ -282,7 +323,7 @@ where
|
||||
body: ResponseBody<B>,
|
||||
) -> Result<State<S, B, X>, DispatchError> {
|
||||
self.codec
|
||||
.encode(Message::Item((message, body.length())), &mut self.write_buf)
|
||||
.encode(Message::Item((message, body.size())), &mut self.write_buf)
|
||||
.map_err(|err| {
|
||||
if let Some(mut payload) = self.payload.take() {
|
||||
payload.set_error(PayloadError::Incomplete(None));
|
||||
@@ -291,7 +332,7 @@ where
|
||||
})?;
|
||||
|
||||
self.flags.set(Flags::KEEPALIVE, self.codec.keepalive());
|
||||
match body.length() {
|
||||
match body.size() {
|
||||
BodySize::None | BodySize::Empty => Ok(State::None),
|
||||
_ => Ok(State::SendPayload(body)),
|
||||
}
|
||||
@@ -302,7 +343,7 @@ where
|
||||
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
|
||||
}
|
||||
|
||||
fn poll_response(&mut self) -> Result<bool, DispatchError> {
|
||||
fn poll_response(&mut self) -> Result<PollResponse, DispatchError> {
|
||||
loop {
|
||||
let state = match self.state {
|
||||
State::None => match self.messages.pop_front() {
|
||||
@@ -312,6 +353,9 @@ where
|
||||
Some(DispatcherMessage::Error(res)) => {
|
||||
Some(self.send_response(res, ResponseBody::Other(Body::Empty))?)
|
||||
}
|
||||
Some(DispatcherMessage::Upgrade(req)) => {
|
||||
return Ok(PollResponse::Upgrade(req));
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
State::ExpectCall(ref mut fut) => match fut.poll() {
|
||||
@@ -361,10 +405,10 @@ where
|
||||
)?;
|
||||
self.state = State::None;
|
||||
}
|
||||
Async::NotReady => return Ok(false),
|
||||
Async::NotReady => return Ok(PollResponse::DoNothing),
|
||||
}
|
||||
} else {
|
||||
return Ok(true);
|
||||
return Ok(PollResponse::DrainWriteBuf);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -392,7 +436,7 @@ where
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
Ok(PollResponse::DoNothing)
|
||||
}
|
||||
|
||||
fn handle_request(&mut self, req: Request) -> Result<State<S, B, X>, DispatchError> {
|
||||
@@ -448,16 +492,20 @@ where
|
||||
|
||||
match msg {
|
||||
Message::Item(mut req) => {
|
||||
match self.codec.message_type() {
|
||||
MessageType::Payload | MessageType::Stream => {
|
||||
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));
|
||||
break;
|
||||
}
|
||||
if pl == MessageType::Payload || pl == MessageType::Stream {
|
||||
let (ps, pl) = Payload::create(false);
|
||||
let (req1, _) =
|
||||
req.replace_payload(crate::Payload::H1(pl));
|
||||
req = req1;
|
||||
self.payload = Some(ps);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// handle request early
|
||||
if self.state.is_empty() {
|
||||
@@ -603,22 +651,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X> Future for Dispatcher<T, S, B, X>
|
||||
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>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = DispatchError;
|
||||
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let inner = self.inner.as_mut().unwrap();
|
||||
match self.inner {
|
||||
DispatcherState::Normal(ref mut inner) => {
|
||||
inner.poll_keepalive()?;
|
||||
|
||||
if inner.flags.contains(Flags::SHUTDOWN) {
|
||||
@@ -639,7 +690,9 @@ where
|
||||
} else {
|
||||
// read socket into a buf
|
||||
if !inner.flags.contains(Flags::READ_DISCONNECT) {
|
||||
if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? {
|
||||
if let Some(true) =
|
||||
read_available(&mut inner.io, &mut inner.read_buf)?
|
||||
{
|
||||
inner.flags.insert(Flags::READ_DISCONNECT)
|
||||
}
|
||||
}
|
||||
@@ -649,12 +702,34 @@ where
|
||||
if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||
inner.write_buf.reserve(HW_BUFFER_SIZE);
|
||||
}
|
||||
let need_write = inner.poll_response()?;
|
||||
let result = inner.poll_response()?;
|
||||
let drain = result == PollResponse::DrainWriteBuf;
|
||||
|
||||
// switch to upgrade handler
|
||||
if let PollResponse::Upgrade(req) = result {
|
||||
if let DispatcherState::Normal(inner) =
|
||||
std::mem::replace(&mut self.inner, DispatcherState::None)
|
||||
{
|
||||
let mut parts = FramedParts::with_read_buf(
|
||||
inner.io,
|
||||
inner.codec,
|
||||
inner.read_buf,
|
||||
);
|
||||
parts.write_buf = inner.write_buf;
|
||||
let framed = Framed::from_parts(parts);
|
||||
self.inner = DispatcherState::Upgrade(
|
||||
inner.upgrade.unwrap().call((req, framed)),
|
||||
);
|
||||
return self.poll();
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
// we didnt get WouldBlock from write operation,
|
||||
// so data get written to kernel completely (OSX)
|
||||
// and we have to write again otherwise response can get stuck
|
||||
if inner.poll_flush()? || !need_write {
|
||||
if inner.poll_flush()? || !drain {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -694,6 +769,13 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
DispatcherState::Upgrade(ref mut fut) => fut.poll().map_err(|e| {
|
||||
error!("Upgrade handler error: {}", e);
|
||||
DispatchError::Upgrade
|
||||
}),
|
||||
DispatcherState::None => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_available<T>(io: &mut T, buf: &mut BytesMut) -> Result<Option<bool>, io::Error>
|
||||
@@ -737,94 +819,35 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{cmp, io};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_service::IntoService;
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use futures::future::{lazy, ok};
|
||||
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
use crate::h1::ExpectHandler;
|
||||
|
||||
struct Buffer {
|
||||
buf: Bytes,
|
||||
write_buf: BytesMut,
|
||||
err: Option<io::Error>,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
fn new(data: &'static str) -> Buffer {
|
||||
Buffer {
|
||||
buf: Bytes::from(data),
|
||||
write_buf: BytesMut::new(),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Buffer {}
|
||||
impl io::Read for Buffer {
|
||||
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
|
||||
if self.buf.is_empty() {
|
||||
if self.err.is_some() {
|
||||
Err(self.err.take().unwrap())
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
||||
}
|
||||
} else {
|
||||
let size = cmp::min(self.buf.len(), dst.len());
|
||||
let b = self.buf.split_to(size);
|
||||
dst[..size].copy_from_slice(&b);
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for Buffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.write_buf.extend(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl AsyncWrite for Buffer {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
use crate::h1::{ExpectHandler, UpgradeHandler};
|
||||
use crate::test::TestBuffer;
|
||||
|
||||
#[test]
|
||||
fn test_req_parse_err() {
|
||||
let mut sys = actix_rt::System::new("test");
|
||||
let _ = sys.block_on(lazy(|| {
|
||||
let buf = Buffer::new("GET /test HTTP/1\r\n\r\n");
|
||||
let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n");
|
||||
|
||||
let mut h1 = Dispatcher::new(
|
||||
let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<TestBuffer>>::new(
|
||||
buf,
|
||||
ServiceConfig::default(),
|
||||
CloneableService::new(
|
||||
(|_| ok::<_, Error>(Response::Ok().finish())).into_service(),
|
||||
),
|
||||
CloneableService::new(ExpectHandler),
|
||||
None,
|
||||
);
|
||||
assert!(h1.poll().is_err());
|
||||
assert!(h1
|
||||
.inner
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.flags
|
||||
.contains(Flags::READ_DISCONNECT));
|
||||
assert_eq!(
|
||||
&h1.inner.as_ref().unwrap().io.write_buf[..26],
|
||||
b"HTTP/1.1 400 Bad Request\r\n"
|
||||
);
|
||||
|
||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
||||
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
|
||||
assert_eq!(&inner.io.write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n");
|
||||
}
|
||||
ok::<_, ()>(())
|
||||
}));
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ mod encoder;
|
||||
mod expect;
|
||||
mod payload;
|
||||
mod service;
|
||||
mod upgrade;
|
||||
mod utils;
|
||||
|
||||
pub use self::client::{ClientCodec, ClientPayloadCodec};
|
||||
pub use self::codec::Codec;
|
||||
@@ -16,6 +18,8 @@ pub use self::dispatcher::Dispatcher;
|
||||
pub use self::expect::ExpectHandler;
|
||||
pub use self::payload::Payload;
|
||||
pub use self::service::{H1Service, H1ServiceHandler, OneRequest};
|
||||
pub use self::upgrade::UpgradeHandler;
|
||||
pub use self::utils::SendResponse;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Codec message
|
||||
|
@@ -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};
|
||||
@@ -16,13 +16,14 @@ use crate::response::Response;
|
||||
|
||||
use super::codec::Codec;
|
||||
use super::dispatcher::Dispatcher;
|
||||
use super::{ExpectHandler, Message};
|
||||
use super::{ExpectHandler, Message, UpgradeHandler};
|
||||
|
||||
/// `NewService` implementation for HTTP1 transport
|
||||
pub struct H1Service<T, P, S, B, X = ExpectHandler> {
|
||||
pub struct H1Service<T, P, S, B, X = ExpectHandler, U = UpgradeHandler<T>> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
@@ -42,6 +43,7 @@ where
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: ExpectHandler,
|
||||
upgrade: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -55,12 +57,13 @@ where
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: ExpectHandler,
|
||||
upgrade: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> H1Service<T, P, S, B, X>
|
||||
impl<T, P, S, B, X, U> H1Service<T, P, S, B, X, U>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -68,24 +71,40 @@ where
|
||||
S::InitError: fmt::Debug,
|
||||
B: MessageBody,
|
||||
{
|
||||
pub fn expect<U>(self, expect: U) -> H1Service<T, P, S, B, U>
|
||||
pub fn expect<X1>(self, expect: X1) -> H1Service<T, P, S, B, X1, U>
|
||||
where
|
||||
U: NewService<Request = Request, Response = Request>,
|
||||
U::Error: Into<Error>,
|
||||
U::InitError: fmt::Debug,
|
||||
X1: NewService<Request = Request, Response = Request>,
|
||||
X1::Error: Into<Error>,
|
||||
X1::InitError: fmt::Debug,
|
||||
{
|
||||
H1Service {
|
||||
expect,
|
||||
cfg: self.cfg,
|
||||
srv: self.srv,
|
||||
upgrade: self.upgrade,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> H1Service<T, P, S, B, X, U1>
|
||||
where
|
||||
U1: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U1::Error: fmt::Display,
|
||||
U1::InitError: fmt::Debug,
|
||||
{
|
||||
H1Service {
|
||||
upgrade,
|
||||
cfg: self.cfg,
|
||||
srv: self.srv,
|
||||
expect: self.expect,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> NewService<SrvConfig> for H1Service<T, P, S, B, X>
|
||||
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>>,
|
||||
@@ -94,19 +113,24 @@ where
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type InitError = ();
|
||||
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Future = H1ServiceResponse<T, P, S, B, X>;
|
||||
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
|
||||
type Future = H1ServiceResponse<T, P, S, B, X, U>;
|
||||
|
||||
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
|
||||
H1ServiceResponse {
|
||||
fut: self.srv.new_service(cfg).into_future(),
|
||||
fut_ex: Some(self.expect.new_service(&())),
|
||||
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())),
|
||||
expect: None,
|
||||
upgrade: None,
|
||||
cfg: Some(self.cfg.clone()),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@@ -114,7 +138,7 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct H1ServiceResponse<T, P, S, B, X>
|
||||
pub struct H1ServiceResponse<T, P, S, B, X, U>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -122,17 +146,22 @@ where
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
fut: S::Future,
|
||||
fut_ex: Option<X::Future>,
|
||||
fut_upg: Option<U::Future>,
|
||||
expect: Option<X::Service>,
|
||||
upgrade: Option<U::Service>,
|
||||
cfg: Option<ServiceConfig>,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> Future for H1ServiceResponse<T, P, S, B, X>
|
||||
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>>,
|
||||
@@ -141,8 +170,11 @@ where
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: NewService<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
type Item = H1ServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Item = H1ServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
@@ -154,6 +186,14 @@ where
|
||||
self.fut_ex.take();
|
||||
}
|
||||
|
||||
if let Some(ref mut fut) = self.fut_upg {
|
||||
let upgrade = try_ready!(fut
|
||||
.poll()
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
self.upgrade = Some(upgrade);
|
||||
self.fut_ex.take();
|
||||
}
|
||||
|
||||
let service = try_ready!(self
|
||||
.fut
|
||||
.poll()
|
||||
@@ -162,19 +202,21 @@ where
|
||||
self.cfg.take().unwrap(),
|
||||
service,
|
||||
self.expect.take().unwrap(),
|
||||
self.upgrade.take(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Service` implementation for HTTP1 transport
|
||||
pub struct H1ServiceHandler<T, P, S, B, X> {
|
||||
pub struct H1ServiceHandler<T, P, S, B, X, U> {
|
||||
srv: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
cfg: ServiceConfig,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> H1ServiceHandler<T, P, S, B, X>
|
||||
impl<T, P, S, B, X, U> H1ServiceHandler<T, P, S, B, X, U>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -182,31 +224,41 @@ where
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
fn new(cfg: ServiceConfig, srv: S, expect: X) -> H1ServiceHandler<T, P, S, B, X> {
|
||||
fn new(
|
||||
cfg: ServiceConfig,
|
||||
srv: S,
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
) -> H1ServiceHandler<T, P, S, B, X, U> {
|
||||
H1ServiceHandler {
|
||||
srv: CloneableService::new(srv),
|
||||
expect: CloneableService::new(expect),
|
||||
upgrade: upgrade.map(|s| CloneableService::new(s)),
|
||||
cfg,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> Service for H1ServiceHandler<T, P, S, B, X>
|
||||
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>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type Future = Dispatcher<T, S, B, X>;
|
||||
type Future = Dispatcher<T, S, B, X, U>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
let ready = self
|
||||
@@ -243,6 +295,7 @@ where
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
self.upgrade.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -256,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 {
|
||||
@@ -269,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>);
|
||||
@@ -295,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>);
|
||||
@@ -319,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;
|
||||
|
40
actix-http/src/h1/upgrade.rs
Normal file
40
actix-http/src/h1/upgrade.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::FutureResult;
|
||||
use futures::{Async, Poll};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::h1::Codec;
|
||||
use crate::request::Request;
|
||||
|
||||
pub struct UpgradeHandler<T>(PhantomData<T>);
|
||||
|
||||
impl<T> NewService for UpgradeHandler<T> {
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = ();
|
||||
type Error = Error;
|
||||
type Service = UpgradeHandler<T>;
|
||||
type InitError = Error;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Service for UpgradeHandler<T> {
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = ();
|
||||
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, _: Self::Request) -> Self::Future {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
92
actix-http/src/h1/utils.rs
Normal file
92
actix-http/src/h1/utils.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use futures::{Async, Future, Poll, Sink};
|
||||
|
||||
use crate::body::{BodySize, MessageBody, ResponseBody};
|
||||
use crate::error::Error;
|
||||
use crate::h1::{Codec, Message};
|
||||
use crate::response::Response;
|
||||
|
||||
/// Send http/1 response
|
||||
pub struct SendResponse<T, B> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
body: Option<ResponseBody<B>>,
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
|
||||
impl<T, B> SendResponse<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
pub fn new(framed: Framed<T, Codec>, response: Response<B>) -> Self {
|
||||
let (res, body) = response.into_parts();
|
||||
|
||||
SendResponse {
|
||||
res: Some((res, body.size()).into()),
|
||||
body: Some(body),
|
||||
framed: Some(framed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> Future for SendResponse<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Item = Framed<T, Codec>;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
let mut body_ready = self.body.is_some();
|
||||
let framed = self.framed.as_mut().unwrap();
|
||||
|
||||
// send body
|
||||
if self.res.is_none() && self.body.is_some() {
|
||||
while body_ready && self.body.is_some() && !framed.is_write_buf_full() {
|
||||
match self.body.as_mut().unwrap().poll_next()? {
|
||||
Async::Ready(item) => {
|
||||
// body is done
|
||||
if item.is_none() {
|
||||
let _ = self.body.take();
|
||||
}
|
||||
framed.force_send(Message::Chunk(item))?;
|
||||
}
|
||||
Async::NotReady => body_ready = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush write buffer
|
||||
if !framed.is_write_buf_empty() {
|
||||
match framed.poll_complete()? {
|
||||
Async::Ready(_) => {
|
||||
if body_ready {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
// send response
|
||||
if let Some(res) = self.res.take() {
|
||||
framed.force_send(res)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.body.is_some() {
|
||||
if body_ready {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(self.framed.take().unwrap()))
|
||||
}
|
||||
}
|
@@ -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),
|
||||
@@ -153,10 +154,10 @@ where
|
||||
fn prepare_response(
|
||||
&self,
|
||||
head: &ResponseHead,
|
||||
length: &mut BodySize,
|
||||
size: &mut BodySize,
|
||||
) -> http::Response<()> {
|
||||
let mut has_date = false;
|
||||
let mut skip_len = length != &BodySize::Stream;
|
||||
let mut skip_len = size != &BodySize::Stream;
|
||||
|
||||
let mut res = http::Response::new(());
|
||||
*res.status_mut() = head.status;
|
||||
@@ -166,14 +167,14 @@ where
|
||||
match head.status {
|
||||
http::StatusCode::NO_CONTENT
|
||||
| http::StatusCode::CONTINUE
|
||||
| http::StatusCode::PROCESSING => *length = BodySize::None,
|
||||
| http::StatusCode::PROCESSING => *size = BodySize::None,
|
||||
http::StatusCode::SWITCHING_PROTOCOLS => {
|
||||
skip_len = true;
|
||||
*length = BodySize::Stream;
|
||||
*size = BodySize::Stream;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
let _ = match length {
|
||||
let _ = match size {
|
||||
BodySize::None | BodySize::Stream => None,
|
||||
BodySize::Empty => res
|
||||
.headers_mut()
|
||||
@@ -229,16 +230,15 @@ where
|
||||
let (res, body) = res.into().replace_body(());
|
||||
|
||||
let mut send = send.take().unwrap();
|
||||
let mut length = body.length();
|
||||
let h2_res = self.prepare_response(res.head(), &mut length);
|
||||
let mut size = body.size();
|
||||
let h2_res = self.prepare_response(res.head(), &mut size);
|
||||
|
||||
let stream = send
|
||||
.send_response(h2_res, length.is_eof())
|
||||
.map_err(|e| {
|
||||
let stream =
|
||||
send.send_response(h2_res, size.is_eof()).map_err(|e| {
|
||||
trace!("Error sending h2 response: {:?}", e);
|
||||
})?;
|
||||
|
||||
if length.is_eof() {
|
||||
if size.is_eof() {
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
self.state = ServiceResponseState::SendPayload(stream, body);
|
||||
@@ -251,16 +251,15 @@ where
|
||||
let (res, body) = res.replace_body(());
|
||||
|
||||
let mut send = send.take().unwrap();
|
||||
let mut length = body.length();
|
||||
let h2_res = self.prepare_response(res.head(), &mut length);
|
||||
let mut size = body.size();
|
||||
let h2_res = self.prepare_response(res.head(), &mut size);
|
||||
|
||||
let stream = send
|
||||
.send_response(h2_res, length.is_eof())
|
||||
.map_err(|e| {
|
||||
let stream =
|
||||
send.send_response(h2_res, size.is_eof()).map_err(|e| {
|
||||
trace!("Error sending h2 response: {:?}", e);
|
||||
})?;
|
||||
|
||||
if length.is_eof() {
|
||||
if size.is_eof() {
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
self.state = ServiceResponseState::SendPayload(
|
||||
|
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::{io, mem, ptr, slice};
|
||||
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use http::Version;
|
||||
use std::{mem, ptr, slice};
|
||||
|
||||
const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\
|
||||
2021222324252627282930313233343536373839\
|
||||
@@ -167,6 +168,18 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Writer<'a>(pub &'a mut BytesMut);
|
||||
|
||||
impl<'a> io::Write for Writer<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -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> {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
//! Http response
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::io::Write;
|
||||
use std::{fmt, io, str};
|
||||
use std::{fmt, str};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::future::{ok, FutureResult, IntoFuture};
|
||||
@@ -51,13 +51,9 @@ impl Response<Body> {
|
||||
/// Constructs an error response
|
||||
#[inline]
|
||||
pub fn from_error(error: Error) -> Response {
|
||||
let mut resp = error.as_response_error().error_response();
|
||||
let mut buf = BytesMut::new();
|
||||
let _ = write!(Writer(&mut buf), "{}", error);
|
||||
resp.headers_mut()
|
||||
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
||||
let mut resp = error.as_response_error().render_response();
|
||||
resp.error = Some(error);
|
||||
resp.set_body(Body::from(buf))
|
||||
resp
|
||||
}
|
||||
|
||||
/// Convert response to response with body
|
||||
@@ -210,6 +206,18 @@ impl<B> Response<B> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Split response and body
|
||||
pub fn into_parts(self) -> (Response<()>, ResponseBody<B>) {
|
||||
(
|
||||
Response {
|
||||
head: self.head,
|
||||
body: ResponseBody::Body(()),
|
||||
error: self.error,
|
||||
},
|
||||
self.body,
|
||||
)
|
||||
}
|
||||
|
||||
/// Drop request's body
|
||||
pub fn drop_body(self) -> Response<()> {
|
||||
Response {
|
||||
@@ -264,7 +272,7 @@ impl<B: MessageBody> fmt::Debug for Response<B> {
|
||||
for (key, val) in self.head.headers.iter() {
|
||||
let _ = writeln!(f, " {:?}: {:?}", key, val);
|
||||
}
|
||||
let _ = writeln!(f, " body: {:?}", self.body.length());
|
||||
let _ = writeln!(f, " body: {:?}", self.body.size());
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -297,18 +305,6 @@ impl<'a> Iterator for CookieIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Writer<'a>(pub &'a mut BytesMut);
|
||||
|
||||
impl<'a> io::Write for Writer<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// An HTTP response builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `Response` through a
|
||||
|
@@ -1,8 +1,10 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, io};
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig};
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
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};
|
||||
@@ -18,10 +20,11 @@ use crate::response::Response;
|
||||
use crate::{h1, h2::Dispatcher};
|
||||
|
||||
/// `NewService` HTTP1.1/HTTP2 transport implementation
|
||||
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler> {
|
||||
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler<T>> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
@@ -57,6 +60,7 @@ where
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: h1::ExpectHandler,
|
||||
upgrade: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -70,12 +74,13 @@ where
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: h1::ExpectHandler,
|
||||
upgrade: None,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> HttpService<T, P, S, B, X>
|
||||
impl<T, P, S, B, X, U> HttpService<T, P, S, B, X, U>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -88,24 +93,44 @@ where
|
||||
/// Service get called with request that contains `EXPECT` header.
|
||||
/// Service must return request in case of success, in that case
|
||||
/// request will be forwarded to main service.
|
||||
pub fn expect<U>(self, expect: U) -> HttpService<T, P, S, B, U>
|
||||
pub fn expect<X1>(self, expect: X1) -> HttpService<T, P, S, B, X1, U>
|
||||
where
|
||||
U: NewService<Request = Request, Response = Request>,
|
||||
U::Error: Into<Error>,
|
||||
U::InitError: fmt::Debug,
|
||||
X1: NewService<Request = Request, Response = Request>,
|
||||
X1::Error: Into<Error>,
|
||||
X1::InitError: fmt::Debug,
|
||||
{
|
||||
HttpService {
|
||||
expect,
|
||||
cfg: self.cfg,
|
||||
srv: self.srv,
|
||||
upgrade: self.upgrade,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide service for custom `Connection: UPGRADE` support.
|
||||
///
|
||||
/// If service is provided then normal requests handling get halted
|
||||
/// and this service get called with original request and framed object.
|
||||
pub fn upgrade<U1>(self, upgrade: Option<U1>) -> HttpService<T, P, S, B, X, U1>
|
||||
where
|
||||
U1: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U1::Error: fmt::Display,
|
||||
U1::InitError: fmt::Debug,
|
||||
{
|
||||
HttpService {
|
||||
upgrade,
|
||||
cfg: self.cfg,
|
||||
srv: self.srv,
|
||||
expect: self.expect,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> NewService<SrvConfig> for HttpService<T, P, S, B, X>
|
||||
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,
|
||||
@@ -115,19 +140,24 @@ where
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
type Request = ServerIo<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type InitError = ();
|
||||
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Future = HttpServiceResponse<T, P, S, B, X>;
|
||||
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
|
||||
type Future = HttpServiceResponse<T, P, S, B, X, U>;
|
||||
|
||||
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
|
||||
HttpServiceResponse {
|
||||
fut: self.srv.new_service(cfg).into_future(),
|
||||
fut_ex: Some(self.expect.new_service(&())),
|
||||
fut_upg: self.upgrade.as_ref().map(|f| f.new_service(&())),
|
||||
expect: None,
|
||||
upgrade: None,
|
||||
cfg: Some(self.cfg.clone()),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@@ -135,17 +165,26 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HttpServiceResponse<T, P, S: NewService<SrvConfig>, B, X: NewService> {
|
||||
pub struct HttpServiceResponse<
|
||||
T,
|
||||
P,
|
||||
S: NewService<SrvConfig>,
|
||||
B,
|
||||
X: NewService,
|
||||
U: NewService,
|
||||
> {
|
||||
fut: S::Future,
|
||||
fut_ex: Option<X::Future>,
|
||||
fut_upg: Option<U::Future>,
|
||||
expect: Option<X::Service>,
|
||||
upgrade: Option<U::Service>,
|
||||
cfg: Option<ServiceConfig>,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> Future for HttpServiceResponse<T, P, S, B, X>
|
||||
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,
|
||||
@@ -155,8 +194,11 @@ where
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
U: NewService<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
type Item = HttpServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Item = HttpServiceHandler<T, P, S::Service, B, X::Service, U::Service>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
@@ -168,6 +210,14 @@ where
|
||||
self.fut_ex.take();
|
||||
}
|
||||
|
||||
if let Some(ref mut fut) = self.fut_upg {
|
||||
let upgrade = try_ready!(fut
|
||||
.poll()
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
self.upgrade = Some(upgrade);
|
||||
self.fut_ex.take();
|
||||
}
|
||||
|
||||
let service = try_ready!(self
|
||||
.fut
|
||||
.poll()
|
||||
@@ -176,19 +226,21 @@ where
|
||||
self.cfg.take().unwrap(),
|
||||
service,
|
||||
self.expect.take().unwrap(),
|
||||
self.upgrade.take(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Service` implementation for http transport
|
||||
pub struct HttpServiceHandler<T, P, S, B, X> {
|
||||
pub struct HttpServiceHandler<T, P, S, B, X, U> {
|
||||
srv: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
upgrade: Option<CloneableService<U>>,
|
||||
cfg: ServiceConfig,
|
||||
_t: PhantomData<(T, P, B, X)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> HttpServiceHandler<T, P, S, B, X>
|
||||
impl<T, P, S, B, X, U> HttpServiceHandler<T, P, S, B, X, U>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
@@ -197,20 +249,28 @@ where
|
||||
B: MessageBody + 'static,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
fn new(cfg: ServiceConfig, srv: S, expect: X) -> HttpServiceHandler<T, P, S, B, X> {
|
||||
fn new(
|
||||
cfg: ServiceConfig,
|
||||
srv: S,
|
||||
expect: X,
|
||||
upgrade: Option<U>,
|
||||
) -> HttpServiceHandler<T, P, S, B, X, U> {
|
||||
HttpServiceHandler {
|
||||
cfg,
|
||||
srv: CloneableService::new(srv),
|
||||
expect: CloneableService::new(expect),
|
||||
upgrade: upgrade.map(|s| CloneableService::new(s)),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> Service for HttpServiceHandler<T, P, S, B, X>
|
||||
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,
|
||||
@@ -218,11 +278,13 @@ where
|
||||
B: MessageBody + 'static,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
type Request = ServerIo<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type Future = HttpServiceHandlerResponse<T, S, B, X>;
|
||||
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
let ready = self
|
||||
@@ -257,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,
|
||||
@@ -266,6 +329,7 @@ where
|
||||
server::handshake(io),
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
peer_addr,
|
||||
))),
|
||||
}
|
||||
}
|
||||
@@ -275,6 +339,7 @@ where
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
self.upgrade.clone(),
|
||||
)),
|
||||
},
|
||||
_ => HttpServiceHandlerResponse {
|
||||
@@ -284,23 +349,26 @@ where
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
self.upgrade.clone(),
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum State<T, S, B, X>
|
||||
enum State<T, S, B, X, U>
|
||||
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>,
|
||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1(h1::Dispatcher<T, S, B, X>),
|
||||
H1(h1::Dispatcher<T, S, B, X, U>),
|
||||
H2(Dispatcher<Io<T>, S, B>),
|
||||
Unknown(
|
||||
Option<(
|
||||
@@ -309,14 +377,22 @@ where
|
||||
ServiceConfig,
|
||||
CloneableService<S>,
|
||||
CloneableService<X>,
|
||||
Option<CloneableService<U>>,
|
||||
)>,
|
||||
),
|
||||
Handshake(
|
||||
Option<(
|
||||
Handshake<Io<T>, Bytes>,
|
||||
ServiceConfig,
|
||||
CloneableService<S>,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
),
|
||||
Handshake(Option<(Handshake<Io<T>, Bytes>, ServiceConfig, CloneableService<S>)>),
|
||||
}
|
||||
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X>
|
||||
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,
|
||||
@@ -324,15 +400,17 @@ where
|
||||
B: MessageBody + 'static,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
state: State<T, S, B, X>,
|
||||
state: State<T, S, B, X, U>,
|
||||
}
|
||||
|
||||
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
||||
|
||||
impl<T, S, B, X> Future for HttpServiceHandlerResponse<T, S, B, X>
|
||||
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,
|
||||
@@ -340,6 +418,8 @@ where
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
U: Service<Request = (Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = DispatchError;
|
||||
@@ -366,14 +446,19 @@ where
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
let (io, buf, cfg, srv, expect) = data.take().unwrap();
|
||||
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,
|
||||
@@ -383,6 +468,7 @@ where
|
||||
None,
|
||||
srv,
|
||||
expect,
|
||||
upgrade,
|
||||
))
|
||||
}
|
||||
self.poll()
|
||||
@@ -400,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()
|
||||
}
|
||||
}
|
||||
@@ -453,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)
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,12 @@
|
||||
//! Test Various helpers for Actix applications to use during testing.
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bytes::Bytes;
|
||||
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};
|
||||
use http::{HttpTryFrom, Method, Uri, Version};
|
||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||
@@ -181,3 +185,86 @@ impl TestRequest {
|
||||
fn parts(parts: &mut Option<Inner>) -> &mut Inner {
|
||||
parts.as_mut().expect("cannot reuse test request builder")
|
||||
}
|
||||
|
||||
/// Async io buffer
|
||||
pub struct TestBuffer {
|
||||
pub read_buf: BytesMut,
|
||||
pub write_buf: BytesMut,
|
||||
pub err: Option<io::Error>,
|
||||
}
|
||||
|
||||
impl TestBuffer {
|
||||
/// Create new TestBuffer instance
|
||||
pub fn new<T>(data: T) -> TestBuffer
|
||||
where
|
||||
BytesMut: From<T>,
|
||||
{
|
||||
TestBuffer {
|
||||
read_buf: BytesMut::from(data),
|
||||
write_buf: BytesMut::new(),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new empty TestBuffer instance
|
||||
pub fn empty() -> TestBuffer {
|
||||
TestBuffer::new("")
|
||||
}
|
||||
|
||||
/// Add extra data to read buffer.
|
||||
pub fn extend_read_buf<T: AsRef<[u8]>>(&mut self, data: T) {
|
||||
self.read_buf.extend_from_slice(data.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for TestBuffer {
|
||||
fn read(&mut self, dst: &mut [u8]) -> Result<usize, io::Error> {
|
||||
if self.read_buf.is_empty() {
|
||||
if self.err.is_some() {
|
||||
Err(self.err.take().unwrap())
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
|
||||
}
|
||||
} else {
|
||||
let size = std::cmp::min(self.read_buf.len(), dst.len());
|
||||
let b = self.read_buf.split_to(size);
|
||||
dst[..size].copy_from_slice(&b);
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for TestBuffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.write_buf.extend(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for TestBuffer {}
|
||||
|
||||
impl AsyncWrite for TestBuffer {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
fn write_buf<B: Buf>(&mut self, _: &mut B) -> Poll<usize, io::Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@@ -9,21 +9,18 @@ use derive_more::{Display, From};
|
||||
use http::{header, Method, StatusCode};
|
||||
|
||||
use crate::error::ResponseError;
|
||||
use crate::httpmessage::HttpMessage;
|
||||
use crate::request::Request;
|
||||
use crate::message::RequestHead;
|
||||
use crate::response::{Response, ResponseBuilder};
|
||||
|
||||
mod codec;
|
||||
mod frame;
|
||||
mod mask;
|
||||
mod proto;
|
||||
mod service;
|
||||
mod transport;
|
||||
|
||||
pub use self::codec::{Codec, Frame, Message};
|
||||
pub use self::frame::Parser;
|
||||
pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
|
||||
pub use self::service::VerifyWebSockets;
|
||||
pub use self::transport::Transport;
|
||||
|
||||
/// Websocket protocol errors
|
||||
@@ -112,7 +109,7 @@ impl ResponseError for HandshakeError {
|
||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
||||
// /// the returned response headers contain the first protocol in this list
|
||||
// /// which the server also knows.
|
||||
pub fn handshake(req: &Request) -> Result<ResponseBuilder, HandshakeError> {
|
||||
pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
|
||||
verify_handshake(req)?;
|
||||
Ok(handshake_response(req))
|
||||
}
|
||||
@@ -121,9 +118,9 @@ pub fn handshake(req: &Request) -> Result<ResponseBuilder, HandshakeError> {
|
||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
||||
// /// the returned response headers contain the first protocol in this list
|
||||
// /// which the server also knows.
|
||||
pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> {
|
||||
pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
|
||||
// WebSocket accepts only GET
|
||||
if *req.method() != Method::GET {
|
||||
if req.method != Method::GET {
|
||||
return Err(HandshakeError::GetMethodRequired);
|
||||
}
|
||||
|
||||
@@ -171,7 +168,7 @@ pub fn verify_handshake(req: &Request) -> Result<(), HandshakeError> {
|
||||
/// Create websocket's handshake response
|
||||
///
|
||||
/// This function returns handshake `Response`, ready to send to peer.
|
||||
pub fn handshake_response(req: &Request) -> ResponseBuilder {
|
||||
pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
|
||||
let key = {
|
||||
let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
|
||||
proto::hash_key(key.as_ref())
|
||||
@@ -195,13 +192,13 @@ mod tests {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
assert_eq!(
|
||||
HandshakeError::GetMethodRequired,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default().finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoWebsocketUpgrade,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default()
|
||||
@@ -209,7 +206,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoWebsocketUpgrade,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default()
|
||||
@@ -220,7 +217,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoConnectionUpgrade,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default()
|
||||
@@ -235,7 +232,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(
|
||||
HandshakeError::NoVersionHeader,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default()
|
||||
@@ -254,7 +251,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(
|
||||
HandshakeError::UnsupportedVersion,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default()
|
||||
@@ -273,7 +270,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(
|
||||
HandshakeError::BadWebsocketKey,
|
||||
verify_handshake(&req).err().unwrap()
|
||||
verify_handshake(req.head()).err().unwrap()
|
||||
);
|
||||
|
||||
let req = TestRequest::default()
|
||||
@@ -296,7 +293,7 @@ mod tests {
|
||||
.finish();
|
||||
assert_eq!(
|
||||
StatusCode::SWITCHING_PROTOCOLS,
|
||||
handshake_response(&req).finish().status()
|
||||
handshake_response(req.head()).finish().status()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,52 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, IntoFuture, Poll};
|
||||
|
||||
use crate::h1::Codec;
|
||||
use crate::request::Request;
|
||||
|
||||
use super::{verify_handshake, HandshakeError};
|
||||
|
||||
pub struct VerifyWebSockets<T> {
|
||||
_t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for VerifyWebSockets<T> {
|
||||
fn default() -> Self {
|
||||
VerifyWebSockets { _t: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> NewService for VerifyWebSockets<T> {
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = (Request, Framed<T, Codec>);
|
||||
type Error = (HandshakeError, Framed<T, Codec>);
|
||||
type InitError = ();
|
||||
type Service = VerifyWebSockets<T>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(VerifyWebSockets { _t: PhantomData })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Service for VerifyWebSockets<T> {
|
||||
type Request = (Request, Framed<T, Codec>);
|
||||
type Response = (Request, Framed<T, Codec>);
|
||||
type Error = (HandshakeError, Framed<T, Codec>);
|
||||
type Future = FutureResult<Self::Response, Self::Error>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (req, framed): (Request, Framed<T, Codec>)) -> Self::Future {
|
||||
match verify_handshake(&req) {
|
||||
Err(e) => Err((e, framed)).into_future(),
|
||||
Ok(_) => Ok((req, framed)).into_future(),
|
||||
}
|
||||
}
|
||||
}
|
@@ -31,9 +31,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
|
||||
fn test_h1_v2() {
|
||||
env_logger::init();
|
||||
let mut srv = TestServer::new(move || {
|
||||
HttpService::build()
|
||||
.finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
|
||||
.map(|_| ())
|
||||
HttpService::build().finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
|
||||
});
|
||||
let response = srv.block_on(srv.get("/").send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
@@ -5,10 +5,11 @@ use std::{net, thread};
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_http_test::TestServer;
|
||||
use actix_server_config::ServerConfig;
|
||||
use actix_service::{fn_cfg_factory, NewService};
|
||||
use actix_service::{fn_cfg_factory, fn_service, NewService};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::{self, ok, Future};
|
||||
use futures::stream::{once, Stream};
|
||||
use tokio_timer::sleep;
|
||||
|
||||
use actix_http::body::Body;
|
||||
use actix_http::error::PayloadError;
|
||||
@@ -34,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();
|
||||
@@ -49,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())
|
||||
})
|
||||
@@ -114,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())
|
||||
})
|
||||
@@ -153,6 +159,62 @@ fn test_h2_body() -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expect_continue() {
|
||||
let srv = TestServer::new(|| {
|
||||
HttpService::build()
|
||||
.expect(fn_service(|req: Request| {
|
||||
if req.head().uri.query() == Some("yes=") {
|
||||
Ok(req)
|
||||
} else {
|
||||
Err(error::ErrorPreconditionFailed("error"))
|
||||
}
|
||||
}))
|
||||
.finish(|_| future::ok::<_, ()>(Response::Ok().finish()))
|
||||
});
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length"));
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expect_continue_h1() {
|
||||
let srv = TestServer::new(|| {
|
||||
HttpService::build()
|
||||
.expect(fn_service(|req: Request| {
|
||||
sleep(Duration::from_millis(20)).then(move |_| {
|
||||
if req.head().uri.query() == Some("yes=") {
|
||||
Ok(req)
|
||||
} else {
|
||||
Err(error::ErrorPreconditionFailed("error"))
|
||||
}
|
||||
})
|
||||
}))
|
||||
.h1(|_| future::ok::<_, ()>(Response::Ok().finish()))
|
||||
});
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"GET /test HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 412 Precondition Failed\r\ncontent-length"));
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"GET /test?yes= HTTP/1.1\r\nexpect: 100-continue\r\n\r\n");
|
||||
let mut data = String::new();
|
||||
let _ = stream.read_to_string(&mut data);
|
||||
assert!(data.starts_with("HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slow_request() {
|
||||
let srv = TestServer::new(|| {
|
||||
|
76
actix-http/tests/test_ws.rs
Normal file
76
actix-http/tests/test_ws.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_http::{body, h1, ws, Error, HttpService, Request, Response};
|
||||
use actix_http_test::TestServer;
|
||||
use actix_utils::framed::FramedTransport;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::{self, ok};
|
||||
use futures::{Future, Sink, Stream};
|
||||
|
||||
fn ws_service<T: AsyncRead + AsyncWrite>(
|
||||
(req, framed): (Request, Framed<T, h1::Codec>),
|
||||
) -> impl Future<Item = (), Error = Error> {
|
||||
let res = ws::handshake(req.head()).unwrap().message_body(());
|
||||
|
||||
framed
|
||||
.send((res, body::BodySize::None).into())
|
||||
.map_err(|_| panic!())
|
||||
.and_then(|framed| {
|
||||
FramedTransport::new(framed.into_framed(ws::Codec::new()), service)
|
||||
.map_err(|_| panic!())
|
||||
})
|
||||
}
|
||||
|
||||
fn service(msg: ws::Frame) -> impl Future<Item = ws::Message, Error = Error> {
|
||||
let msg = match msg {
|
||||
ws::Frame::Ping(msg) => ws::Message::Pong(msg),
|
||||
ws::Frame::Text(text) => {
|
||||
ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string())
|
||||
}
|
||||
ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()),
|
||||
ws::Frame::Close(reason) => ws::Message::Close(reason),
|
||||
_ => panic!(),
|
||||
};
|
||||
ok(msg)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple() {
|
||||
let mut srv = TestServer::new(|| {
|
||||
HttpService::build()
|
||||
.upgrade(ws_service)
|
||||
.finish(|_| future::ok::<_, ()>(Response::NotFound()))
|
||||
});
|
||||
|
||||
// client service
|
||||
let framed = srv.ws().unwrap();
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Text("text".to_string())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Binary("text".into())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
|
||||
);
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Ping("text".into())))
|
||||
.unwrap();
|
||||
let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
|
||||
|
||||
let framed = srv
|
||||
.block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
|
||||
.unwrap();
|
||||
|
||||
let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
|
||||
assert_eq!(
|
||||
item,
|
||||
Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
|
||||
);
|
||||
}
|
@@ -2,4 +2,6 @@
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-04-xx
|
||||
|
||||
* Do not support nested multipart
|
||||
|
||||
* Split multipart support to separate crate
|
||||
|
@@ -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> {
|
||||
@@ -245,7 +236,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 +253,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 +330,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 +339,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,
|
||||
)))))
|
||||
))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -864,16 +831,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 +849,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,8 +866,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll().unwrap() {
|
||||
|
@@ -1,5 +1,9 @@
|
||||
# Changes
|
||||
|
||||
## [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-alpha.6"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Session for actix web framework."
|
||||
readme = "README.md"
|
||||
@@ -24,12 +24,12 @@ default = ["cookie-session"]
|
||||
cookie-session = ["actix-web/secure-cookies"]
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.4"
|
||||
actix-web = "1.0.0-alpha.6"
|
||||
actix-service = "0.3.4"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
futures = "0.1.25"
|
||||
hashbrown = "0.1.8"
|
||||
hashbrown = "0.2.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
time = "0.1"
|
||||
|
@@ -120,7 +120,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 {
|
||||
@@ -256,13 +256,13 @@ impl CookieSession {
|
||||
}
|
||||
}
|
||||
|
||||
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,23 +283,22 @@ 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>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
//self.service.poll_ready().map_err(|e| e.into())
|
||||
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);
|
||||
|
@@ -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,9 +18,9 @@ name = "actix_web_actors"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.8.0-alpha.2"
|
||||
actix-web = "1.0.0-alpha.3"
|
||||
actix-http = "0.1.0-alpha.3"
|
||||
actix = "0.8.0"
|
||||
actix-web = "1.0.0-alpha.5"
|
||||
actix-http = "0.1.0-alpha.5"
|
||||
actix-codec = "0.1.2"
|
||||
bytes = "0.4"
|
||||
futures = "0.1.25"
|
||||
|
@@ -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,9 @@
|
||||
# Changes
|
||||
|
||||
## [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-alpha.6"
|
||||
description = "Actix web proc macros"
|
||||
readme = "README.md"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
@@ -16,6 +16,7 @@ quote = "0.6"
|
||||
syn = { version = "0.15", features = ["full", "parsing"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "1.0.0-alpha.2" }
|
||||
actix-http = { version = "0.1.0-alpha.2", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.6" }
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
futures = { version = "0.1" }
|
||||
|
@@ -1,118 +1,94 @@
|
||||
#![recursion_limit = "512"]
|
||||
//! Actix-web codegen module
|
||||
//!
|
||||
//! Generators for routes and scopes
|
||||
//!
|
||||
//! ## Route
|
||||
//!
|
||||
//! Macros:
|
||||
//!
|
||||
//! - [get](attr.get.html)
|
||||
//! - [post](attr.post.html)
|
||||
//! - [put](attr.put.html)
|
||||
//! - [delete](attr.delete.html)
|
||||
//!
|
||||
//! ### Attributes:
|
||||
//!
|
||||
//! - `"path"` - Raw literal string with path for which to register handle. Mandatory.
|
||||
//! - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
|
||||
//!
|
||||
//! ## Notes
|
||||
//!
|
||||
//! Function name can be specified as any expression that is going to be accessible to the generate
|
||||
//! code (e.g `my_guard` or `my_module::my_guard`)
|
||||
//!
|
||||
//! ## Example:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use actix_web::HttpResponse;
|
||||
//! use actix_web_codegen::get;
|
||||
//! use futures::{future, Future};
|
||||
//!
|
||||
//! #[get("/test")]
|
||||
//! fn async_test() -> impl Future<Item=HttpResponse, Error=actix_web::Error> {
|
||||
//! future::ok(HttpResponse::Ok().finish())
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod route;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
/// #[get("path")] attribute
|
||||
/// Creates route handler with `GET` method guard.
|
||||
///
|
||||
/// Syntax: `#[get("path"[, attributes])]`
|
||||
///
|
||||
/// ## Attributes:
|
||||
///
|
||||
/// - `"path"` - Raw literal string with path for which to register handler. Mandatory.
|
||||
/// - `guard="function_name"` - Registers function as guard using `actix_web::guard::fn_guard`
|
||||
#[proc_macro_attribute]
|
||||
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(args as syn::AttributeArgs);
|
||||
if args.is_empty() {
|
||||
panic!("invalid server definition, expected: #[get(\"some path\")]");
|
||||
}
|
||||
|
||||
// path
|
||||
let path = match args[0] {
|
||||
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
|
||||
let fname = quote!(#fname).to_string();
|
||||
fname.as_str()[1..fname.len() - 1].to_owned()
|
||||
}
|
||||
_ => panic!("resource path"),
|
||||
};
|
||||
|
||||
let ast: syn::ItemFn = syn::parse(input).unwrap();
|
||||
let name = ast.ident.clone();
|
||||
|
||||
(quote! {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct #name;
|
||||
|
||||
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
|
||||
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
|
||||
#ast
|
||||
actix_web::dev::HttpServiceFactory::register(
|
||||
actix_web::Resource::new(#path)
|
||||
.guard(actix_web::guard::Get())
|
||||
.to(#name), config);
|
||||
}
|
||||
}
|
||||
})
|
||||
.into()
|
||||
let gen = route::Args::new(&args, input, route::GuardType::Get);
|
||||
gen.generate()
|
||||
}
|
||||
|
||||
/// #[post("path")] attribute
|
||||
/// Creates route handler with `POST` method guard.
|
||||
///
|
||||
/// Syntax: `#[post("path"[, attributes])]`
|
||||
///
|
||||
/// Attributes are the same as in [get](attr.get.html)
|
||||
#[proc_macro_attribute]
|
||||
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(args as syn::AttributeArgs);
|
||||
if args.is_empty() {
|
||||
panic!("invalid server definition, expected: #[post(\"some path\")]");
|
||||
}
|
||||
|
||||
// path
|
||||
let path = match args[0] {
|
||||
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
|
||||
let fname = quote!(#fname).to_string();
|
||||
fname.as_str()[1..fname.len() - 1].to_owned()
|
||||
}
|
||||
_ => panic!("resource path"),
|
||||
};
|
||||
|
||||
let ast: syn::ItemFn = syn::parse(input).unwrap();
|
||||
let name = ast.ident.clone();
|
||||
|
||||
(quote! {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct #name;
|
||||
|
||||
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
|
||||
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
|
||||
#ast
|
||||
actix_web::dev::HttpServiceFactory::register(
|
||||
actix_web::Resource::new(#path)
|
||||
.guard(actix_web::guard::Post())
|
||||
.to(#name), config);
|
||||
}
|
||||
}
|
||||
})
|
||||
.into()
|
||||
let gen = route::Args::new(&args, input, route::GuardType::Post);
|
||||
gen.generate()
|
||||
}
|
||||
|
||||
/// #[put("path")] attribute
|
||||
/// Creates route handler with `PUT` method guard.
|
||||
///
|
||||
/// Syntax: `#[put("path"[, attributes])]`
|
||||
///
|
||||
/// Attributes are the same as in [get](attr.get.html)
|
||||
#[proc_macro_attribute]
|
||||
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(args as syn::AttributeArgs);
|
||||
if args.is_empty() {
|
||||
panic!("invalid server definition, expected: #[put(\"some path\")]");
|
||||
}
|
||||
|
||||
// path
|
||||
let path = match args[0] {
|
||||
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
|
||||
let fname = quote!(#fname).to_string();
|
||||
fname.as_str()[1..fname.len() - 1].to_owned()
|
||||
}
|
||||
_ => panic!("resource path"),
|
||||
};
|
||||
|
||||
let ast: syn::ItemFn = syn::parse(input).unwrap();
|
||||
let name = ast.ident.clone();
|
||||
|
||||
(quote! {
|
||||
#[allow(non_camel_case_types)]
|
||||
struct #name;
|
||||
|
||||
impl<P: 'static> actix_web::dev::HttpServiceFactory<P> for #name {
|
||||
fn register(self, config: &mut actix_web::dev::ServiceConfig<P>) {
|
||||
#ast
|
||||
actix_web::dev::HttpServiceFactory::register(
|
||||
actix_web::Resource::new(#path)
|
||||
.guard(actix_web::guard::Put())
|
||||
.to(#name), config);
|
||||
}
|
||||
}
|
||||
})
|
||||
.into()
|
||||
let gen = route::Args::new(&args, input, route::GuardType::Put);
|
||||
gen.generate()
|
||||
}
|
||||
|
||||
/// Creates route handler with `DELETE` method guard.
|
||||
///
|
||||
/// Syntax: `#[delete("path"[, attributes])]`
|
||||
///
|
||||
/// Attributes are the same as in [get](attr.get.html)
|
||||
#[proc_macro_attribute]
|
||||
pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let args = parse_macro_input!(args as syn::AttributeArgs);
|
||||
let gen = route::Args::new(&args, input, route::GuardType::Delete);
|
||||
gen.generate()
|
||||
}
|
||||
|
184
actix-web-codegen/src/route.rs
Normal file
184
actix-web-codegen/src/route.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
enum ResourceType {
|
||||
Async,
|
||||
Sync,
|
||||
}
|
||||
|
||||
impl fmt::Display for ResourceType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&ResourceType::Async => write!(f, "to_async"),
|
||||
&ResourceType::Sync => write!(f, "to"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum GuardType {
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
}
|
||||
|
||||
impl fmt::Display for GuardType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&GuardType::Get => write!(f, "Get"),
|
||||
&GuardType::Post => write!(f, "Post"),
|
||||
&GuardType::Put => write!(f, "Put"),
|
||||
&GuardType::Delete => write!(f, "Delete"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Args {
|
||||
name: syn::Ident,
|
||||
path: String,
|
||||
ast: syn::ItemFn,
|
||||
resource_type: ResourceType,
|
||||
pub guard: GuardType,
|
||||
pub extra_guards: Vec<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Args {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let ast = &self.ast;
|
||||
let guards = format!(".guard(actix_web::guard::{}())", self.guard);
|
||||
let guards = self.extra_guards.iter().fold(guards, |acc, val| {
|
||||
format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)
|
||||
});
|
||||
|
||||
write!(
|
||||
f,
|
||||
"
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct {name};
|
||||
|
||||
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});
|
||||
|
||||
actix_web::dev::HttpServiceFactory::register(resource, config)
|
||||
}}
|
||||
}}",
|
||||
name = self.name,
|
||||
ast = quote!(#ast),
|
||||
path = self.path,
|
||||
guards = guards,
|
||||
to = self.resource_type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_resource_type(typ: &syn::Type) -> ResourceType {
|
||||
let mut guess = ResourceType::Sync;
|
||||
|
||||
match typ {
|
||||
syn::Type::ImplTrait(typ) => {
|
||||
for bound in typ.bounds.iter() {
|
||||
match bound {
|
||||
syn::TypeParamBound::Trait(bound) => {
|
||||
for bound in bound.path.segments.iter() {
|
||||
if bound.ident == "Future" {
|
||||
guess = ResourceType::Async;
|
||||
break;
|
||||
} else if bound.ident == "Responder" {
|
||||
guess = ResourceType::Sync;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
guess
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn new(
|
||||
args: &Vec<syn::NestedMeta>,
|
||||
input: TokenStream,
|
||||
guard: GuardType,
|
||||
) -> Self {
|
||||
if args.is_empty() {
|
||||
panic!(
|
||||
"invalid server definition, expected: #[{}(\"some path\")]",
|
||||
guard
|
||||
);
|
||||
}
|
||||
|
||||
let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function");
|
||||
let name = ast.ident.clone();
|
||||
|
||||
let mut extra_guards = Vec::new();
|
||||
let mut path = None;
|
||||
for arg in args {
|
||||
match arg {
|
||||
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
|
||||
if path.is_some() {
|
||||
panic!("Multiple paths specified! Should be only one!")
|
||||
}
|
||||
let fname = quote!(#fname).to_string();
|
||||
path = Some(fname.as_str()[1..fname.len() - 1].to_owned())
|
||||
}
|
||||
syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => {
|
||||
match ident.ident.to_string().to_lowercase().as_str() {
|
||||
"guard" => match ident.lit {
|
||||
syn::Lit::Str(ref text) => extra_guards.push(text.value()),
|
||||
_ => panic!("Attribute guard expects literal string!"),
|
||||
},
|
||||
attr => panic!(
|
||||
"Unknown attribute key is specified: {}. Allowed: guard",
|
||||
attr
|
||||
),
|
||||
}
|
||||
}
|
||||
attr => panic!("Unknown attribute{:?}", attr),
|
||||
}
|
||||
}
|
||||
|
||||
let resource_type = if ast.asyncness.is_some() {
|
||||
ResourceType::Async
|
||||
} else {
|
||||
match ast.decl.output {
|
||||
syn::ReturnType::Default => {
|
||||
panic!("Function {} has no return type. Cannot be used as handler")
|
||||
}
|
||||
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
|
||||
}
|
||||
};
|
||||
|
||||
let path = path.unwrap();
|
||||
|
||||
Self {
|
||||
name,
|
||||
path,
|
||||
ast,
|
||||
resource_type,
|
||||
guard,
|
||||
extra_guards,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&self) -> TokenStream {
|
||||
let text = self.to_string();
|
||||
|
||||
match text.parse() {
|
||||
Ok(res) => res,
|
||||
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text),
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +1,40 @@
|
||||
use actix_http::HttpService;
|
||||
use actix_http_test::TestServer;
|
||||
use actix_web::{get, http, App, HttpResponse, Responder};
|
||||
use actix_web::{http, App, HttpResponse, Responder};
|
||||
use actix_web_codegen::get;
|
||||
use futures::{future, Future};
|
||||
|
||||
#[get("/test")]
|
||||
fn test() -> impl Responder {
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
fn auto_async() -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
|
||||
future::ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
fn auto_sync() -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
|
||||
future::ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_body() {
|
||||
let mut srv = TestServer::new(|| HttpService::new(App::new().service(test)));
|
||||
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||
let response = srv.block_on(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync)));
|
||||
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||
let response = srv.block_on(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_async() {
|
||||
let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_async)));
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||
let response = srv.block_on(request.send()).unwrap();
|
||||
|
@@ -1,5 +1,23 @@
|
||||
# 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
|
||||
|
||||
* Do not set any default headers
|
||||
|
||||
### Added
|
||||
|
||||
* Add Debug impl for BoxedSocket
|
||||
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
### Changed
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "0.1.0-alpha.4"
|
||||
version = "0.1.0-alpha.6"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http client."
|
||||
readme = "README.md"
|
||||
@@ -36,9 +36,9 @@ flate2-zlib = ["actix-http/flate2-zlib"]
|
||||
flate2-rust = ["actix-http/flate2-rust"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.1.1"
|
||||
actix-service = "0.3.4"
|
||||
actix-http = "0.1.0-alpha.4"
|
||||
actix-codec = "0.1.2"
|
||||
actix-service = "0.3.6"
|
||||
actix-http = "0.1.0-alpha.5"
|
||||
base64 = "0.10.1"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
@@ -55,8 +55,8 @@ openssl = { version="0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-web = { version = "1.0.0-alpha.4", features=["ssl"] }
|
||||
actix-http = { version = "0.1.0-alpha.4", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.6", features=["ssl"] }
|
||||
actix-http = { version = "0.1.0-alpha.5", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-utils = "0.3.4"
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::io;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_http::body::Body;
|
||||
@@ -102,6 +102,12 @@ impl<T: AsyncRead + AsyncWrite> AsyncSocket for Socket<T> {
|
||||
|
||||
pub struct BoxedSocket(Box<dyn AsyncSocket>);
|
||||
|
||||
impl fmt::Debug for BoxedSocket {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "BoxedSocket")
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for BoxedSocket {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.as_read_mut().read(buf)
|
||||
|
@@ -61,7 +61,6 @@ pub struct ClientRequest {
|
||||
pub(crate) head: RequestHead,
|
||||
err: Option<HttpError>,
|
||||
cookies: Option<CookieJar>,
|
||||
default_headers: bool,
|
||||
response_decompress: bool,
|
||||
timeout: Option<Duration>,
|
||||
config: Rc<ClientConfig>,
|
||||
@@ -79,7 +78,6 @@ impl ClientRequest {
|
||||
err: None,
|
||||
cookies: None,
|
||||
timeout: None,
|
||||
default_headers: true,
|
||||
response_decompress: true,
|
||||
}
|
||||
.method(method)
|
||||
@@ -316,13 +314,6 @@ impl ClientRequest {
|
||||
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
|
||||
}
|
||||
|
||||
/// Disable automatic decompress of response's body
|
||||
pub fn no_decompress(mut self) -> Self {
|
||||
self.response_decompress = false;
|
||||
@@ -392,36 +383,6 @@ impl ClientRequest {
|
||||
return Either::A(err(InvalidUrl::UnknownScheme.into()));
|
||||
}
|
||||
|
||||
// set default headers
|
||||
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
|
||||
if !self.head.headers.contains_key(&header::USER_AGENT) {
|
||||
self.head.headers.insert(
|
||||
header::USER_AGENT,
|
||||
HeaderValue::from_static(concat!("awc/", env!("CARGO_PKG_VERSION"))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// set cookies
|
||||
if let Some(ref mut jar) = self.cookies {
|
||||
let mut cookie = String::new();
|
||||
@@ -436,7 +397,7 @@ impl ClientRequest {
|
||||
);
|
||||
}
|
||||
|
||||
let slf = self;
|
||||
let mut slf = self;
|
||||
|
||||
// enable br only for https
|
||||
#[cfg(any(
|
||||
@@ -444,7 +405,8 @@ impl ClientRequest {
|
||||
feature = "flate2-zlib",
|
||||
feature = "flate2-rust"
|
||||
))]
|
||||
let slf = {
|
||||
{
|
||||
if slf.response_decompress {
|
||||
let https = slf
|
||||
.head
|
||||
.uri
|
||||
@@ -453,16 +415,16 @@ impl ClientRequest {
|
||||
.unwrap_or(true);
|
||||
|
||||
if https {
|
||||
slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
|
||||
slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
|
||||
} else {
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
{
|
||||
slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
|
||||
}
|
||||
#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))]
|
||||
slf
|
||||
slf = slf
|
||||
.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let head = slf.head;
|
||||
let config = slf.config.as_ref();
|
||||
@@ -582,22 +544,36 @@ 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")
|
||||
.content_length(100);
|
||||
assert!(req.headers().contains_key(header::CONTENT_TYPE));
|
||||
assert!(req.headers().contains_key(header::DATE));
|
||||
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()
|
||||
@@ -612,12 +588,10 @@ mod tests {
|
||||
.unwrap(),
|
||||
"111"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_header_override() {
|
||||
test::run_on(|| {
|
||||
let req = Client::build()
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
@@ -633,12 +607,10 @@ mod tests {
|
||||
.unwrap(),
|
||||
"222"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_basic_auth() {
|
||||
test::run_on(|| {
|
||||
let req = Client::new()
|
||||
.get("/")
|
||||
.basic_auth("username", Some("password"));
|
||||
@@ -662,12 +634,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
|
||||
@@ -678,6 +648,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);
|
||||
}
|
||||
}
|
||||
|
190
awc/src/ws.rs
190
awc/src/ws.rs
@@ -1,13 +1,11 @@
|
||||
//! Websockets client
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::Write;
|
||||
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;
|
||||
@@ -33,7 +31,6 @@ pub struct WebsocketsRequest {
|
||||
protocols: Option<String>,
|
||||
max_size: usize,
|
||||
server_mode: bool,
|
||||
default_headers: bool,
|
||||
cookies: Option<CookieJar>,
|
||||
config: Rc<ClientConfig>,
|
||||
}
|
||||
@@ -63,7 +60,6 @@ impl WebsocketsRequest {
|
||||
max_size: 65_536,
|
||||
server_mode: false,
|
||||
cookies: None,
|
||||
default_headers: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,13 +115,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 +177,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 +220,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,15 +261,16 @@ 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()
|
||||
@@ -387,7 +345,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 +358,117 @@ 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"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basics() {
|
||||
let req = Client::new()
|
||||
.ws("/")
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
use std::io::Write;
|
||||
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 +11,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;
|
||||
|
||||
@@ -94,11 +97,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 +110,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 +426,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 +487,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();
|
||||
|
@@ -11,7 +11,7 @@ use futures::future::{ok, Either};
|
||||
use futures::{Future, Sink, Stream};
|
||||
use tokio_tcp::TcpStream;
|
||||
|
||||
use actix_http::{body::BodySize, h1, ws, ResponseError, ServiceConfig};
|
||||
use actix_http::{body::BodySize, h1, ws, Request, ResponseError, ServiceConfig};
|
||||
|
||||
fn ws_service(req: ws::Frame) -> impl Future<Item = ws::Message, Error = io::Error> {
|
||||
match req {
|
||||
@@ -40,10 +40,11 @@ fn test_simple() {
|
||||
fn_service(|io: Io<TcpStream>| Ok(io.into_parts().0))
|
||||
.and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())))
|
||||
.and_then(TakeItem::new().map_err(|_| ()))
|
||||
.and_then(|(req, framed): (_, Framed<_, _>)| {
|
||||
.and_then(
|
||||
|(req, framed): (Option<h1::Message<Request>>, Framed<_, _>)| {
|
||||
// validate request
|
||||
if let Some(h1::Message::Item(req)) = req {
|
||||
match ws::verify_handshake(&req) {
|
||||
match ws::verify_handshake(req.head()) {
|
||||
Err(e) => {
|
||||
// validation failed
|
||||
let res = e.error_response();
|
||||
@@ -58,7 +59,7 @@ fn test_simple() {
|
||||
)
|
||||
}
|
||||
Ok(_) => {
|
||||
let res = ws::handshake_response(&req).finish();
|
||||
let res = ws::handshake_response(req.head()).finish();
|
||||
Either::B(
|
||||
// send handshake response
|
||||
framed
|
||||
@@ -80,7 +81,8 @@ fn test_simple() {
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
// client service
|
||||
|
@@ -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"))
|
||||
|
@@ -1,7 +1,6 @@
|
||||
use actix_http::Error;
|
||||
use actix_rt::System;
|
||||
use bytes::BytesMut;
|
||||
use futures::{future::lazy, Future, Stream};
|
||||
use futures::{future::lazy, Future};
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
std::env::set_var("RUST_LOG", "actix_http=trace");
|
||||
@@ -13,17 +12,14 @@ fn main() -> Result<(), Error> {
|
||||
.header("User-Agent", "Actix-web")
|
||||
.send() // <- Send http request
|
||||
.from_err()
|
||||
.and_then(|response| {
|
||||
.and_then(|mut response| {
|
||||
// <- server http response
|
||||
println!("Response: {:?}", response);
|
||||
|
||||
// read response body
|
||||
response
|
||||
.body()
|
||||
.from_err()
|
||||
.fold(BytesMut::new(), move |mut acc, chunk| {
|
||||
acc.extend_from_slice(&chunk);
|
||||
Ok::<_, Error>(acc)
|
||||
})
|
||||
.map(|body| println!("Downloaded: {:?} bytes", body.len()))
|
||||
})
|
||||
}))
|
||||
|
659
src/app.rs
659
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,45 +23,49 @@ 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 = (),
|
||||
>,
|
||||
{
|
||||
/// Set application data. Applicatin data could be accessed
|
||||
/// Set application data. Application data could be accessed
|
||||
/// by using `Data<T>` extractor where `T` is data type.
|
||||
///
|
||||
/// **Note**: http server accepts an application factory rather than
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
24
src/data.rs
24
src/data.rs
@@ -25,15 +25,16 @@ pub(crate) trait DataFactoryResult {
|
||||
/// during application configuration process
|
||||
/// with `App::data()` method.
|
||||
///
|
||||
/// Applicatin data could be accessed by using `Data<T>`
|
||||
/// Application data could be accessed by using `Data<T>`
|
||||
/// extractor where `T` is data type.
|
||||
///
|
||||
/// **Note**: http server accepts an application factory rather than
|
||||
/// an application instance. Http server constructs an application
|
||||
/// instance for each thread, thus application data must be constructed
|
||||
/// multiple times. If you want to share data between different
|
||||
/// threads, a shared object should be used, e.g. `Arc`. Application
|
||||
/// data does not need to be `Send` or `Sync`.
|
||||
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application
|
||||
/// data does not need to be `Send` or `Sync`. Internally `Data` instance
|
||||
/// uses `Arc`.
|
||||
///
|
||||
/// If route data is not set for a handler, using `Data<T>` extractor would
|
||||
/// cause *Internal Server Error* response.
|
||||
@@ -60,6 +61,7 @@ pub(crate) trait DataFactoryResult {
|
||||
/// web::get().to(index)));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Data<T>(Arc<T>);
|
||||
|
||||
impl<T> Data<T> {
|
||||
@@ -87,15 +89,21 @@ 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 {
|
||||
log::debug!(
|
||||
"Failed to construct App-level Data extractor. \
|
||||
Request path: {:?}",
|
||||
req.path()
|
||||
);
|
||||
Err(ErrorInternalServerError(
|
||||
"App data is not configured, to configure use App::data()",
|
||||
))
|
||||
@@ -226,15 +234,17 @@ 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 {
|
||||
log::debug!("Failed to construct Route-level Data extractor");
|
||||
Err(ErrorInternalServerError(
|
||||
"Route data is not configured, to configure use Route::data()",
|
||||
))
|
||||
|
@@ -31,7 +31,7 @@ pub enum UrlencodedError {
|
||||
#[display(fmt = "Can not decode chunked transfer encoding")]
|
||||
Chunked,
|
||||
/// Payload size is bigger than allowed. (default: 256kB)
|
||||
#[display(fmt = "Urlencoded payload size is bigger than allowed. (default: 256kB)")]
|
||||
#[display(fmt = "Urlencoded payload size is bigger than allowed (default: 256kB)")]
|
||||
Overflow,
|
||||
/// Payload size is now known
|
||||
#[display(fmt = "Payload size is now known")]
|
||||
@@ -66,7 +66,7 @@ impl ResponseError for UrlencodedError {
|
||||
#[derive(Debug, Display, From)]
|
||||
pub enum JsonPayloadError {
|
||||
/// Payload size is bigger than allowed. (default: 32kB)
|
||||
#[display(fmt = "Json payload size is bigger than allowed.")]
|
||||
#[display(fmt = "Json payload size is bigger than allowed")]
|
||||
Overflow,
|
||||
/// Content type error
|
||||
#[display(fmt = "Content type error")]
|
||||
|
@@ -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;
|
||||
|
@@ -242,13 +242,13 @@ where
|
||||
}
|
||||
|
||||
/// 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 +258,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 +279,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 +313,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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -67,7 +67,6 @@
|
||||
//! ## Package feature
|
||||
//!
|
||||
//! * `client` - enables http client
|
||||
//! * `tls` - enables ssl support via `native-tls` crate
|
||||
//! * `ssl` - enables ssl support via `openssl` crate, supports `http/2`
|
||||
//! * `rust-tls` - enables ssl support via `rustls` crate, supports `http/2`
|
||||
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
|
||||
@@ -134,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};
|
||||
@@ -144,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 {
|
||||
@@ -445,7 +444,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 +500,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 +516,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())
|
||||
|
@@ -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;
|
||||
@@ -241,8 +241,8 @@ impl<B> Drop for StreamLog<B> {
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for StreamLog<B> {
|
||||
fn length(&self) -> BodySize {
|
||||
self.body.length()
|
||||
fn size(&self) -> BodySize {
|
||||
self.body.size()
|
||||
}
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, 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")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user