1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-29 16:19:19 +02:00

Compare commits

...

42 Commits

Author SHA1 Message Date
Nikolay Kim
442f5057dd alpha.3 release 2019-04-02 21:49:31 -07:00
Nikolay Kim
19eef36f8f Merge branch 'tarpaulin' 2019-04-02 21:11:03 -07:00
Nikolay Kim
51d5006ccf Detect socket disconnection during protocol selection 2019-04-02 20:50:25 -07:00
Nikolay Kim
3aebe09e5c travis 2019-04-02 19:21:22 -07:00
Nikolay Kim
4227cddd30 fix dev dependencies 2019-04-02 15:00:10 -07:00
Nikolay Kim
db1f7651a3 more patch cratesio 2019-04-02 14:47:59 -07:00
Nikolay Kim
00000fb316 mut obj 2019-04-02 14:27:54 -07:00
Nikolay Kim
f100976ef0 rename close_connection to force_close 2019-04-02 14:08:30 -07:00
Nikolay Kim
deac983bc7 fix test-server workspace setup 2019-04-02 14:04:28 -07:00
Nikolay Kim
bca31eb7ad remove Deref 2019-04-02 13:35:01 -07:00
Nikolay Kim
e282ef7925 return back consuming builder 2019-04-02 12:51:16 -07:00
Nikolay Kim
49a499ce74 properly allocate read buffer 2019-04-02 11:11:32 -07:00
Nikolay Kim
d067b1d5f1 do not use static 2019-04-02 10:53:44 -07:00
Nikolay Kim
c27fbdc35f Preallocate read buffer for h1 codec, #749 2019-04-02 10:19:56 -07:00
Nikolay Kim
1bd0995d7a remove unneded & 2019-04-01 18:00:38 -07:00
Nikolay Kim
2d43489278 ClientRequest::json() accepts reference instead of object 2019-04-01 17:53:30 -07:00
Nikolay Kim
89a0a50e14 Merge branch 'master' of github.com:actix/actix-web 2019-04-01 15:20:04 -07:00
Nikolay Kim
38afc93304 Use non-consuming builder pattern for ClientRequest 2019-04-01 15:19:34 -07:00
Darin
03c84be1f2 Merge pull request #750 from Dowwie/master
added docs for wrap and wrap_fn
2019-04-01 17:37:04 -04:00
dowwie
6d169f4c9c Merge branch 'master' of https://github.com/Dowwie/actix-web 2019-04-01 15:10:49 -04:00
dowwie
3dd3f7bc92 updated scope wrap doc 2019-04-01 15:10:28 -04:00
Darin
e6936d9f73 Merge branch 'master' into master 2019-04-01 14:53:23 -04:00
dowwie
03dfbdfcdd updated wrap and wrap fn descriptions, still requiring viable examples 2019-04-01 14:52:05 -04:00
Nikolay Kim
5c4e4edda4 add ClientResponse::json() 2019-04-01 11:51:18 -07:00
Nikolay Kim
c5fa6c1abe do not consume response 2019-04-01 11:29:50 -07:00
Nikolay Kim
6c195d8521 add Derev<Target=RequestHead> for ClientRequest 2019-04-01 10:26:25 -07:00
Nikolay Kim
96fd61f3d5 rust 1.31.0 compatibility 2019-04-01 10:26:09 -07:00
dowwie
8800b8ef13 mentioned re-use in wrap doc 2019-04-01 09:59:21 -04:00
dowwie
220c04b7b3 added docs for wrap and wrap_fn 2019-04-01 09:30:11 -04:00
Nikolay Kim
34695f4bce rename test methods; update tests 2019-03-31 20:43:00 -07:00
Nikolay Kim
15c5a3bcfb fix test 2019-03-31 18:57:54 -07:00
Nikolay Kim
ab45974e35 add default handler 2019-03-31 18:19:18 -07:00
Nikolay Kim
e4b3f79458 allocate enough space 2019-03-31 17:05:02 -07:00
Nikolay Kim
ce8294740e fix tests with disabled features 2019-03-31 17:04:34 -07:00
Llaurence
ddf5089bff Warn when an unsealed private cookie isn't valid UTF-8 (#746) 2019-03-31 16:26:56 +03:00
Nikolay Kim
7596d0b7cb fix fn_guard doc string 2019-03-30 20:48:00 -07:00
Nikolay Kim
1a871d708e update guard doc test 2019-03-30 12:13:21 -07:00
Nikolay Kim
351df84cca update stable release api doc link 2019-03-30 11:37:56 -07:00
Nikolay Kim
6fcbe4bcda add fn_guard 2019-03-30 11:33:31 -07:00
Nikolay Kim
457b75c995 update api docs; move web to submodule 2019-03-30 10:04:38 -07:00
Nikolay Kim
724e9c2efb replace deprecated fn 2019-03-30 07:56:09 -07:00
Douman
2e159d1eb9 test-server: Request functions should accept path (#743) 2019-03-30 07:53:45 -07:00
64 changed files with 1490 additions and 740 deletions

View File

@@ -8,11 +8,12 @@ cache:
matrix:
include:
- rust: 1.31.0
- rust: stable
- rust: beta
- rust: nightly-2019-03-02
- rust: nightly-2019-04-02
allow_failures:
- rust: nightly-2019-03-02
- rust: nightly-2019-04-02
env:
global:
@@ -25,7 +26,7 @@ before_install:
- sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
before_cache: |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin
fi
@@ -34,6 +35,7 @@ before_script:
- export PATH=$PATH:~/.cargo/bin
script:
- cargo clean
- cargo update
- cargo check --all --no-default-features
- cargo test --all-features --all -- --nocapture
@@ -42,14 +44,14 @@ script:
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
cargo doc --all-features --no-deps &&
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 &&
echo "Uploaded documentation"
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-03-02" ]]; then
if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then
taskset -c 0 cargo tarpaulin --out Xml --all --all-features
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"

View File

@@ -1,5 +1,20 @@
# Changes
## [1.0.0-alpha.3] - 2019-04-02
### Changed
* Renamed `TestRequest::to_service()` to `TestRequest::to_srv_request()`
* Renamed `TestRequest::to_response()` to `TestRequest::to_srv_response()`
* Removed `Deref` impls
### Removed
* Removed unused `actix_web::web::md()`
## [1.0.0-alpha.2] - 2019-03-29
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "1.0.0-alpha.2"
version = "1.0.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -32,6 +32,7 @@ members = [
"actix-session",
"actix-web-actors",
"actix-web-codegen",
"test-server",
]
[package.metadata.docs.rs]
@@ -71,11 +72,11 @@ actix-utils = "0.3.4"
actix-router = "0.1.0"
actix-rt = "0.2.2"
actix-web-codegen = "0.1.0-alpha.1"
actix-http = { version = "0.1.0-alpha.2", features=["fail"] }
actix-server = "0.4.1"
actix-http = { version = "0.1.0-alpha.3", features=["fail"] }
actix-server = "0.4.2"
actix-server-config = "0.1.0"
actix-threadpool = "0.1.0"
awc = { version = "0.1.0-alpha.2", optional = true }
awc = { version = "0.1.0-alpha.3", optional = true }
bytes = "0.4"
derive_more = "0.14"
@@ -100,8 +101,8 @@ openssl = { version="0.10", optional = true }
rustls = { version = "^0.15", optional = true }
[dev-dependencies]
actix-http = { version = "0.1.0-alpha.2", features=["ssl", "brotli", "flate2-zlib"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-http = { version = "0.1.0-alpha.3", features=["ssl", "brotli", "flate2-zlib"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
rand = "0.6"
env_logger = "0.6"
serde_derive = "1.0"
@@ -113,3 +114,14 @@ flate2 = "1.0.2"
lto = true
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" }
actix-web-codegen = { path = "actix-web-codegen" }
actix-web-actors = { path = "actix-web-actors" }
actix-session = { path = "actix-session" }
actix-files = { path = "actix-files" }
awc = { path = "awc" }

View File

@@ -19,7 +19,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* [User Guide](https://actix.rs/docs/)
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
* [API Documentation (Releases)](https://docs.rs/actix-web/0.7.18/actix_web/)
* [API Documentation (0.7 Release)](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

View File

@@ -1,5 +1,10 @@
# Changes
## [0.1.0-alpha.2] - 2019-04-02
* Add default handler support
## [0.1.0-alpha.1] - 2019-03-28
* Initial impl

View File

@@ -18,7 +18,7 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = "1.0.0-alpha.2"
actix-web = "1.0.0-alpha.3"
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.2", features=["ssl"] }
actix-web = { version = "1.0.0-alpha.3", features=["ssl"] }

View File

@@ -7,23 +7,23 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
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, ServiceFromRequest,
ServiceRequest, ServiceResponse,
};
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
use actix_web::http::header::DispositionType;
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
use bytes::Bytes;
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, Poll, Stream};
use mime;
use mime_guess::get_mime_type;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use v_htmlescape::escape as escape_html_entity;
use actix_service::{boxed::BoxedNewService, NewService, Service};
use actix_web::dev::{
HttpServiceFactory, ResourceDef, ServiceConfig, ServiceFromRequest, ServiceRequest,
ServiceResponse,
};
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
use actix_web::http::header::DispositionType;
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
use futures::future::{ok, FutureResult};
mod error;
mod named;
mod range;
@@ -32,6 +32,7 @@ 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, ()>;
@@ -232,8 +233,6 @@ pub struct Files<S> {
default: Rc<RefCell<Option<Rc<HttpNewService<S>>>>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
_chunk_size: usize,
_follow_symlinks: bool,
file_flags: named::Flags,
}
@@ -245,8 +244,6 @@ impl<S> Clone for Files<S> {
show_index: self.show_index,
default: self.default.clone(),
renderer: self.renderer.clone(),
_chunk_size: self._chunk_size,
_follow_symlinks: self._follow_symlinks,
file_flags: self.file_flags,
path: self.path.clone(),
mime_override: self.mime_override.clone(),
@@ -274,8 +271,6 @@ impl<S: 'static> Files<S> {
default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing),
mime_override: None,
_chunk_size: 0,
_follow_symlinks: false,
file_flags: named::Flags::default(),
}
}
@@ -334,6 +329,24 @@ impl<S: 'static> Files<S> {
self.file_flags.set(named::Flags::LAST_MD, value);
self
}
/// Sets default handler which is used when no matched file could be found.
pub fn default_handler<F, U>(mut self, f: F) -> Self
where
F: IntoNewService<U>,
U: NewService<
Request = ServiceRequest<S>,
Response = ServiceResponse,
Error = Error,
> + 'static,
{
// create and configure default resource
self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::new_service(
f.into_new_service().map_init_err(|_| ()),
)))));
self
}
}
impl<P> HttpServiceFactory<P> for Files<P>
@@ -353,41 +366,95 @@ where
}
}
impl<P> NewService for Files<P> {
impl<P: 'static> NewService for Files<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type Service = Self;
type Service = FilesService<P>;
type InitError = ();
type Future = FutureResult<Self::Service, Self::InitError>;
type Future = Box<Future<Item = Self::Service, Error = Self::InitError>>;
fn new_service(&self, _: &()) -> Self::Future {
ok(self.clone())
let mut srv = FilesService {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
default: None,
renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(),
file_flags: self.file_flags,
};
if let Some(ref default) = *self.default.borrow() {
Box::new(
default
.new_service(&())
.map(move |default| {
srv.default = Some(default);
srv
})
.map_err(|_| ()),
)
} else {
Box::new(ok(srv))
}
}
}
impl<P> Service for Files<P> {
pub struct FilesService<P> {
directory: PathBuf,
index: Option<String>,
show_index: bool,
default: Option<HttpService<P>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags,
}
impl<P> FilesService<P> {
fn handle_err(
&mut self,
e: io::Error,
req: HttpRequest,
payload: Payload<P>,
) -> Either<
FutureResult<ServiceResponse, Error>,
Box<Future<Item = ServiceResponse, Error = Error>>,
> {
log::debug!("Files: Failed to handle {}: {}", req.path(), e);
if let Some(ref mut default) = self.default {
Either::B(default.call(ServiceRequest::from_parts(req, payload)))
} else {
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
}
}
}
impl<P> Service for FilesService<P> {
type Request = ServiceRequest<P>;
type Response = ServiceResponse;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
type Future = Either<
FutureResult<Self::Response, Self::Error>,
Box<Future<Item = Self::Response, Error = Self::Error>>,
>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(Async::Ready(()))
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
let (req, _) = req.into_parts();
let (req, pl) = req.into_parts();
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item,
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))),
};
// full filepath
let path = match self.directory.join(&real_path.0).canonicalize() {
Ok(path) => path,
Err(e) => return ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => return self.handle_err(e, req, pl),
};
if path.is_dir() {
@@ -403,25 +470,25 @@ impl<P> Service for Files<P> {
}
named_file.flags = self.file_flags;
match named_file.respond_to(&req) {
Ok(item) => ok(ServiceResponse::new(req.clone(), item)),
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
}
Either::A(ok(match named_file.respond_to(&req) {
Ok(item) => ServiceResponse::new(req.clone(), item),
Err(e) => ServiceResponse::from_err(e, req.clone()),
}))
}
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => return self.handle_err(e, req, pl),
}
} else if self.show_index {
let dir = Directory::new(self.directory.clone(), path);
let x = (self.renderer)(&dir, &req);
match x {
Ok(resp) => ok(resp),
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Ok(resp) => Either::A(ok(resp)),
Err(e) => return self.handle_err(e, req, pl),
}
} else {
ok(ServiceResponse::from_err(
Either::A(ok(ServiceResponse::from_err(
FilesError::IsDirectory,
req.clone(),
))
)))
}
} else {
match NamedFile::open(path) {
@@ -434,11 +501,15 @@ impl<P> Service for Files<P> {
named_file.flags = self.file_flags;
match named_file.respond_to(&req) {
Ok(item) => ok(ServiceResponse::new(req.clone(), item)),
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Ok(item) => {
Either::A(ok(ServiceResponse::new(req.clone(), item)))
}
Err(e) => {
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
}
}
}
Err(e) => ok(ServiceResponse::from_err(e, req.clone())),
Err(e) => self.handle_err(e, req, pl),
}
}
}
@@ -481,7 +552,7 @@ impl<P> FromRequest<P> for PathBufWrp {
type Future = Result<Self, Self::Error>;
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
PathBufWrp::get_pathbuf(req.match_info().path())
PathBufWrp::get_pathbuf(req.request().match_info().path())
}
}
@@ -495,7 +566,10 @@ mod tests {
use bytes::BytesMut;
use super::*;
use actix_web::http::{header, header::DispositionType, Method, StatusCode};
use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType,
};
use actix_web::http::{Method, StatusCode};
use actix_web::test::{self, TestRequest};
use actix_web::App;
@@ -612,7 +686,6 @@ mod tests {
#[test]
fn test_named_file_image_attachment() {
use header::{ContentDisposition, DispositionParam, DispositionType};
let cd = ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename(String::from("test.png"))],
@@ -890,20 +963,32 @@ mod tests {
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
}
// #[test]
// fn test_named_file_content_encoding() {
// let req = TestRequest::default().method(Method::GET).finish();
// let file = NamedFile::open("Cargo.toml").unwrap();
#[test]
fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().enable_encoding().service(
web::resource("/").to(|| {
NamedFile::open("Cargo.toml")
.unwrap()
.set_content_encoding(header::ContentEncoding::Identity)
}),
));
// assert!(file.encoding.is_none());
// let resp = file
// .set_content_encoding(ContentEncoding::Identity)
// .respond_to(&req)
// .unwrap();
let request = TestRequest::get()
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.to_request();
let res = test::call_success(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK);
// assert!(resp.content_encoding().is_some());
// assert_eq!(resp.content_encoding().unwrap().as_str(), "identity");
// }
assert_eq!(
res.headers()
.get(header::CONTENT_ENCODING)
.unwrap()
.to_str()
.unwrap(),
"identity"
);
}
#[test]
fn test_named_file_allowed_method() {
@@ -954,22 +1039,29 @@ mod tests {
let _st: Files<()> = Files::new("/", "Cargo.toml");
}
// #[test]
// fn test_default_handler_file_missing() {
// let st = Files::new(".")
// .default_handler(|_: &_| "default content");
// let req = TestRequest::with_uri("/missing")
// .param("tail", "missing")
// .finish();
#[test]
fn test_default_handler_file_missing() {
let mut st = test::block_on(
Files::new("/", ".")
.default_handler(|req: ServiceRequest<_>| {
Ok(req.into_response(HttpResponse::Ok().body("default content")))
})
.new_service(&()),
)
.unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request();
// let resp = st.handle(&req).respond_to(&req).unwrap();
// let resp = resp.as_msg();
// assert_eq!(resp.status(), StatusCode::OK);
// assert_eq!(
// resp.body(),
// &Body::Binary(Binary::Slice(b"default content"))
// );
// }
let mut resp = test::call_success(&mut st, req);
assert_eq!(resp.status(), StatusCode::OK);
let bytes =
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {
b.extend(c);
Ok::<_, Error>(b)
}))
.unwrap();
assert_eq!(bytes.freeze(), Bytes::from_static(b"default content"));
}
// #[test]
// fn test_serve_index() {

View File

@@ -43,7 +43,7 @@ pub struct NamedFile {
pub(crate) content_disposition: header::ContentDisposition,
pub(crate) md: Metadata,
modified: Option<SystemTime>,
encoding: Option<ContentEncoding>,
pub(crate) encoding: Option<ContentEncoding>,
pub(crate) status_code: StatusCode,
pub(crate) flags: Flags,
}

View File

@@ -1,5 +1,20 @@
# Changes
## [0.1.0-alpha.3] - 2019-04-02
### Added
* Warn when an unsealed private cookie isn't valid UTF-8
### Fixed
* Rust 1.31.0 compatibility
* Preallocate read buffer for h1 codec
* Detect socket disconnection during protocol selection
## [0.1.0-alpha.2] - 2019-03-29
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives"
readme = "README.md"
@@ -100,7 +100,7 @@ openssl = { version="0.10", optional = true }
actix-rt = "0.2.2"
actix-server = { version = "0.4.1", features=["ssl"] }
actix-connect = { version = "0.1.0", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
env_logger = "0.6"
serde_derive = "1.0"
openssl = { version="0.10" }

View File

@@ -470,7 +470,9 @@ impl<'a> Iterator for Iter<'a> {
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
#[cfg(feature = "secure-cookies")]
use super::Key;
use super::{Cookie, CookieJar};
#[test]
#[allow(deprecated)]

View File

@@ -59,7 +59,7 @@ mod parse;
#[macro_use]
mod secure;
#[cfg(feature = "secure-cookies")]
pub use secure::*;
pub use self::secure::*;
use std::borrow::Cow;
use std::fmt;
@@ -68,11 +68,11 @@ use std::str::FromStr;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use time::{Duration, Tm};
pub use builder::CookieBuilder;
pub use draft::*;
pub use jar::{CookieJar, Delta, Iter};
use parse::parse_cookie;
pub use parse::ParseError;
pub use self::builder::CookieBuilder;
pub use self::draft::*;
pub use self::jar::{CookieJar, Delta, Iter};
use self::parse::parse_cookie;
pub use self::parse::ParseError;
#[derive(Debug, Clone)]
enum CookieStr {

View File

@@ -1,3 +1,6 @@
use std::str;
use log::warn;
use ring::aead::{open_in_place, seal_in_place, Aad, Algorithm, Nonce, AES_256_GCM};
use ring::aead::{OpeningKey, SealingKey};
use ring::rand::{SecureRandom, SystemRandom};
@@ -57,9 +60,16 @@ impl<'a> PrivateJar<'a> {
let unsealed = open_in_place(&key, nonce, ad, 0, sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?;
::std::str::from_utf8(unsealed)
.map(|s| s.to_string())
.map_err(|_| "bad unsealed utf8")
if let Ok(unsealed_utf8) = str::from_utf8(unsealed) {
Ok(unsealed_utf8.to_string())
} else {
warn!(
"Private cookie does not have utf8 content!
It is likely the secret key used to encrypt them has been leaked.
Please change it as soon as possible."
);
Err("bad unsealed utf8")
}
}
/// Returns a reference to the `Cookie` inside this jar with the name `name`
@@ -147,34 +157,12 @@ impl<'a> PrivateJar<'a> {
/// Encrypts the cookie's value with
/// authenticated encryption assuring confidentiality, integrity, and authenticity.
fn encrypt_cookie(&self, cookie: &mut Cookie) {
let mut data;
let output_len = {
// Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, &self.key).expect("sealing key creation");
// Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len();
let cookie_val = cookie.value().as_bytes();
data = vec![0; NONCE_LEN + cookie_val.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new()
.fill(nonce)
.expect("couldn't random fill nonce");
in_out[..cookie_val.len()].copy_from_slice(cookie_val);
let nonce = Nonce::try_assume_unique_for_key(nonce)
.expect("invalid length of `nonce`");
// Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(cookie.name().as_bytes());
// Perform the actual sealing operation and get the output length.
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal")
};
let name = cookie.name().as_bytes();
let value = cookie.value().as_bytes();
let data = encrypt_name_value(name, value, &self.key);
// Base64 encode the nonce and encrypted value.
let sealed_value = base64::encode(&data[..(NONCE_LEN + output_len)]);
let sealed_value = base64::encode(&data);
cookie.set_value(sealed_value);
}
@@ -206,9 +194,38 @@ impl<'a> PrivateJar<'a> {
}
}
fn encrypt_name_value(name: &[u8], value: &[u8], key: &[u8]) -> Vec<u8> {
// Create the `SealingKey` structure.
let key = SealingKey::new(ALGO, key).expect("sealing key creation");
// Create a vec to hold the [nonce | cookie value | overhead].
let overhead = ALGO.tag_len();
let mut data = vec![0; NONCE_LEN + value.len() + overhead];
// Randomly generate the nonce, then copy the cookie value as input.
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new()
.fill(nonce)
.expect("couldn't random fill nonce");
in_out[..value.len()].copy_from_slice(value);
let nonce =
Nonce::try_assume_unique_for_key(nonce).expect("invalid length of `nonce`");
// Use cookie's name as associated data to prevent value swapping.
let ad = Aad::from(name);
// Perform the actual sealing operation and get the output length.
let output_len =
seal_in_place(&key, nonce, ad, in_out, overhead).expect("in-place seal");
// Remove the overhead and return the sealed content.
data.truncate(NONCE_LEN + output_len);
data
}
#[cfg(test)]
mod test {
use super::{Cookie, CookieJar, Key};
use super::{encrypt_name_value, Cookie, CookieJar, Key};
#[test]
fn simple() {
@@ -223,4 +240,30 @@ mod test {
let mut jar = CookieJar::new();
assert_secure_behaviour!(jar, jar.private(&key));
}
#[test]
fn non_utf8() {
let key = Key::generate();
let mut jar = CookieJar::new();
let name = "malicious";
let mut assert_non_utf8 = |value: &[u8]| {
let sealed = encrypt_name_value(name.as_bytes(), value, &key.encryption());
let encoded = base64::encode(&sealed);
assert_eq!(
jar.private(&key).unseal(name, &encoded),
Err("bad unsealed utf8")
);
jar.add(Cookie::new(name, encoded));
assert_eq!(jar.private(&key).get(name), None);
};
assert_non_utf8(&[0x72, 0xfb, 0xdf, 0x74]); // rûst in ISO/IEC 8859-1
let mut malicious =
String::from(r#"{"id":"abc123??%X","admin":true}"#).into_bytes();
malicious[8] |= 0b1100_0000;
malicious[9] |= 0b1100_0000;
assert_non_utf8(&malicious);
}
}

View File

@@ -10,7 +10,7 @@ use http::header::{
use http::{Method, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder};
use super::{decoder, encoder, reserve_readbuf};
use super::{Message, MessageType};
use crate::body::BodySize;
use crate::config::ServiceConfig;
@@ -150,6 +150,7 @@ impl Decoder for ClientCodec {
} else {
self.inner.payload = None;
}
reserve_readbuf(src);
Ok(Some(req))
} else {
Ok(None)
@@ -168,7 +169,10 @@ impl Decoder for ClientPayloadCodec {
);
Ok(match self.inner.payload.as_mut().unwrap().decode(src)? {
Some(PayloadItem::Chunk(chunk)) => Some(Some(chunk)),
Some(PayloadItem::Chunk(chunk)) => {
reserve_readbuf(src);
Some(Some(chunk))
}
Some(PayloadItem::Eof) => {
self.inner.payload.take();
Some(None)

View File

@@ -9,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD
use http::{Method, StatusCode, Version};
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
use super::{decoder, encoder};
use super::{decoder, encoder, reserve_readbuf};
use super::{Message, MessageType};
use crate::body::BodySize;
use crate::config::ServiceConfig;
@@ -107,7 +107,10 @@ impl Decoder for Codec {
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if self.payload.is_some() {
Ok(match self.payload.as_mut().unwrap().decode(src)? {
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
Some(PayloadItem::Chunk(chunk)) => {
reserve_readbuf(src);
Some(Message::Chunk(Some(chunk)))
}
Some(PayloadItem::Eof) => {
self.payload.take();
Some(Message::Chunk(None))
@@ -132,6 +135,7 @@ impl Decoder for Codec {
self.flags.insert(Flags::STREAM);
}
}
reserve_readbuf(src);
Ok(Some(Message::Item(req)))
} else {
Ok(None)

View File

@@ -1,5 +1,5 @@
//! HTTP/1 implementation
use bytes::Bytes;
use bytes::{Bytes, BytesMut};
mod client;
mod codec;
@@ -38,6 +38,16 @@ pub enum MessageType {
Stream,
}
const LW: usize = 2 * 1024;
const HW: usize = 32 * 1024;
pub(crate) fn reserve_readbuf(src: &mut BytesMut) {
let cap = src.capacity();
if cap < LW {
src.reserve(HW - cap);
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -8,6 +8,7 @@ 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;

View File

@@ -247,7 +247,10 @@ where
loop {
unsafe {
let b = item.1.bytes_mut();
let n = { try_ready!(item.0.poll_read(b)) };
let n = try_ready!(item.0.poll_read(b));
if n == 0 {
return Ok(Async::Ready(()));
}
item.1.advance_mut(n);
if item.1.len() >= HTTP2_PREFACE.len() {
break;

View File

@@ -172,13 +172,14 @@ impl Parser {
};
if payload_len < 126 {
dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | payload_len as u8]);
} else if payload_len <= 65_535 {
dst.reserve(p_len + 4);
dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 126]);
dst.put_u16_be(payload_len as u16);
} else {
dst.reserve(p_len + 10);
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
dst.put_slice(&[one, two | 127]);
dst.put_u64_be(payload_len as u64);
};
@@ -186,7 +187,7 @@ impl Parser {
if mask {
let mask = rand::random::<u32>();
dst.put_u32_le(mask);
dst.extend_from_slice(payload.as_ref());
dst.put_slice(payload.as_ref());
let pos = dst.len() - payload_len;
apply_mask(&mut dst[pos..], mask);
} else {

View File

@@ -35,10 +35,10 @@ fn test_h1_v2() {
.finish(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
.map(|_| ())
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
let request = srv.get().header("x-test", "111").send();
let request = srv.get("/").header("x-test", "111").send();
let response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
@@ -46,7 +46,7 @@ fn test_h1_v2() {
let bytes = srv.load_body(response).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let response = srv.block_on(srv.post().send()).unwrap();
let response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -61,7 +61,7 @@ fn test_connection_close() {
.finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
.map(|_| ())
});
let response = srv.block_on(srv.get().close_connection().send()).unwrap();
let response = srv.block_on(srv.get("/").force_close().send()).unwrap();
assert!(response.status().is_success());
}

View File

@@ -16,6 +16,7 @@ use actix_http::{
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
};
#[cfg(feature = "ssl")]
fn load_body<S>(stream: S) -> impl Future<Item = BytesMut, Error = PayloadError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@@ -36,7 +37,7 @@ fn test_h1() {
.h1(|_| future::ok::<_, ()>(Response::Ok().finish()))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
}
@@ -54,7 +55,7 @@ fn test_h1_2() {
.map(|_| ())
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
}
@@ -97,7 +98,7 @@ fn test_h2() -> std::io::Result<()> {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
Ok(())
}
@@ -120,7 +121,7 @@ fn test_h2_1() -> std::io::Result<()> {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
Ok(())
}
@@ -144,7 +145,7 @@ fn test_h2_body() -> std::io::Result<()> {
)
});
let response = srv.block_on(srv.sget().send_body(data.clone())).unwrap();
let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap();
assert!(response.status().is_success());
let body = srv.load_body(response).unwrap();
@@ -346,6 +347,7 @@ fn test_content_length() {
}
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_content_length() {
use actix_http::http::{
@@ -435,7 +437,7 @@ fn test_h1_headers() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -443,6 +445,7 @@ fn test_h1_headers() {
assert_eq!(bytes, Bytes::from(data2));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_headers() {
let data = STR.repeat(10);
@@ -479,7 +482,7 @@ fn test_h2_headers() {
}).map_err(|_| ()))
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -515,7 +518,7 @@ fn test_h1_body() {
HttpService::build().h1(|_| future::ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -523,6 +526,7 @@ fn test_h1_body() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_body2() {
let openssl = ssl_acceptor().unwrap();
@@ -537,7 +541,7 @@ fn test_h2_body2() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -551,7 +555,7 @@ fn test_h1_head_empty() {
HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.head().send()).unwrap();
let response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -567,6 +571,7 @@ fn test_h1_head_empty() {
assert!(bytes.is_empty());
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_head_empty() {
let openssl = ssl_acceptor().unwrap();
@@ -581,7 +586,7 @@ fn test_h2_head_empty() {
)
});
let response = srv.block_on(srv.shead().send()).unwrap();
let response = srv.block_on(srv.shead("/").send()).unwrap();
assert!(response.status().is_success());
assert_eq!(response.version(), http::Version::HTTP_2);
@@ -606,7 +611,7 @@ fn test_h1_head_binary() {
})
});
let response = srv.block_on(srv.head().send()).unwrap();
let response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -622,6 +627,7 @@ fn test_h1_head_binary() {
assert!(bytes.is_empty());
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_head_binary() {
let openssl = ssl_acceptor().unwrap();
@@ -640,7 +646,7 @@ fn test_h2_head_binary() {
)
});
let response = srv.block_on(srv.shead().send()).unwrap();
let response = srv.block_on(srv.shead("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -662,7 +668,7 @@ fn test_h1_head_binary2() {
HttpService::build().h1(|_| ok::<_, ()>(Response::Ok().body(STR)))
});
let response = srv.block_on(srv.head().send()).unwrap();
let response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -674,6 +680,7 @@ fn test_h1_head_binary2() {
}
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_head_binary2() {
let openssl = ssl_acceptor().unwrap();
@@ -688,7 +695,7 @@ fn test_h2_head_binary2() {
)
});
let response = srv.block_on(srv.shead().send()).unwrap();
let response = srv.block_on(srv.shead("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -712,7 +719,7 @@ fn test_h1_body_length() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -720,6 +727,7 @@ fn test_h1_body_length() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_body_length() {
let openssl = ssl_acceptor().unwrap();
@@ -739,7 +747,7 @@ fn test_h2_body_length() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -760,7 +768,7 @@ fn test_h1_body_chunked_explicit() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
assert_eq!(
response
@@ -779,6 +787,7 @@ fn test_h1_body_chunked_explicit() {
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_body_chunked_explicit() {
let openssl = ssl_acceptor().unwrap();
@@ -801,7 +810,7 @@ fn test_h2_body_chunked_explicit() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
assert!(!response.headers().contains_key(header::TRANSFER_ENCODING));
@@ -821,7 +830,7 @@ fn test_h1_body_chunked_implicit() {
})
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
assert_eq!(
response
@@ -853,7 +862,7 @@ fn test_h1_response_http_error_handling() {
}))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response
@@ -861,6 +870,7 @@ fn test_h1_response_http_error_handling() {
assert!(bytes.is_empty());
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_response_http_error_handling() {
let openssl = ssl_acceptor().unwrap();
@@ -885,7 +895,7 @@ fn test_h2_response_http_error_handling() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response
@@ -900,7 +910,7 @@ fn test_h1_service_error() {
.h1(|_| Err::<Response, Error>(error::ErrorBadRequest("error")))
});
let response = srv.block_on(srv.get().send()).unwrap();
let response = srv.block_on(srv.get("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response
@@ -908,6 +918,7 @@ fn test_h1_service_error() {
assert!(bytes.is_empty());
}
#[cfg(feature = "ssl")]
#[test]
fn test_h2_service_error() {
let openssl = ssl_acceptor().unwrap();
@@ -923,7 +934,7 @@ fn test_h2_service_error() {
)
});
let response = srv.block_on(srv.sget().send()).unwrap();
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
// read response

View File

@@ -1,5 +1,9 @@
# Changes
## [0.1.0-alpha.3] - 2019-04-02
* Update actix-web
## [0.1.0-alpha.2] - 2019-03-29
* Update actix-web

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-session"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Session for actix web framework."
readme = "README.md"
@@ -24,7 +24,7 @@ default = ["cookie-session"]
cookie-session = ["actix-web/secure-cookies"]
[dependencies]
actix-web = "1.0.0-alpha.2"
actix-web = "1.0.0-alpha.3"
actix-service = "0.3.4"
bytes = "0.4"
derive_more = "0.14"

View File

@@ -333,6 +333,7 @@ mod tests {
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.response()
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());
@@ -352,6 +353,7 @@ mod tests {
let request = test::TestRequest::get().to_request();
let response = test::block_on(app.call(request)).unwrap();
assert!(response
.response()
.cookies()
.find(|c| c.name() == "actix-session")
.is_some());

View File

@@ -190,7 +190,7 @@ mod tests {
#[test]
fn session() {
let mut req = test::TestRequest::default().to_service();
let mut req = test::TestRequest::default().to_srv_request();
Session::set_session(
vec![("key".to_string(), "\"value\"".to_string())].into_iter(),

View File

@@ -1,5 +1,9 @@
# Changes
## [0.1.0-alpha.3] - 2019-04-02
* Update actix-http and actix-web
## [0.1.0-alpha.2] - 2019-03-29
* Update actix-http and actix-web

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "1.0.0-alpha.2"
version = "1.0.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework."
readme = "README.md"
@@ -19,12 +19,12 @@ path = "src/lib.rs"
[dependencies]
actix = "0.8.0-alpha.2"
actix-web = "1.0.0-alpha.2"
actix-http = "0.1.0-alpha.2"
actix-web = "1.0.0-alpha.3"
actix-http = "0.1.0-alpha.3"
actix-codec = "0.1.2"
bytes = "0.4"
futures = "0.1.25"
[dev-dependencies]
env_logger = "0.6"
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }

View File

@@ -20,7 +20,7 @@ pub use actix_http::ws::{
use actix_web::dev::HttpResponseBuilder;
use actix_web::error::{Error, ErrorInternalServerError, PayloadError};
use actix_web::http::{header, Method, StatusCode};
use actix_web::{HttpMessage, HttpRequest, HttpResponse};
use actix_web::{HttpRequest, HttpResponse};
use bytes::{Bytes, BytesMut};
use futures::sync::oneshot::Sender;
use futures::{Async, Future, Poll, Stream};
@@ -64,7 +64,7 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
}
// Upgrade connection
if !req.upgrade() {
if !req.head().upgrade() {
return Err(HandshakeError::NoConnectionUpgrade);
}

View File

@@ -1,5 +1,23 @@
# Changes
## [0.1.0-alpha.3] - 2019-04-02
### Added
* Export `MessageBody` type
* `ClientResponse::json()` - Loads and parse `application/json` encoded body
### Changed
* `ClientRequest::json()` accepts reference instead of object.
* `ClientResponse::body()` does not consume response object.
* Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
## [0.1.0-alpha.2] - 2019-03-29
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "awc"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client."
readme = "README.md"
@@ -38,12 +38,13 @@ flate2-rust = ["actix-http/flate2-rust"]
[dependencies]
actix-codec = "0.1.1"
actix-service = "0.3.4"
actix-http = "0.1.0-alpa.2"
actix-http = "0.1.0-alpa.3"
base64 = "0.10.1"
bytes = "0.4"
derive_more = "0.14"
futures = "0.1.25"
log =" 0.4"
mime = "0.3"
percent-encoding = "1.0"
rand = "0.6"
serde = "1.0"
@@ -54,14 +55,13 @@ openssl = { version="0.10", optional = true }
[dev-dependencies]
actix-rt = "0.2.2"
actix-web = { version = "1.0.0-alpha.2", features=["ssl"] }
actix-http = { version = "0.1.0-alpa.2", features=["ssl"] }
actix-http-test = { version = "0.1.0-alpha.2", features=["ssl"] }
actix-web = { version = "1.0.0-alpha.3", features=["ssl"] }
actix-http = { version = "0.1.0-alpa.3", 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"] }
brotli2 = { version="0.3.2" }
flate2 = { version="1.0.2" }
env_logger = "0.6"
mime = "0.3"
rand = "0.6"
tokio-tcp = "0.1"

View File

@@ -4,6 +4,9 @@ pub use actix_http::error::PayloadError;
pub use actix_http::ws::HandshakeError as WsHandshakeError;
pub use actix_http::ws::ProtocolError as WsProtocolError;
use actix_http::{Response, ResponseError};
use serde_json::error::Error as JsonError;
use actix_http::http::{header::HeaderValue, Error as HttpError, StatusCode};
use derive_more::{Display, From};
@@ -47,3 +50,24 @@ impl From<HttpError> for WsClientError {
WsClientError::SendRequest(err.into())
}
}
/// A set of errors that can occur during parsing json payloads
#[derive(Debug, Display, From)]
pub enum JsonPayloadError {
/// Content type error
#[display(fmt = "Content type error")]
ContentType,
/// Deserialize error
#[display(fmt = "Json deserialize error: {}", _0)]
Deserialize(JsonError),
/// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)]
Payload(PayloadError),
}
/// Return `InternlaServerError` for `JsonPayloadError`
impl ResponseError for JsonPayloadError {
fn error_response(&self) -> Response {
Response::new(StatusCode::INTERNAL_SERVER_ERROR)
}
}

View File

@@ -39,7 +39,7 @@ pub mod ws;
pub use self::builder::ClientBuilder;
pub use self::request::ClientRequest;
pub use self::response::ClientResponse;
pub use self::response::{ClientResponse, JsonBody, MessageBody};
use self::connect::{Connect, ConnectorWrapper};
@@ -105,7 +105,7 @@ impl Client {
let mut req = ClientRequest::new(method, url, self.0.clone());
for (key, value) in &self.0.headers {
req.head.headers.insert(key.clone(), value.clone());
req = req.set_header_if_none(key.clone(), value.clone());
}
req
}
@@ -120,7 +120,7 @@ impl Client {
{
let mut req = self.request(head.method.clone(), url);
for (key, value) in &head.headers {
req.head.headers.insert(key.clone(), value.clone());
req = req.set_header_if_none(key.clone(), value.clone());
}
req
}

View File

@@ -17,8 +17,8 @@ use actix_http::cookie::{Cookie, CookieJar};
use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderName, HeaderValue, HttpTryFrom,
Method, Uri, Version,
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue,
HttpTryFrom, Method, Uri, Version,
};
use actix_http::{Error, Payload, RequestHead};
@@ -116,6 +116,18 @@ impl ClientRequest {
self
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head.headers
}
#[inline]
/// Returns request's mutable headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head.headers
}
/// Set a header.
///
/// ```rust
@@ -165,7 +177,7 @@ impl ClientRequest {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
self.head.headers.append(key, value);
let _ = self.head.headers.append(key, value);
}
Err(e) => self.err = Some(e.into()),
},
@@ -183,7 +195,7 @@ impl ClientRequest {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
self.head.headers.insert(key, value);
let _ = self.head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
@@ -203,7 +215,7 @@ impl ClientRequest {
if !self.head.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => {
self.head.headers.insert(key, value);
let _ = self.head.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
}
@@ -214,9 +226,10 @@ impl ClientRequest {
self
}
/// Close connection
/// Force close connection instead of returning it back to connections pool.
/// This setting affect only http/1 connections.
#[inline]
pub fn close_connection(mut self) -> Self {
pub fn force_close(mut self) -> Self {
self.head.set_connection_type(ConnectionType::Close);
self
}
@@ -380,7 +393,7 @@ impl ClientRequest {
}
// set default headers
let slf = if self.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) {
@@ -401,28 +414,44 @@ impl ClientRequest {
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("awc/", env!("CARGO_PKG_VERSION")),
)
} else {
self
};
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();
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);
}
self.head.headers.insert(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
let slf = self;
// enable br only for https
let https = slf
.head
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
#[cfg(any(
feature = "brotli",
feature = "flate2-zlib",
feature = "flate2-rust"
))]
let mut slf = {
let slf = {
let https = slf
.head
.uri
.scheme_part()
.map(|s| s == &uri::Scheme::HTTPS)
.unwrap_or(true);
if https {
slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING)
} else {
@@ -435,23 +464,8 @@ impl ClientRequest {
}
};
let mut head = slf.head;
// set cookies
if let Some(ref mut jar) = slf.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(
header::COOKIE,
HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(),
);
}
let config = slf.config;
let head = slf.head;
let config = slf.config.as_ref();
let response_decompress = slf.response_decompress;
let fut = config
@@ -485,21 +499,18 @@ impl ClientRequest {
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(
self,
value: T,
value: &T,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_json::to_string(&value) {
let body = match serde_json::to_string(value) {
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
// set content-type
let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) {
self.header(header::CONTENT_TYPE, "application/json")
} else {
self
};
let slf = self.set_header_if_none(header::CONTENT_TYPE, "application/json");
Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
}
@@ -509,21 +520,21 @@ impl ClientRequest {
/// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>(
self,
value: T,
value: &T,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_urlencoded::to_string(&value) {
let body = match serde_urlencoded::to_string(value) {
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
let slf = if !self.head.headers.contains_key(header::CONTENT_TYPE) {
self.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
} else {
self
};
// set content-type
let slf = self.set_header_if_none(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
);
Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
}
@@ -628,12 +639,11 @@ mod tests {
#[test]
fn client_basic_auth() {
test::run_on(|| {
let client = Client::new()
let req = Client::new()
.get("/")
.basic_auth("username", Some("password"));
assert_eq!(
client
.head
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
@@ -642,10 +652,9 @@ mod tests {
"Basic dXNlcm5hbWU6cGFzc3dvcmQ="
);
let client = Client::new().get("/").basic_auth("username", None);
let req = Client::new().get("/").basic_auth("username", None);
assert_eq!(
client
.head
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()
@@ -659,10 +668,9 @@ mod tests {
#[test]
fn client_bearer_auth() {
test::run_on(|| {
let client = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
let req = Client::new().get("/").bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
client
.head
req.head
.headers
.get(header::AUTHORIZATION)
.unwrap()

View File

@@ -1,16 +1,18 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use std::marker::PhantomData;
use bytes::{Bytes, BytesMut};
use futures::{Future, Poll, Stream};
use futures::{Async, Future, Poll, Stream};
use actix_http::error::PayloadError;
use actix_http::cookie::Cookie;
use actix_http::error::{CookieParseError, PayloadError};
use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE};
use actix_http::http::{HeaderMap, StatusCode, Version};
use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead};
use serde::de::DeserializeOwned;
use actix_http::cookie::Cookie;
use actix_http::error::CookieParseError;
use crate::error::JsonPayloadError;
/// Client Response
pub struct ClientResponse<S = PayloadStream> {
@@ -81,7 +83,7 @@ impl<S> ClientResponse<S> {
}
#[inline]
/// Returns Request's headers.
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
@@ -102,12 +104,23 @@ impl<S> ClientResponse<S> {
impl<S> ClientResponse<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Load http response's body.
pub fn body(self) -> MessageBody<S> {
/// Loads http response's body.
pub fn body(&mut self) -> MessageBody<S> {
MessageBody::new(self)
}
/// Loads and parse `application/json` encoded body.
/// Return `JsonBody<T>` future. It resolves to a `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 256k
pub fn json<T: DeserializeOwned>(&mut self) -> JsonBody<S, T> {
JsonBody::new(self)
}
}
impl<S> Stream for ClientResponse<S>
@@ -135,19 +148,17 @@ impl<S> fmt::Debug for ClientResponse<S> {
/// Future that resolves to a complete http message body.
pub struct MessageBody<S> {
limit: usize,
length: Option<usize>,
stream: Option<ClientResponse<S>>,
err: Option<PayloadError>,
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
fut: Option<ReadBody<S>>,
}
impl<S> MessageBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
S: Stream<Item = Bytes, Error = PayloadError>,
{
/// Create `MessageBody` for request.
pub fn new(res: ClientResponse<S>) -> MessageBody<S> {
pub fn new(res: &mut ClientResponse<S>) -> MessageBody<S> {
let mut len = None;
if let Some(l) = res.headers().get(CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
@@ -162,24 +173,22 @@ where
}
MessageBody {
limit: 262_144,
length: len,
stream: Some(res),
fut: None,
err: None,
fut: Some(ReadBody::new(res.take_payload(), 262_144)),
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
if let Some(ref mut fut) = self.fut {
fut.limit = limit;
}
self
}
fn err(e: PayloadError) -> Self {
MessageBody {
stream: None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
@@ -189,44 +198,149 @@ where
impl<S> Future for MessageBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
if len > self.fut.as_ref().unwrap().limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
self.stream
.take()
.expect("Can not be used second time")
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
self.fut.as_mut().unwrap().poll()
}
}
/// Response's payload json parser, it resolves to a deserialized `T` value.
///
/// Returns error:
///
/// * content type is not `application/json`
/// * content length is greater than 64k
pub struct JsonBody<S, U> {
length: Option<usize>,
err: Option<JsonPayloadError>,
fut: Option<ReadBody<S>>,
_t: PhantomData<U>,
}
impl<S, U> JsonBody<S, U>
where
S: Stream<Item = Bytes, Error = PayloadError>,
U: DeserializeOwned,
{
/// Create `JsonBody` for request.
pub fn new(req: &mut ClientResponse<S>) -> Self {
// check content-type
let json = if let Ok(Some(mime)) = req.mime_type() {
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
} else {
false
};
if !json {
return JsonBody {
length: None,
fut: None,
err: Some(JsonPayloadError::ContentType),
_t: PhantomData,
};
}
let mut len = None;
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::<usize>() {
len = Some(l)
}
}
}
JsonBody {
length: len,
err: None,
fut: Some(ReadBody::new(req.take_payload(), 65536)),
_t: PhantomData,
}
}
/// Change max size of payload. By default max size is 64Kb
pub fn limit(mut self, limit: usize) -> Self {
if let Some(ref mut fut) = self.fut {
fut.limit = limit;
}
self
}
}
impl<T, U> Future for JsonBody<T, U>
where
T: Stream<Item = Bytes, Error = PayloadError>,
U: DeserializeOwned + 'static,
{
type Item = U;
type Error = JsonPayloadError;
fn poll(&mut self) -> Poll<U, JsonPayloadError> {
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.fut.as_ref().unwrap().limit {
return Err(JsonPayloadError::Payload(PayloadError::Overflow));
}
}
let body = futures::try_ready!(self.fut.as_mut().unwrap().poll());
Ok(Async::Ready(serde_json::from_slice::<U>(&body)?))
}
}
struct ReadBody<S> {
stream: Payload<S>,
buf: BytesMut,
limit: usize,
}
impl<S> ReadBody<S> {
fn new(stream: Payload<S>, limit: usize) -> Self {
Self {
stream,
buf: BytesMut::with_capacity(std::cmp::min(limit, 32768)),
limit,
}
}
}
impl<S> Future for ReadBody<S>
where
S: Stream<Item = Bytes, Error = PayloadError>,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
return match self.stream.poll()? {
Async::Ready(Some(chunk)) => {
if (self.buf.len() + chunk.len()) > self.limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
self.buf.extend_from_slice(&chunk);
continue;
}
})
.map(|body| body.freeze()),
));
self.poll()
}
Async::Ready(None) => Ok(Async::Ready(self.buf.take().freeze())),
Async::NotReady => Ok(Async::NotReady),
};
}
}
}
@@ -234,24 +348,26 @@ where
mod tests {
use super::*;
use futures::Async;
use serde::{Deserialize, Serialize};
use crate::{http::header, test::TestResponse};
use crate::{http::header, test::block_on, test::TestResponse};
#[test]
fn test_body() {
let req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish();
match req.body().poll().err().unwrap() {
PayloadError::UnknownLength => (),
_ => unreachable!("error"),
}
let req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
let mut req =
TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish();
match req.body().poll().err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
let req = TestResponse::default()
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"test"))
.finish();
match req.body().poll().ok().unwrap() {
@@ -259,7 +375,7 @@ mod tests {
_ => unreachable!("error"),
}
let req = TestResponse::default()
let mut req = TestResponse::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.finish();
match req.body().limit(5).poll().err().unwrap() {
@@ -267,4 +383,76 @@ mod tests {
_ => unreachable!("error"),
}
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
fn json_eq(err: JsonPayloadError, other: JsonPayloadError) -> bool {
match err {
JsonPayloadError::Payload(PayloadError::Overflow) => match other {
JsonPayloadError::Payload(PayloadError::Overflow) => true,
_ => false,
},
JsonPayloadError::ContentType => match other {
JsonPayloadError::ContentType => true,
_ => false,
},
_ => false,
}
}
#[test]
fn test_json_body() {
let mut req = TestResponse::default().finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/text"),
)
.finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
let mut req = TestResponse::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("10000"),
)
.finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100));
assert!(json_eq(
json.err().unwrap(),
JsonPayloadError::Payload(PayloadError::Overflow)
));
let mut req = TestResponse::default()
.header(
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
)
.header(
header::CONTENT_LENGTH,
header::HeaderValue::from_static("16"),
)
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
.finish();
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
assert_eq!(
json.ok().unwrap(),
MyObject {
name: "test".to_owned()
}
);
}
}

View File

@@ -6,6 +6,8 @@ use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue};
use actix_http::http::{HeaderName, HttpTryFrom, Version};
use actix_http::{h1, Payload, ResponseHead};
use bytes::Bytes;
#[cfg(test)]
use futures::Future;
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
use crate::ClientResponse;
@@ -18,7 +20,7 @@ thread_local! {
}
#[cfg(test)]
pub fn run_on<F, R>(f: F) -> R
pub(crate) fn run_on<F, R>(f: F) -> R
where
F: Fn() -> R,
{
@@ -29,6 +31,14 @@ where
.unwrap()
}
#[cfg(test)]
pub(crate) fn block_on<F>(f: F) -> Result<F::Item, F::Error>
where
F: Future,
{
RT.with(move |rt| rt.borrow_mut().block_on(f))
}
/// Test `ClientResponse` builder
pub struct TestResponse {
head: ResponseHead,

View File

@@ -44,15 +44,15 @@ fn test_simple() {
))
});
let request = srv.get().header("x-test", "111").send();
let response = srv.block_on(request).unwrap();
let request = srv.get("/").header("x-test", "111").send();
let mut response = srv.block_on(request).unwrap();
assert!(response.status().is_success());
// read response
let bytes = srv.block_on(response.body()).unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
let response = srv.block_on(srv.post().send()).unwrap();
let mut response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -114,7 +114,7 @@ fn test_timeout_override() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().header("Connection", "close").finish().unwrap();
// let request = srv.get("/").header("Connection", "close").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
// }
@@ -128,7 +128,7 @@ fn test_timeout_override() {
// })
// });
// let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
// let request = srv.get("/").uri(srv.url("/?qp=5").as_str()).finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -139,7 +139,7 @@ fn test_timeout_override() {
// let mut srv =
// test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().disable_decompress().finish().unwrap();
// let request = srv.get("/").disable_decompress().finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -177,7 +177,7 @@ fn test_client_gzip_encoding() {
});
// client request
let response = srv.block_on(srv.post().send()).unwrap();
let mut response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -200,7 +200,7 @@ fn test_client_gzip_encoding_large() {
});
// client request
let response = srv.block_on(srv.post().send()).unwrap();
let mut response = srv.block_on(srv.post("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -229,7 +229,7 @@ fn test_client_gzip_encoding_large_random() {
});
// client request
let response = srv.block_on(srv.post().send_body(data.clone())).unwrap();
let mut response = srv.block_on(srv.post("/").send_body(data.clone())).unwrap();
assert!(response.status().is_success());
// read response
@@ -253,7 +253,7 @@ fn test_client_brotli_encoding() {
});
// client request
let response = srv.block_on(srv.post().send_body(STR)).unwrap();
let mut response = srv.block_on(srv.post("/").send_body(STR)).unwrap();
assert!(response.status().is_success());
// read response
@@ -375,7 +375,7 @@ fn test_client_brotli_encoding() {
// let body = once(Ok(Bytes::from_static(STR.as_ref())));
// let request = srv.get().body(Body::Streaming(Box::new(body))).unwrap();
// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -395,7 +395,7 @@ fn test_client_brotli_encoding() {
// })
// });
// let request = srv.get().finish().unwrap();
// let request = srv.get("/").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());
@@ -459,7 +459,7 @@ fn test_client_cookie_handling() {
))
});
let request = srv.get().cookie(cookie1.clone()).cookie(cookie2.clone());
let request = srv.get("/").cookie(cookie1.clone()).cookie(cookie2.clone());
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
let c1 = response.cookie("cookie1").expect("Missing cookie1");
@@ -472,7 +472,7 @@ fn test_client_cookie_handling() {
// fn test_default_headers() {
// let srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok().body(STR)));
// let request = srv.get().finish().unwrap();
// let request = srv.get("/").finish().unwrap();
// let repr = format!("{:?}", request);
// assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
// assert!(repr.contains(concat!(
@@ -482,7 +482,7 @@ fn test_client_cookie_handling() {
// )));
// let request_override = srv
// .get()
// .get("/")
// .header("User-Agent", "test")
// .header("Accept-Encoding", "over_test")
// .finish()
@@ -551,7 +551,7 @@ fn client_basic_auth() {
});
// set authorization header to Basic <base64 encoded username:password>
let request = srv.get().basic_auth("username", Some("password"));
let request = srv.get("/").basic_auth("username", Some("password"));
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
}
@@ -579,7 +579,7 @@ fn client_bearer_auth() {
});
// set authorization header to Bearer <token>
let request = srv.get().bearer_auth("someS3cr3tAutht0k3n");
let request = srv.get("/").bearer_auth("someS3cr3tAutht0k3n");
let response = srv.block_on(request.send()).unwrap();
assert!(response.status().is_success());
}

View File

@@ -8,7 +8,7 @@ use actix_http::encoding::{Decoder, Encoder};
use actix_server_config::ServerConfig;
use actix_service::boxed::{self, BoxedNewService};
use actix_service::{
ApplyTransform, IntoNewService, IntoTransform, NewService, Transform,
apply_transform, IntoNewService, IntoTransform, NewService, Transform,
};
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
use bytes::Bytes;
@@ -112,7 +112,29 @@ where
self
}
/// Register a middleware.
/// 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,
@@ -138,7 +160,7 @@ where
F: IntoTransform<M, AppRouting<Out>>,
{
let fref = Rc::new(RefCell::new(None));
let endpoint = ApplyTransform::new(mw, AppEntry::new(fref.clone()));
let endpoint = apply_transform(mw, AppEntry::new(fref.clone()));
AppRouter {
endpoint,
chain: self.chain,
@@ -152,7 +174,12 @@ where
}
}
/// Register a middleware function.
/// 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;
@@ -400,7 +427,13 @@ where
self
}
/// Register a middleware.
/// 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*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
pub fn wrap<M, B1, F>(
self,
mw: F,
@@ -426,7 +459,7 @@ where
B1: MessageBody,
F: IntoTransform<M, T::Service>,
{
let endpoint = ApplyTransform::new(mw, self.endpoint);
let endpoint = apply_transform(mw, self.endpoint);
AppRouter {
endpoint,
chain: self.chain,
@@ -440,7 +473,13 @@ where
}
}
/// Register a middleware function.
/// 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*.
///
/// Use middleware when you need to read or modify *every* request or response in some way.
///
pub fn wrap_fn<B1, F, R>(
self,
mw: F,

View File

@@ -92,7 +92,7 @@ impl<T: 'static, P> FromRequest<P> for Data<T> {
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
if let Some(st) = req.config().extensions().get::<Data<T>>() {
if let Some(st) = req.request().config().extensions().get::<Data<T>>() {
Ok(st.clone())
} else {
Err(ErrorInternalServerError(

View File

@@ -79,7 +79,7 @@ pub enum JsonPayloadError {
Payload(PayloadError),
}
/// Return `BadRequest` for `UrlencodedError`
/// Return `BadRequest` for `JsonPayloadError`
impl ResponseError for JsonPayloadError {
fn error_response(&self) -> HttpResponse {
match *self {

View File

@@ -19,7 +19,7 @@
//! App::new().service(web::resource("/index.html").route(
//! web::route()
//! .guard(guard::Post())
//! .guard(|head: &dev::RequestHead| head.method == http::Method::GET)
//! .guard(guard::fn_guard(|head| head.method == http::Method::GET))
//! .to(|| HttpResponse::MethodNotAllowed()))
//! );
//! }
@@ -39,15 +39,46 @@ pub trait Guard {
fn check(&self, request: &RequestHead) -> bool;
}
#[doc(hidden)]
pub struct FnGuard<F: Fn(&RequestHead) -> bool + 'static>(F);
/// Create guard object for supplied function.
///
/// ```rust
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
/// App::new().service(web::resource("/index.html").route(
/// web::route()
/// .guard(
/// guard::fn_guard(
/// |req| req.headers()
/// .contains_key("content-type")))
/// .to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn fn_guard<F>(f: F) -> impl Guard
where
F: Fn(&RequestHead) -> bool,
{
FnGuard(f)
}
struct FnGuard<F: Fn(&RequestHead) -> bool>(F);
impl<F> Guard for FnGuard<F>
where
F: Fn(&RequestHead) -> bool,
{
fn check(&self, head: &RequestHead) -> bool {
(self.0)(head)
}
}
impl<F> Guard for F
where
F: Fn(&RequestHead) -> bool + 'static,
F: Fn(&RequestHead) -> bool,
{
fn check(&self, head: &RequestHead) -> bool {
(*self)(head)
(self)(head)
}
}
@@ -93,7 +124,6 @@ impl Guard for AnyGuard {
/// Return guard that matches if all of the supplied guards.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{guard, web, App, HttpResponse};
///
/// fn main() {
@@ -279,13 +309,13 @@ mod tests {
.to_http_request();
let pred = Header("transfer-encoding", "chunked");
assert!(pred.check(&req));
assert!(pred.check(req.head()));
let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&req));
assert!(!pred.check(req.head()));
let pred = Header("content-type", "other");
assert!(!pred.check(&req));
assert!(!pred.check(req.head()));
}
// #[test]
@@ -311,50 +341,50 @@ mod tests {
.method(Method::POST)
.to_http_request();
assert!(Get().check(&req));
assert!(!Get().check(&req2));
assert!(Post().check(&req2));
assert!(!Post().check(&req));
assert!(Get().check(req.head()));
assert!(!Get().check(req2.head()));
assert!(Post().check(req2.head()));
assert!(!Post().check(req.head()));
let r = TestRequest::default().method(Method::PUT).to_http_request();
assert!(Put().check(&r));
assert!(!Put().check(&req));
assert!(Put().check(r.head()));
assert!(!Put().check(req.head()));
let r = TestRequest::default()
.method(Method::DELETE)
.to_http_request();
assert!(Delete().check(&r));
assert!(!Delete().check(&req));
assert!(Delete().check(r.head()));
assert!(!Delete().check(req.head()));
let r = TestRequest::default()
.method(Method::HEAD)
.to_http_request();
assert!(Head().check(&r));
assert!(!Head().check(&req));
assert!(Head().check(r.head()));
assert!(!Head().check(req.head()));
let r = TestRequest::default()
.method(Method::OPTIONS)
.to_http_request();
assert!(Options().check(&r));
assert!(!Options().check(&req));
assert!(Options().check(r.head()));
assert!(!Options().check(req.head()));
let r = TestRequest::default()
.method(Method::CONNECT)
.to_http_request();
assert!(Connect().check(&r));
assert!(!Connect().check(&req));
assert!(Connect().check(r.head()));
assert!(!Connect().check(req.head()));
let r = TestRequest::default()
.method(Method::PATCH)
.to_http_request();
assert!(Patch().check(&r));
assert!(!Patch().check(&req));
assert!(Patch().check(r.head()));
assert!(!Patch().check(req.head()));
let r = TestRequest::default()
.method(Method::TRACE)
.to_http_request();
assert!(Trace().check(&r));
assert!(!Trace().check(&req));
assert!(Trace().check(r.head()));
assert!(!Trace().check(req.head()));
}
#[test]
@@ -363,13 +393,13 @@ mod tests {
.method(Method::TRACE)
.to_http_request();
assert!(Not(Get()).check(&r));
assert!(!Not(Trace()).check(&r));
assert!(Not(Get()).check(r.head()));
assert!(!Not(Trace()).check(r.head()));
assert!(All(Trace()).and(Trace()).check(&r));
assert!(!All(Get()).and(Trace()).check(&r));
assert!(All(Trace()).and(Trace()).check(r.head()));
assert!(!All(Get()).and(Trace()).check(r.head()));
assert!(Any(Get()).or(Trace()).check(&r));
assert!(!Any(Get()).or(Get()).check(&r));
assert!(Any(Get()).or(Trace()).check(r.head()));
assert!(!Any(Get()).or(Get()).check(r.head()));
}
}

View File

@@ -42,6 +42,9 @@
//! represents an HTTP server instance and is used to instantiate and
//! configure servers.
//!
//! * [web](web/index.html): This module
//! provide essentials helper functions and types for application registration.
//!
//! * [HttpRequest](struct.HttpRequest.html) and
//! [HttpResponse](struct.HttpResponse.html): These structs
//! represent HTTP requests and responses and expose various methods
@@ -59,7 +62,7 @@
//! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Supports [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.32 or later
//! * Supported Rust version: 1.31 or later
//!
//! ## Package feature
//!
@@ -67,7 +70,7 @@
//! * `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`
//! * `cookies` - enables cookies support, includes `ring` crate as
//! * `secure-cookies` - enables secure cookies support, includes `ring` crate as
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
@@ -98,6 +101,7 @@ mod server;
mod service;
pub mod test;
mod types;
pub mod web;
#[allow(unused_imports)]
#[macro_use]
@@ -159,213 +163,6 @@ pub mod dev {
}
}
pub mod web {
//! Various types
use actix_http::{http::Method, Response};
use actix_service::{fn_transform, Service, Transform};
use futures::{Future, IntoFuture};
pub use actix_http::Response as HttpResponse;
pub use bytes::{Bytes, BytesMut};
use crate::error::{BlockingError, Error};
use crate::extract::FromRequest;
use crate::handler::{AsyncFactory, Factory};
use crate::resource::Resource;
use crate::responder::Responder;
use crate::route::Route;
use crate::scope::Scope;
use crate::service::{ServiceRequest, ServiceResponse};
pub use crate::data::{Data, RouteData};
pub use crate::request::HttpRequest;
pub use crate::types::*;
/// Create resource for a specific path.
///
/// Resources may have variable path segments. For example, a
/// resource with the path `/a/{name}/c` would match all incoming
/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
///
/// 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. This is done by
/// looking up the identifier in the `Params` object returned by
/// `HttpRequest.match_info()` method.
///
/// By default, each segment matches the regular expression `[^{}/]+`.
///
/// You can also specify a custom regex in the form `{identifier:regex}`:
///
/// For instance, to route `GET`-requests on any route matching
/// `/users/{userid}/{friend}` and store `userid` and `friend` in
/// the exposed `Params` object:
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/users/{userid}/{friend}")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn resource<P: 'static>(path: &str) -> Resource<P> {
Resource::new(path)
}
/// Configure scope for common root path.
///
/// Scopes collect multiple paths under a common path prefix.
/// Scope path can contain variable path segments as resources.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// 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()))
/// );
/// }
/// ```
///
/// In the above example, three routes get added:
/// * /{project_id}/path1
/// * /{project_id}/path2
/// * /{project_id}/path3
///
pub fn scope<P: 'static>(path: &str) -> Scope<P> {
Scope::new(path)
}
/// Create *route* without configuration.
pub fn route<P: 'static>() -> Route<P> {
Route::new()
}
/// Create *route* with `GET` method guard.
pub fn get<P: 'static>() -> Route<P> {
Route::new().method(Method::GET)
}
/// Create *route* with `POST` method guard.
pub fn post<P: 'static>() -> Route<P> {
Route::new().method(Method::POST)
}
/// Create *route* with `PUT` method guard.
pub fn put<P: 'static>() -> Route<P> {
Route::new().method(Method::PUT)
}
/// Create *route* with `PATCH` method guard.
pub fn patch<P: 'static>() -> Route<P> {
Route::new().method(Method::PATCH)
}
/// Create *route* with `DELETE` method guard.
pub fn delete<P: 'static>() -> Route<P> {
Route::new().method(Method::DELETE)
}
/// Create *route* with `HEAD` method guard.
pub fn head<P: 'static>() -> Route<P> {
Route::new().method(Method::HEAD)
}
/// Create *route* and add method guard.
pub fn method<P: 'static>(method: Method) -> Route<P> {
Route::new().method(method)
}
/// Create a new route and add handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index() -> HttpResponse {
/// unimplemented!()
/// }
///
/// App::new().service(
/// web::resource("/").route(
/// web::to(index))
/// );
/// ```
pub fn to<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: Factory<I, R> + 'static,
I: FromRequest<P> + 'static,
R: Responder + 'static,
{
Route::new().to(handler)
}
/// Create a new route and add async handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse, Error};
///
/// fn index() -> impl futures::Future<Item=HttpResponse, Error=Error> {
/// futures::future::ok(HttpResponse::Ok().finish())
/// }
///
/// App::new().service(web::resource("/").route(
/// web::to_async(index))
/// );
/// ```
pub fn to_async<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: AsyncFactory<I, R>,
I: FromRequest<P> + 'static,
R: IntoFuture + 'static,
R::Item: Into<Response>,
R::Error: Into<Error>,
{
Route::new().to_async(handler)
}
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + std::fmt::Debug + 'static,
{
actix_threadpool::run(f).from_err()
}
/// Create middleare
pub fn md<F, R, S, P, B>(
f: F,
) -> impl Transform<
S,
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
InitError = (),
>
where
S: Service<
Request = ServiceRequest<P>,
Response = ServiceResponse<B>,
Error = Error,
>,
F: FnMut(ServiceRequest<P>, &mut S) -> R + Clone,
R: IntoFuture<Item = ServiceResponse<B>, Error = Error>,
{
fn_transform(f)
}
}
#[cfg(feature = "client")]
pub mod client {
//! An HTTP Client

View File

@@ -113,7 +113,7 @@ where
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
// negotiate content-encoding
let encoding = if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
let encoding = if let Some(val) = req.headers().get(ACCEPT_ENCODING) {
if let Ok(enc) = val.to_str() {
AcceptEncoding::parse(enc, self.encoding)
} else {
@@ -157,7 +157,7 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let resp = futures::try_ready!(self.fut.poll());
let enc = if let Some(enc) = resp.head().extensions().get::<Enc>() {
let enc = if let Some(enc) = resp.response().extensions().get::<Enc>() {
enc.0
} else {
self.encoding

View File

@@ -51,7 +51,7 @@ use crate::error::{ResponseError, Result};
use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{self, HttpTryFrom, Method, StatusCode, Uri};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{HttpMessage, HttpResponse};
use crate::HttpResponse;
/// A set of errors that can occur during processing CORS
#[derive(Debug, Display)]
@@ -702,9 +702,9 @@ where
if self.inner.preflight && Method::OPTIONS == *req.method() {
if let Err(e) = self
.inner
.validate_origin(&req)
.and_then(|_| self.inner.validate_allowed_method(&req))
.and_then(|_| self.inner.validate_allowed_headers(&req))
.validate_origin(req.head())
.and_then(|_| self.inner.validate_allowed_method(req.head()))
.and_then(|_| self.inner.validate_allowed_headers(req.head()))
{
return Either::A(ok(req.error_response(e)));
}
@@ -739,7 +739,7 @@ where
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
})
.if_some(
self.inner.access_control_allow_origin(&req),
self.inner.access_control_allow_origin(req.head()),
|origin, resp| {
let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
},
@@ -762,7 +762,7 @@ where
Either::A(ok(req.into_response(res)))
} else if req.headers().contains_key(header::ORIGIN) {
// Only check requests with a origin header.
if let Err(e) = self.inner.validate_origin(&req) {
if let Err(e) = self.inner.validate_origin(req.head()) {
return Either::A(ok(req.error_response(e)));
}
@@ -771,7 +771,7 @@ where
Either::B(Either::B(Box::new(self.service.call(req).and_then(
move |mut res| {
if let Some(origin) =
inner.access_control_allow_origin(&res.request())
inner.access_control_allow_origin(res.request().head())
{
res.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
@@ -848,8 +848,8 @@ mod tests {
#[test]
fn validate_origin_allows_all_origins() {
let mut cors = Cors::new().finish(test::ok_service());
let req =
TestRequest::with_header("Origin", "https://www.example.com").to_service();
let req = TestRequest::with_header("Origin", "https://www.example.com")
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
@@ -867,20 +867,20 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
assert!(cors.inner.validate_allowed_method(&req).is_err());
assert!(cors.inner.validate_allowed_headers(&req).is_err());
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);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
assert!(cors.inner.validate_allowed_method(&req).is_err());
assert!(cors.inner.validate_allowed_headers(&req).is_err());
assert!(cors.inner.validate_allowed_method(req.head()).is_err());
assert!(cors.inner.validate_allowed_headers(req.head()).is_err());
let req = TestRequest::with_header("Origin", "https://www.example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
@@ -889,7 +889,7 @@ mod tests {
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -935,7 +935,7 @@ mod tests {
"AUTHORIZATION,ACCEPT",
)
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
@@ -960,10 +960,10 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.unknown.com")
.method(Method::GET)
.to_service();
cors.inner.validate_origin(&req).unwrap();
cors.inner.validate_allowed_method(&req).unwrap();
cors.inner.validate_allowed_headers(&req).unwrap();
.to_srv_request();
cors.inner.validate_origin(req.head()).unwrap();
cors.inner.validate_allowed_method(req.head()).unwrap();
cors.inner.validate_allowed_headers(req.head()).unwrap();
}
#[test]
@@ -974,7 +974,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::GET)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(resp.status(), StatusCode::OK);
@@ -984,7 +984,7 @@ mod tests {
fn test_no_origin_response() {
let mut cors = Cors::new().disable_preflight().finish(test::ok_service());
let req = TestRequest::default().method(Method::GET).to_service();
let req = TestRequest::default().method(Method::GET).to_srv_request();
let resp = test::call_success(&mut cors, req);
assert!(resp
.headers()
@@ -993,7 +993,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
&b"https://www.example.com"[..],
@@ -1019,7 +1019,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1066,7 +1066,7 @@ mod tests {
}));
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
&b"Accept, Origin"[..],
@@ -1082,7 +1082,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://www.example.com")
.method(Method::OPTIONS)
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
let origins_str = resp
@@ -1105,7 +1105,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.com")
.method(Method::GET)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1118,7 +1118,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.org")
.method(Method::GET)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1141,7 +1141,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.com")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(
@@ -1155,7 +1155,7 @@ mod tests {
let req = TestRequest::with_header("Origin", "https://example.org")
.header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
.method(Method::OPTIONS)
.to_service();
.to_srv_request();
let resp = test::call_success(&mut cors, req);
assert_eq!(

View File

@@ -10,7 +10,6 @@ use futures::{Async, Poll, Stream};
use crate::dev::Payload;
use crate::error::{Error, PayloadError};
use crate::service::ServiceRequest;
use crate::HttpMessage;
/// `Middleware` for decompressing request's payload.
/// `Decompress` middleware must be added with `App::chain()` method.

View File

@@ -166,11 +166,11 @@ mod tests {
)
.unwrap();
let req = TestRequest::default().to_service();
let req = TestRequest::default().to_srv_request();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
let req = TestRequest::default().to_service();
let req = TestRequest::default().to_srv_request();
let srv = FnService::new(|req: ServiceRequest<_>| {
req.into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())
});
@@ -192,7 +192,7 @@ mod tests {
let mut mw =
block_on(DefaultHeaders::new().content_type().new_transform(srv)).unwrap();
let req = TestRequest::default().to_service();
let req = TestRequest::default().to_srv_request();
let resp = block_on(mw.call(req)).unwrap();
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),

View File

@@ -180,7 +180,7 @@ mod tests {
)
.unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_service());
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
@@ -206,7 +206,7 @@ mod tests {
)
.unwrap();
let resp = test::call_success(&mut mw, TestRequest::default().to_service());
let resp = test::call_success(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
}

View File

@@ -148,7 +148,7 @@ impl<P> FromRequest<P> for Identity {
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Ok(Identity(req.clone()))
Ok(Identity(req.request().clone()))
}
}
@@ -507,7 +507,7 @@ mod tests {
let resp =
test::call_success(&mut srv, TestRequest::with_uri("/login").to_request());
assert_eq!(resp.status(), StatusCode::OK);
let c = resp.cookies().next().unwrap().to_owned();
let c = resp.response().cookies().next().unwrap().to_owned();
let resp = test::call_success(
&mut srv,

View File

@@ -15,7 +15,7 @@ use time;
use crate::dev::{BodySize, MessageBody, ResponseBody};
use crate::error::{Error, Result};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{HttpMessage, HttpResponse};
use crate::HttpResponse;
/// `Middleware` for logging request and response info to the terminal.
///
@@ -201,7 +201,7 @@ where
if let Some(ref mut format) = self.format {
for unit in &mut format.0 {
unit.render_response(&res);
unit.render_response(res.response());
}
}
@@ -469,44 +469,40 @@ mod tests {
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.to_service();
.to_srv_request();
let _res = block_on(srv.call(req));
}
// #[test]
// fn test_default_format() {
// let format = Format::default();
#[test]
fn test_default_format() {
let mut format = Format::default();
// let req = TestRequest::with_header(
// header::USER_AGENT,
// header::HeaderValue::from_static("ACTIX-WEB"),
// )
// .finish();
// let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
// let entry_time = time::now();
let req = TestRequest::with_header(
header::USER_AGENT,
header::HeaderValue::from_static("ACTIX-WEB"),
)
.to_srv_request();
// let render = |fmt: &mut Formatter| {
// for unit in &format.0 {
// unit.render(fmt, &req, &resp, entry_time)?;
// }
// Ok(())
// };
// let s = format!("{}", FormatDisplay(&render));
// assert!(s.contains("GET / HTTP/1.1"));
// assert!(s.contains("200 0"));
// assert!(s.contains("ACTIX-WEB"));
let now = time::now();
for unit in &mut format.0 {
unit.render_request(now, &req);
}
// let req = TestRequest::with_uri("/?test").finish();
// let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
// let entry_time = time::now();
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
for unit in &mut format.0 {
unit.render_response(&resp);
}
// let render = |fmt: &mut Formatter| {
// for unit in &format.0 {
// unit.render(fmt, &req, &resp, entry_time)?;
// }
// Ok(())
// };
// let s = format!("{}", FormatDisplay(&render));
// assert!(s.contains("GET /?test HTTP/1.1"));
// }
let entry_time = time::now();
let render = |fmt: &mut Formatter| {
for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?;
}
Ok(())
};
let s = format!("{}", FormatDisplay(&render));
assert!(s.contains("GET / HTTP/1.1"));
assert!(s.contains("200 1024"));
assert!(s.contains("ACTIX-WEB"));
}
}

View File

@@ -1,6 +1,5 @@
use std::cell::{Ref, RefMut};
use std::fmt;
use std::ops::Deref;
use std::rc::Rc;
use actix_http::http::{HeaderMap, Method, Uri, Version};
@@ -66,6 +65,12 @@ impl HttpRequest {
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 {
@@ -111,6 +116,18 @@ impl HttpRequest {
}
}
/// 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()
}
/// Generate url for named resource
///
/// ```rust
@@ -154,15 +171,7 @@ impl HttpRequest {
/// Get *ConnectionInfo* for the current request.
#[inline]
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
ConnectionInfo::get(&*self, &*self.config())
}
}
impl Deref for HttpRequest {
type Target = RequestHead;
fn deref(&self) -> &RequestHead {
self.head()
ConnectionInfo::get(self.head(), &*self.config())
}
}
@@ -219,7 +228,7 @@ impl<P> FromRequest<P> for HttpRequest {
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Ok(req.clone())
Ok(req.request().clone())
}
}

View File

@@ -4,7 +4,7 @@ use std::rc::Rc;
use actix_http::{Error, Response};
use actix_service::boxed::{self, BoxedNewService, BoxedService};
use actix_service::{
ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform,
apply_transform, IntoNewService, IntoTransform, NewService, Service, Transform,
};
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
@@ -254,7 +254,7 @@ where
>,
F: IntoTransform<M, T::Service>,
{
let endpoint = ApplyTransform::new(mw, self.endpoint);
let endpoint = apply_transform(mw, self.endpoint);
Resource {
endpoint,
rdef: self.rdef,

View File

@@ -313,7 +313,7 @@ pub(crate) mod tests {
let req = TestRequest::with_uri("/some").to_request();
let resp = TestRequest::block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
match resp.body() {
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"some"));

View File

@@ -200,11 +200,15 @@ where
self
}
/// Register a scope level middleware.
/// Registers middleware, in the form of a middleware component (type),
/// that runs during inbound processing in the request
/// lifecycle (request -> response), modifying request as
/// necessary, across all requests managed by the *Scope*. Scope-level
/// middleware is more limited in what it can modify, relative to Route or
/// Application level middleware, in that Scope-level middleware can not modify
/// ServiceResponse.
///
/// This is similar to `App's` middlewares, but middleware get invoked on scope level.
/// Scope level middlewares are not allowed to change response
/// type (i.e modify response's body).
/// Use middleware when you need to read or modify *every* request in some way.
pub fn wrap<M, F>(
self,
mw: F,
@@ -238,10 +242,12 @@ where
}
}
/// Register a scope level middleware function.
///
/// This function accepts instance of `ServiceRequest` type and
/// mutable reference to the next middleware in chain.
/// Registers middleware, in the form of a closure, that runs during inbound
/// processing in the request lifecycle (request -> response), modifying
/// request as necessary, across all requests managed by the *Scope*.
/// Scope-level middleware is more limited in what it can modify, relative
/// to Route or Application level middleware, in that Scope-level middleware
/// can not modify ServiceResponse.
///
/// ```rust
/// use actix_service::Service;
@@ -687,7 +693,7 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
match resp.body() {
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
@@ -793,7 +799,7 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::CREATED);
match resp.body() {
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
@@ -820,7 +826,7 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::CREATED);
match resp.body() {
match resp.response().body() {
ResponseBody::Body(Body::Bytes(ref b)) => {
let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));

View File

@@ -4,7 +4,7 @@ use std::marker::PhantomData;
use std::rc::Rc;
use actix_http::body::{Body, MessageBody, ResponseBody};
use actix_http::http::{HeaderMap, Method, Uri, Version};
use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version};
use actix_http::{
Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead,
Response, ResponseHead,
@@ -123,7 +123,13 @@ impl<P> ServiceRequest<P> {
}
#[inline]
/// Returns mutable Request's headers.
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head().headers
}
#[inline]
/// Returns mutable request's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers
}
@@ -202,20 +208,6 @@ impl<P> HttpMessage for ServiceRequest<P> {
}
}
impl<P> std::ops::Deref for ServiceRequest<P> {
type Target = RequestHead;
fn deref(&self) -> &RequestHead {
self.req.head()
}
}
impl<P> std::ops::DerefMut for ServiceRequest<P> {
fn deref_mut(&mut self) -> &mut RequestHead {
self.head_mut()
}
}
impl<P> fmt::Debug for ServiceRequest<P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(
@@ -255,11 +247,19 @@ impl<P> ServiceFromRequest<P> {
}
#[inline]
/// Get reference to inner HttpRequest
pub fn request(&self) -> &HttpRequest {
&self.req
}
#[inline]
/// Convert this request into a HttpRequest
pub fn into_request(self) -> HttpRequest {
self.req
}
#[inline]
/// Get match information for this request
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
&mut self.req.path
}
@@ -281,14 +281,6 @@ impl<P> ServiceFromRequest<P> {
}
}
impl<P> std::ops::Deref for ServiceFromRequest<P> {
type Target = HttpRequest;
fn deref(&self) -> &HttpRequest {
&self.req
}
}
impl<P> HttpMessage for ServiceFromRequest<P> {
type Stream = P;
@@ -366,6 +358,24 @@ impl<B> ServiceResponse<B> {
&mut self.response
}
/// Get the response status code
#[inline]
pub fn status(&self) -> StatusCode {
self.response.status()
}
#[inline]
/// Returns response's headers.
pub fn headers(&self) -> &HeaderMap {
self.response.headers()
}
#[inline]
/// Returns mutable response's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
self.response.headers_mut()
}
/// Execute closure and in case of error convert it to response.
pub fn checked_expr<F, E>(mut self, f: F) -> Self
where
@@ -402,20 +412,6 @@ impl<B> ServiceResponse<B> {
}
}
impl<B> std::ops::Deref for ServiceResponse<B> {
type Target = Response<B>;
fn deref(&self) -> &Response<B> {
self.response()
}
}
impl<B> std::ops::DerefMut for ServiceResponse<B> {
fn deref_mut(&mut self) -> &mut Response<B> {
self.response_mut()
}
}
impl<B> Into<Response<B>> for ServiceResponse<B> {
fn into(self) -> Response<B> {
self.response
@@ -449,3 +445,30 @@ impl<B: MessageBody> fmt::Debug for ServiceResponse<B> {
res
}
}
#[cfg(test)]
mod tests {
use crate::test::TestRequest;
use crate::HttpResponse;
#[test]
fn test_fmt_debug() {
let req = TestRequest::get()
.uri("/index.html?test=1")
.header("x-test", "111")
.to_srv_request();
let s = format!("{:?}", req);
assert!(s.contains("ServiceRequest"));
assert!(s.contains("test=1"));
assert!(s.contains("x-test"));
let res = HttpResponse::Ok().header("x-test", "111").finish();
let res = TestRequest::post()
.uri("/index.html?test=1")
.to_srv_response(res);
let s = format!("{:?}", res);
assert!(s.contains("ServiceResponse"));
assert!(s.contains("x-test"));
}
}

View File

@@ -56,6 +56,7 @@ where
.unwrap()
}
/// Create service that always responds with `HttpResponse::Ok()`
pub fn ok_service() -> impl Service<
Request = ServiceRequest<PayloadStream>,
Response = ServiceResponse<Body>,
@@ -64,6 +65,7 @@ pub fn ok_service() -> impl Service<
default_service(StatusCode::OK)
}
/// Create service that responds with response with specified status code
pub fn default_service(
status_code: StatusCode,
) -> impl Service<
@@ -318,7 +320,7 @@ impl TestRequest {
}
/// Complete request creation and generate `ServiceRequest` instance
pub fn to_service(mut self) -> ServiceRequest<PayloadStream> {
pub fn to_srv_request(mut self) -> ServiceRequest<PayloadStream> {
let req = self.req.finish();
ServiceRequest::new(
@@ -329,16 +331,16 @@ impl TestRequest {
)
}
/// Complete request creation and generate `ServiceResponse` instance
pub fn to_srv_response<B>(self, res: HttpResponse<B>) -> ServiceResponse<B> {
self.to_srv_request().into_response(res)
}
/// Complete request creation and generate `Request` instance
pub fn to_request(mut self) -> Request<PayloadStream> {
self.req.finish()
}
/// Complete request creation and generate `ServiceResponse` instance
pub fn to_response<B>(self, res: HttpResponse<B>) -> ServiceResponse<B> {
self.to_service().into_response(res)
}
/// Complete request creation and generate `HttpRequest` instance
pub fn to_http_request(mut self) -> HttpRequest {
let req = self.req.finish();

View File

@@ -80,7 +80,7 @@ where
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let req2 = req.clone();
let req2 = req.request().clone();
let (limit, err) = req
.route_data::<FormConfig>()
.map(|c| (c.limit, c.ehandler.clone()))

View File

@@ -174,7 +174,7 @@ where
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
let req2 = req.clone();
let req2 = req.request().clone();
let (limit, err) = req
.route_data::<JsonConfig>()
.map(|c| (c.limit, c.ehandler.clone()))

View File

@@ -170,7 +170,7 @@ where
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
Self::extract(req).map_err(ErrorNotFound)
Self::extract(req.request()).map_err(ErrorNotFound)
}
}

View File

@@ -119,7 +119,7 @@ where
#[inline]
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
serde_urlencoded::from_str::<T>(req.query_string())
serde_urlencoded::from_str::<T>(req.request().query_string())
.map(|val| Ok(Query(val)))
.unwrap_or_else(|e| Err(e.into()))
}

285
src/web.rs Normal file
View File

@@ -0,0 +1,285 @@
//! Essentials helper functions and types for application registration.
use actix_http::{http::Method, Response};
use futures::{Future, IntoFuture};
pub use actix_http::Response as HttpResponse;
pub use bytes::{Bytes, BytesMut};
use crate::error::{BlockingError, Error};
use crate::extract::FromRequest;
use crate::handler::{AsyncFactory, Factory};
use crate::resource::Resource;
use crate::responder::Responder;
use crate::route::Route;
use crate::scope::Scope;
pub use crate::data::{Data, RouteData};
pub use crate::request::HttpRequest;
pub use crate::types::*;
/// Create resource for a specific path.
///
/// Resources may have variable path segments. For example, a
/// resource with the path `/a/{name}/c` would match all incoming
/// requests with paths such as `/a/b/c`, `/a/1/c`, or `/a/etc/c`.
///
/// 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. This is done by
/// looking up the identifier in the `Params` object returned by
/// `HttpRequest.match_info()` method.
///
/// By default, each segment matches the regular expression `[^{}/]+`.
///
/// You can also specify a custom regex in the form `{identifier:regex}`:
///
/// For instance, to route `GET`-requests on any route matching
/// `/users/{userid}/{friend}` and store `userid` and `friend` in
/// the exposed `Params` object:
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/users/{userid}/{friend}")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ```
pub fn resource<P: 'static>(path: &str) -> Resource<P> {
Resource::new(path)
}
/// Configure scope for common root path.
///
/// Scopes collect multiple paths under a common path prefix.
/// Scope path can contain variable path segments as resources.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// 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()))
/// );
/// }
/// ```
///
/// In the above example, three routes get added:
/// * /{project_id}/path1
/// * /{project_id}/path2
/// * /{project_id}/path3
///
pub fn scope<P: 'static>(path: &str) -> Scope<P> {
Scope::new(path)
}
/// Create *route* without configuration.
pub fn route<P: 'static>() -> Route<P> {
Route::new()
}
/// Create *route* with `GET` method guard.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `GET` route get added:
/// * /{project_id}
///
pub fn get<P: 'static>() -> Route<P> {
Route::new().method(Method::GET)
}
/// Create *route* with `POST` method guard.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::post().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `POST` route get added:
/// * /{project_id}
///
pub fn post<P: 'static>() -> Route<P> {
Route::new().method(Method::POST)
}
/// Create *route* with `PUT` method guard.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::put().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `PUT` route get added:
/// * /{project_id}
///
pub fn put<P: 'static>() -> Route<P> {
Route::new().method(Method::PUT)
}
/// Create *route* with `PATCH` method guard.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::patch().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `PATCH` route get added:
/// * /{project_id}
///
pub fn patch<P: 'static>() -> Route<P> {
Route::new().method(Method::PATCH)
}
/// Create *route* with `DELETE` method guard.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::delete().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `DELETE` route get added:
/// * /{project_id}
///
pub fn delete<P: 'static>() -> Route<P> {
Route::new().method(Method::DELETE)
}
/// Create *route* with `HEAD` method guard.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::head().to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `HEAD` route get added:
/// * /{project_id}
///
pub fn head<P: 'static>() -> Route<P> {
Route::new().method(Method::HEAD)
}
/// Create *route* and add method guard.
///
/// ```rust
/// use actix_web::{web, http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/{project_id}")
/// .route(web::method(http::Method::GET).to(|| HttpResponse::Ok()))
/// );
/// }
/// ```
///
/// In the above example, one `GET` route get added:
/// * /{project_id}
///
pub fn method<P: 'static>(method: Method) -> Route<P> {
Route::new().method(method)
}
/// Create a new route and add handler.
///
/// ```rust
/// use actix_web::{web, App, HttpResponse};
///
/// fn index() -> HttpResponse {
/// unimplemented!()
/// }
///
/// App::new().service(
/// web::resource("/").route(
/// web::to(index))
/// );
/// ```
pub fn to<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: Factory<I, R> + 'static,
I: FromRequest<P> + 'static,
R: Responder + 'static,
{
Route::new().to(handler)
}
/// Create a new route and add async handler.
///
/// ```rust
/// # use futures::future::{ok, Future};
/// use actix_web::{web, App, HttpResponse, Error};
///
/// fn index() -> impl Future<Item=HttpResponse, Error=Error> {
/// ok(HttpResponse::Ok().finish())
/// }
///
/// App::new().service(web::resource("/").route(
/// web::to_async(index))
/// );
/// ```
pub fn to_async<F, I, R, P: 'static>(handler: F) -> Route<P>
where
F: AsyncFactory<I, R>,
I: FromRequest<P> + 'static,
R: IntoFuture + 'static,
R::Item: Into<Response>,
R::Error: Into<Error>,
{
Route::new().to_async(handler)
}
/// Execute blocking function on a thread pool, returns future that resolves
/// to result of the function execution.
pub fn block<F, I, E>(f: F) -> impl Future<Item = I, Error = BlockingError<E>>
where
F: FnOnce() -> Result<I, E> + Send + 'static,
I: Send + 'static,
E: Send + std::fmt::Debug + 'static,
{
actix_threadpool::run(f).from_err()
}

View File

@@ -1,5 +1,10 @@
# Changes
## [0.1.0-alpha.3] - 2019-04-02
* Request functions accept path #743
## [0.1.0-alpha.2] - 2019-03-29
* Added TestServerRuntime::load_body() method

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "0.1.0-alpha.2"
version = "0.1.0-alpha.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http test server"
readme = "README.md"
@@ -35,7 +35,7 @@ actix-rt = "0.2.1"
actix-service = "0.3.4"
actix-server = "0.4.0"
actix-utils = "0.3.4"
awc = "0.1.0-alpha.2"
awc = "0.1.0-alpha.3"
base64 = "0.10"
bytes = "0.4"
@@ -53,3 +53,7 @@ time = "0.1"
tokio-tcp = "0.1"
tokio-timer = "0.2"
openssl = { version="0.10", optional = true }
[dev-dependencies]
actix-web = "1.0.0-alpa.3"
actix-http = "0.1.0-alpa.3"

View File

@@ -36,7 +36,7 @@ use net2::TcpBuilder;
/// )
/// );
///
/// let req = srv.get();
/// let req = srv.get("/");
/// let response = srv.block_on(req.send()).unwrap();
/// assert!(response.status().is_success());
/// }
@@ -159,33 +159,33 @@ impl TestServerRuntime {
}
/// Create `GET` request
pub fn get(&self) -> ClientRequest {
self.client.get(self.url("/").as_str())
pub fn get<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.get(self.url(path.as_ref()).as_str())
}
/// Create https `GET` request
pub fn sget(&self) -> ClientRequest {
self.client.get(self.surl("/").as_str())
pub fn sget<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.get(self.surl(path.as_ref()).as_str())
}
/// Create `POST` request
pub fn post(&self) -> ClientRequest {
self.client.post(self.url("/").as_str())
pub fn post<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.post(self.url(path.as_ref()).as_str())
}
/// Create https `POST` request
pub fn spost(&self) -> ClientRequest {
self.client.post(self.surl("/").as_str())
pub fn spost<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.post(self.surl(path.as_ref()).as_str())
}
/// Create `HEAD` request
pub fn head(&self) -> ClientRequest {
self.client.head(self.url("/").as_str())
pub fn head<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.head(self.url(path.as_ref()).as_str())
}
/// Create https `HEAD` request
pub fn shead(&self) -> ClientRequest {
self.client.head(self.surl("/").as_str())
pub fn shead<S: AsRef<str>>(&self, path: S) -> ClientRequest {
self.client.head(self.surl(path.as_ref()).as_str())
}
/// Connect to test http server
@@ -195,7 +195,7 @@ impl TestServerRuntime {
pub fn load_body<S>(
&mut self,
response: ClientResponse<S>,
mut response: ClientResponse<S>,
) -> Result<Bytes, PayloadError>
where
S: Stream<Item = Bytes, Error = PayloadError> + 'static,

View File

@@ -2,8 +2,11 @@ use net2::TcpBuilder;
use std::sync::mpsc;
use std::{net, thread, time::Duration};
#[cfg(feature = "ssl")]
use openssl::ssl::SslAcceptorBuilder;
use actix_http::Response;
use actix_web::{web, App, HttpServer};
use actix_web::{test, web, App, HttpServer};
fn unused_addr() -> net::SocketAddr {
let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
@@ -76,3 +79,77 @@ fn test_start() {
thread::sleep(Duration::from_millis(100));
let _ = sys.stop();
}
#[cfg(feature = "ssl")]
fn ssl_acceptor() -> std::io::Result<SslAcceptorBuilder> {
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
// load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("tests/key.pem", SslFiletype::PEM)
.unwrap();
builder
.set_certificate_chain_file("tests/cert.pem")
.unwrap();
Ok(builder)
}
#[test]
#[cfg(feature = "ssl")]
fn test_start_ssl() {
let addr = unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let sys = actix_rt::System::new("test");
let builder = ssl_acceptor().unwrap();
let srv = HttpServer::new(|| {
App::new().service(
web::resource("/").route(web::to(|| Response::Ok().body("test"))),
)
})
.workers(1)
.shutdown_timeout(1)
.system_exit()
.disable_signals()
.bind_ssl(format!("{}", addr), builder)
.unwrap()
.start();
let _ = tx.send((srv, actix_rt::System::current()));
let _ = sys.run();
});
let (srv, sys) = rx.recv().unwrap();
let client = test::run_on(|| {
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Ok::<_, ()>(
awc::Client::build()
.connector(
awc::Connector::new()
.ssl(builder.build())
.timeout(Duration::from_millis(100))
.service(),
)
.finish(),
)
})
.unwrap();
let host = format!("https://{}", addr);
let response = test::block_on(client.get(host.clone()).send()).unwrap();
assert!(response.status().is_success());
// stop
let _ = srv.stop(false);
thread::sleep(Duration::from_millis(100));
let _ = sys.stop();
}

View File

@@ -54,7 +54,7 @@ fn test_body() {
)
});
let mut response = srv.block_on(srv.get().send()).unwrap();
let mut response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -73,7 +73,7 @@ fn test_body_gzip() {
)
});
let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -111,7 +111,7 @@ fn test_body_encoding_override() {
});
// Builder
let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -161,7 +161,7 @@ fn test_body_gzip_large() {
)
});
let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -195,7 +195,7 @@ fn test_body_gzip_large_random() {
)
});
let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -224,7 +224,7 @@ fn test_body_chunked_implicit() {
)
});
let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap();
assert!(response.status().is_success());
assert_eq!(
response.headers().get(TRANSFER_ENCODING).unwrap(),
@@ -258,7 +258,7 @@ fn test_body_br_streaming() {
let mut response = srv
.block_on(
srv.get()
srv.get("/")
.header(ACCEPT_ENCODING, "br")
.no_decompress()
.send(),
@@ -284,7 +284,7 @@ fn test_head_binary() {
)))
});
let mut response = srv.block_on(srv.head().send()).unwrap();
let mut response = srv.block_on(srv.head("/").send()).unwrap();
assert!(response.status().is_success());
{
@@ -310,7 +310,7 @@ fn test_no_chunking() {
))))
});
let mut response = srv.block_on(srv.get().send()).unwrap();
let mut response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
assert!(!response.headers().contains_key(TRANSFER_ENCODING));
@@ -333,7 +333,7 @@ fn test_body_deflate() {
});
// client request
let mut response = srv.block_on(srv.get().no_decompress().send()).unwrap();
let mut response = srv.block_on(srv.get("/").no_decompress().send()).unwrap();
assert!(response.status().is_success());
// read response
@@ -362,7 +362,7 @@ fn test_body_brotli() {
// client request
let mut response = srv
.block_on(
srv.get()
srv.get("/")
.header(ACCEPT_ENCODING, "br")
.no_decompress()
.send(),
@@ -398,7 +398,7 @@ fn test_encoding() {
let enc = e.finish().unwrap();
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "gzip")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -427,7 +427,7 @@ fn test_gzip_encoding() {
let enc = e.finish().unwrap();
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "gzip")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -457,7 +457,7 @@ fn test_gzip_encoding_large() {
let enc = e.finish().unwrap();
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "gzip")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -491,7 +491,7 @@ fn test_reading_gzip_encoding_large_random() {
let enc = e.finish().unwrap();
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "gzip")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -521,7 +521,7 @@ fn test_reading_deflate_encoding() {
// client request
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "deflate")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -551,7 +551,7 @@ fn test_reading_deflate_encoding_large() {
// client request
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "deflate")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -585,7 +585,7 @@ fn test_reading_deflate_encoding_large_random() {
// client request
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "deflate")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -615,7 +615,7 @@ fn test_brotli_encoding() {
// client request
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "br")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -645,7 +645,7 @@ fn test_brotli_encoding_large() {
// client request
let request = srv
.post()
.post("/")
.header(CONTENT_ENCODING, "br")
.send_body(enc.clone());
let mut response = srv.block_on(request).unwrap();
@@ -912,7 +912,7 @@ fn test_reading_deflate_encoding_large_random_ssl() {
// .finish();
// let second_cookie = http::Cookie::new("second", "second_value");
// let request = srv.get().finish().unwrap();
// let request = srv.get("/").finish().unwrap();
// let response = srv.execute(request.send()).unwrap();
// assert!(response.status().is_success());