, ServiceResponse, Error>;
-type HttpNewService =
- BoxedNewService<(), ServiceRequest
, ServiceResponse, Error, ()>;
+type HttpService = BoxedService;
+type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>;
/// Return the MIME type associated with a filename extension (case-insensitive).
/// If `ext` is empty or no associated type for the extension was found, returns
@@ -225,18 +224,18 @@ type MimeOverride = Fn(&mime::Name) -> DispositionType;
/// .service(fs::Files::new("/static", "."));
/// }
/// ```
-pub struct Files {
+pub struct Files {
path: String,
directory: PathBuf,
index: Option,
show_index: bool,
- default: Rc>>>>,
+ default: Rc>>>,
renderer: Rc,
mime_override: Option>,
file_flags: named::Flags,
}
-impl Clone for Files {
+impl Clone for Files {
fn clone(&self) -> Self {
Self {
directory: self.directory.clone(),
@@ -251,13 +250,13 @@ impl Clone for Files {
}
}
-impl Files {
+impl Files {
/// Create new `Files` instance for specified base directory.
///
/// `File` uses `ThreadPool` for blocking filesystem operations.
/// By default pool with 5x threads of available cpus is used.
/// Pool size can be changed by setting ACTIX_CPU_POOL environment variable.
- pub fn new>(path: &str, dir: T) -> Files {
+ pub fn new>(path: &str, dir: T) -> Files {
let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new());
if !dir.is_dir() {
log::error!("Specified path is not a directory");
@@ -335,7 +334,7 @@ impl Files {
where
F: IntoNewService,
U: NewService<
- Request = ServiceRequest,
+ Request = ServiceRequest,
Response = ServiceResponse,
Error = Error,
> + 'static,
@@ -349,11 +348,8 @@ impl Files {
}
}
-impl HttpServiceFactory
for Files
-where
- P: 'static,
-{
- fn register(self, config: &mut ServiceConfig
) {
+impl HttpServiceFactory for Files {
+ fn register(self, config: &mut AppService) {
if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service());
}
@@ -366,11 +362,11 @@ where
}
}
-impl NewService for Files {
- type Request = ServiceRequest
;
+impl NewService for Files {
+ type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
- type Service = FilesService
;
+ type Service = FilesService;
type InitError = ();
type Future = Box>;
@@ -401,37 +397,36 @@ impl NewService for Files {
}
}
-pub struct FilesService
{
+pub struct FilesService {
directory: PathBuf,
index: Option,
show_index: bool,
- default: Option>,
+ default: Option,
renderer: Rc,
mime_override: Option>,
file_flags: named::Flags,
}
-impl FilesService
{
+impl FilesService {
fn handle_err(
&mut self,
e: io::Error,
- req: HttpRequest,
- payload: Payload
,
+ req: ServiceRequest,
) -> Either<
FutureResult,
Box>,
> {
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)))
+ default.call(req)
} else {
- Either::A(ok(ServiceResponse::from_err(e, req.clone())))
+ Either::A(ok(req.error_response(e)))
}
}
}
-impl Service for FilesService
{
- type Request = ServiceRequest
;
+impl Service for FilesService {
+ type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Future = Either<
@@ -443,18 +438,18 @@ impl
Service for FilesService
{
Ok(Async::Ready(()))
}
- fn call(&mut self, req: ServiceRequest
) -> Self::Future {
- let (req, pl) = req.into_parts();
+ fn call(&mut self, req: ServiceRequest) -> Self::Future {
+ // let (req, pl) = req.into_parts();
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item,
- Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req.clone()))),
+ Err(e) => return Either::A(ok(req.error_response(e))),
};
// full filepath
let path = match self.directory.join(&real_path.0).canonicalize() {
Ok(path) => path,
- Err(e) => return self.handle_err(e, req, pl),
+ Err(e) => return self.handle_err(e, req),
};
if path.is_dir() {
@@ -470,24 +465,26 @@ impl
Service for FilesService
{
}
named_file.flags = self.file_flags;
+ let (req, _) = req.into_parts();
Either::A(ok(match named_file.respond_to(&req) {
- Ok(item) => ServiceResponse::new(req.clone(), item),
- Err(e) => ServiceResponse::from_err(e, req.clone()),
+ Ok(item) => ServiceResponse::new(req, item),
+ Err(e) => ServiceResponse::from_err(e, req),
}))
}
- Err(e) => return self.handle_err(e, req, pl),
+ Err(e) => return self.handle_err(e, req),
}
} else if self.show_index {
let dir = Directory::new(self.directory.clone(), path);
+ let (req, _) = req.into_parts();
let x = (self.renderer)(&dir, &req);
match x {
Ok(resp) => Either::A(ok(resp)),
- Err(e) => return self.handle_err(e, req, pl),
+ Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))),
}
} else {
Either::A(ok(ServiceResponse::from_err(
FilesError::IsDirectory,
- req.clone(),
+ req.into_parts().0,
)))
}
} else {
@@ -500,16 +497,15 @@ impl
Service for FilesService
{
}
named_file.flags = self.file_flags;
+ let (req, _) = req.into_parts();
match named_file.respond_to(&req) {
Ok(item) => {
Either::A(ok(ServiceResponse::new(req.clone(), item)))
}
- Err(e) => {
- Either::A(ok(ServiceResponse::from_err(e, req.clone())))
- }
+ Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))),
}
}
- Err(e) => self.handle_err(e, req, pl),
+ Err(e) => self.handle_err(e, req),
}
}
}
@@ -547,12 +543,13 @@ impl PathBufWrp {
}
}
-impl
FromRequest
for PathBufWrp {
+impl FromRequest for PathBufWrp {
type Error = UriSegmentError;
type Future = Result;
+ type Config = ();
- fn from_request(req: &mut ServiceFromRequest) -> Self::Future {
- PathBufWrp::get_pathbuf(req.request().match_info().path())
+ fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
+ PathBufWrp::get_pathbuf(req.match_info().path())
}
}
@@ -570,6 +567,7 @@ mod tests {
self, ContentDisposition, DispositionParam, DispositionType,
};
use actix_web::http::{Method, StatusCode};
+ use actix_web::middleware::Compress;
use actix_web::test::{self, TestRequest};
use actix_web::App;
@@ -777,7 +775,7 @@ mod tests {
);
let request = TestRequest::get().uri("/").to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::OK);
let content_disposition = response
@@ -801,7 +799,7 @@ mod tests {
.uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=10-20")
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
// Invalid range header
@@ -809,7 +807,7 @@ mod tests {
.uri("/t%65st/Cargo.toml")
.header(header::RANGE, "bytes=1-0")
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
}
@@ -826,7 +824,7 @@ mod tests {
.header(header::RANGE, "bytes=10-20")
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
let contentrange = response
.headers()
.get(header::CONTENT_RANGE)
@@ -841,7 +839,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-5")
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
let contentrange = response
.headers()
@@ -864,7 +862,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-20")
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
let contentlength = response
.headers()
@@ -880,7 +878,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
.header(header::RANGE, "bytes=10-8")
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE);
// Without range header
@@ -888,7 +886,7 @@ mod tests {
.uri("/t%65st/tests/test.binary")
// .no_default_headers()
.to_request();
- let response = test::call_success(&mut srv, request);
+ let response = test::call_service(&mut srv, request);
let contentlength = response
.headers()
@@ -903,7 +901,7 @@ mod tests {
let request = TestRequest::get()
.uri("/t%65st/tests/test.binary")
.to_request();
- let mut response = test::call_success(&mut srv, request);
+ let mut response = test::call_service(&mut srv, request);
// with enabled compression
// {
@@ -934,7 +932,7 @@ mod tests {
let request = TestRequest::get()
.uri("/tests/test%20space.binary")
.to_request();
- let mut response = test::call_success(&mut srv, request);
+ let mut response = test::call_service(&mut srv, request);
assert_eq!(response.status(), StatusCode::OK);
let bytes =
@@ -965,7 +963,7 @@ mod tests {
#[test]
fn test_named_file_content_encoding() {
- let mut srv = test::init_service(App::new().enable_encoding().service(
+ let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
web::resource("/").to(|| {
NamedFile::open("Cargo.toml")
.unwrap()
@@ -977,16 +975,34 @@ mod tests {
.uri("/")
.header(header::ACCEPT_ENCODING, "gzip")
.to_request();
- let res = test::call_success(&mut srv, request);
+ let res = test::call_service(&mut srv, request);
assert_eq!(res.status(), StatusCode::OK);
+ assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
+ }
+ #[test]
+ fn test_named_file_content_encoding_gzip() {
+ let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
+ web::resource("/").to(|| {
+ NamedFile::open("Cargo.toml")
+ .unwrap()
+ .set_content_encoding(header::ContentEncoding::Gzip)
+ }),
+ ));
+
+ let request = TestRequest::get()
+ .uri("/")
+ .header(header::ACCEPT_ENCODING, "gzip")
+ .to_request();
+ let res = test::call_service(&mut srv, request);
+ assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers()
.get(header::CONTENT_ENCODING)
.unwrap()
.to_str()
.unwrap(),
- "identity"
+ "gzip"
);
}
@@ -1005,20 +1021,20 @@ mod tests {
);
let req = TestRequest::with_uri("/missing").to_request();
- let resp = test::call_success(&mut srv, req);
+ let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default().to_request();
- let resp = test::call_success(&mut srv, req);
+ let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
let mut srv = test::init_service(
App::new().service(Files::new("/", ".").show_files_listing()),
);
let req = TestRequest::with_uri("/tests").to_request();
- let mut resp = test::call_success(&mut srv, req);
+ let mut resp = test::call_service(&mut srv, req);
assert_eq!(
resp.headers().get(header::CONTENT_TYPE).unwrap(),
"text/html; charset=utf-8"
@@ -1035,15 +1051,15 @@ mod tests {
#[test]
fn test_static_files_bad_directory() {
- let _st: Files<()> = Files::new("/", "missing");
- let _st: Files<()> = Files::new("/", "Cargo.toml");
+ let _st: Files = Files::new("/", "missing");
+ let _st: Files = Files::new("/", "Cargo.toml");
}
#[test]
fn test_default_handler_file_missing() {
let mut st = test::block_on(
Files::new("/", ".")
- .default_handler(|req: ServiceRequest<_>| {
+ .default_handler(|req: ServiceRequest| {
Ok(req.into_response(HttpResponse::Ok().body("default content")))
})
.new_service(&()),
@@ -1051,7 +1067,7 @@ mod tests {
.unwrap();
let req = TestRequest::with_uri("/missing").to_srv_request();
- let mut resp = test::call_success(&mut st, req);
+ let mut resp = test::call_service(&mut st, req);
assert_eq!(resp.status(), StatusCode::OK);
let bytes =
test::block_on(resp.take_body().fold(BytesMut::new(), |mut b, c| {
diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs
index 4ee1a3caa..c506c02f2 100644
--- a/actix-files/src/named.rs
+++ b/actix-files/src/named.rs
@@ -15,7 +15,7 @@ use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType,
};
use actix_web::http::{ContentEncoding, Method, StatusCode};
-use actix_web::middleware::encoding::BodyEncoding;
+use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
use crate::range::HttpRange;
@@ -296,10 +296,9 @@ impl Responder for NamedFile {
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
- // TODO blocking by compressing
- // if let Some(current_encoding) = self.encoding {
- // resp.content_encoding(current_encoding);
- // }
+ if let Some(current_encoding) = self.encoding {
+ resp.encoding(current_encoding);
+ }
let reader = ChunkedReadFile {
size: self.md.len(),
offset: 0,
@@ -345,7 +344,7 @@ impl Responder for NamedFile {
// check last modified
let not_modified = if !none_match(etag.as_ref(), req) {
true
- } else if req.headers().contains_key(header::IF_NONE_MATCH) {
+ } else if req.headers().contains_key(&header::IF_NONE_MATCH) {
false
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
(last_modified, req.get_header())
@@ -379,7 +378,7 @@ impl Responder for NamedFile {
let mut offset = 0;
// check for range header
- if let Some(ranges) = req.headers().get(header::RANGE) {
+ if let Some(ranges) = req.headers().get(&header::RANGE) {
if let Ok(rangesheader) = ranges.to_str() {
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
length = rangesvec[0].length;
diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml
new file mode 100644
index 000000000..f0622fd90
--- /dev/null
+++ b/actix-framed/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "actix-framed"
+version = "0.1.0"
+authors = ["Nikolay Kim "]
+description = "Actix framed app server"
+readme = "README.md"
+keywords = ["http", "web", "framework", "async", "futures"]
+homepage = "https://actix.rs"
+repository = "https://github.com/actix/actix-web.git"
+documentation = "https://docs.rs/actix-framed/"
+categories = ["network-programming", "asynchronous",
+ "web-programming::http-server",
+ "web-programming::websocket"]
+license = "MIT/Apache-2.0"
+edition = "2018"
+workspace =".."
+
+[lib]
+name = "actix_framed"
+path = "src/lib.rs"
+
+[dependencies]
+actix-codec = "0.1.2"
+actix-service = "0.3.6"
+actix-utils = "0.3.4"
+actix-router = "0.1.2"
+actix-rt = "0.2.2"
+actix-http = "0.1.0"
+
+bytes = "0.4"
+futures = "0.1.25"
+log = "0.4"
+
+[dev-dependencies]
+actix-server = { version = "0.4.3", features=["ssl"] }
+actix-connect = { version = "0.1.4", features=["ssl"] }
+actix-http-test = { version = "0.1.0", features=["ssl"] }
diff --git a/actix-framed/LICENSE-APACHE b/actix-framed/LICENSE-APACHE
new file mode 100644
index 000000000..6cdf2d16c
--- /dev/null
+++ b/actix-framed/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017-NOW Nikolay Kim
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/actix-framed/LICENSE-MIT b/actix-framed/LICENSE-MIT
new file mode 100644
index 000000000..0f80296ae
--- /dev/null
+++ b/actix-framed/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2017 Nikolay Kim
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/actix-framed/README.md b/actix-framed/README.md
new file mode 100644
index 000000000..f56ae145c
--- /dev/null
+++ b/actix-framed/README.md
@@ -0,0 +1 @@
+# Framed app for actix web [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-framed) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
diff --git a/actix-framed/changes.md b/actix-framed/changes.md
new file mode 100644
index 000000000..9cef3c057
--- /dev/null
+++ b/actix-framed/changes.md
@@ -0,0 +1,10 @@
+# Changes
+
+## [0.1.0] - 2019-04-16
+
+* Update tests
+
+
+## [0.1.0-alpha.1] - 2019-04-12
+
+* Initial release
diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs
new file mode 100644
index 000000000..20bc2f771
--- /dev/null
+++ b/actix-framed/src/app.rs
@@ -0,0 +1,215 @@
+use std::rc::Rc;
+
+use actix_codec::{AsyncRead, AsyncWrite, Framed};
+use actix_http::h1::{Codec, SendResponse};
+use actix_http::{Error, Request, Response};
+use actix_router::{Path, Router, Url};
+use actix_service::{IntoNewService, NewService, Service};
+use actix_utils::cloneable::CloneableService;
+use futures::{Async, Future, Poll};
+
+use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
+use crate::request::FramedRequest;
+use crate::state::State;
+
+type BoxedResponse = Box>;
+
+pub trait HttpServiceFactory {
+ type Factory: NewService;
+
+ fn path(&self) -> &str;
+
+ fn create(self) -> Self::Factory;
+}
+
+/// Application builder
+pub struct FramedApp {
+ state: State,
+ services: Vec<(String, BoxedHttpNewService>)>,
+}
+
+impl FramedApp {
+ pub fn new() -> Self {
+ FramedApp {
+ state: State::new(()),
+ services: Vec::new(),
+ }
+ }
+}
+
+impl FramedApp {
+ pub fn with(state: S) -> FramedApp {
+ FramedApp {
+ services: Vec::new(),
+ state: State::new(state),
+ }
+ }
+
+ pub fn service(mut self, factory: U) -> Self
+ where
+ U: HttpServiceFactory,
+ U::Factory: NewService<
+ Request = FramedRequest,
+ Response = (),
+ Error = Error,
+ InitError = (),
+ > + 'static,
+ ::Future: 'static,
+ ::Service: Service<
+ Request = FramedRequest,
+ Response = (),
+ Error = Error,
+ Future = Box>,
+ >,
+ {
+ let path = factory.path().to_string();
+ self.services
+ .push((path, Box::new(HttpNewService::new(factory.create()))));
+ self
+ }
+}
+
+impl IntoNewService> for FramedApp
+where
+ T: AsyncRead + AsyncWrite + 'static,
+ S: 'static,
+{
+ fn into_new_service(self) -> FramedAppFactory {
+ FramedAppFactory {
+ state: self.state,
+ services: Rc::new(self.services),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct FramedAppFactory {
+ state: State,
+ services: Rc>)>>,
+}
+
+impl NewService for FramedAppFactory
+where
+ T: AsyncRead + AsyncWrite + 'static,
+ S: 'static,
+{
+ type Request = (Request, Framed);
+ type Response = ();
+ type Error = Error;
+ type InitError = ();
+ type Service = CloneableService>;
+ type Future = CreateService;
+
+ fn new_service(&self, _: &C) -> Self::Future {
+ CreateService {
+ fut: self
+ .services
+ .iter()
+ .map(|(path, service)| {
+ CreateServiceItem::Future(
+ Some(path.clone()),
+ service.new_service(&()),
+ )
+ })
+ .collect(),
+ state: self.state.clone(),
+ }
+ }
+}
+
+#[doc(hidden)]
+pub struct CreateService {
+ fut: Vec>,
+ state: State,
+}
+
+enum CreateServiceItem {
+ Future(
+ Option,
+ Box>, Error = ()>>,
+ ),
+ Service(String, BoxedHttpService>),
+}
+
+impl Future for CreateService
+where
+ T: AsyncRead + AsyncWrite,
+{
+ type Item = CloneableService>;
+ type Error = ();
+
+ fn poll(&mut self) -> Poll {
+ let mut done = true;
+
+ // poll http services
+ for item in &mut self.fut {
+ let res = match item {
+ CreateServiceItem::Future(ref mut path, ref mut fut) => {
+ match fut.poll()? {
+ Async::Ready(service) => Some((path.take().unwrap(), service)),
+ Async::NotReady => {
+ done = false;
+ None
+ }
+ }
+ }
+ CreateServiceItem::Service(_, _) => continue,
+ };
+
+ if let Some((path, service)) = res {
+ *item = CreateServiceItem::Service(path, service);
+ }
+ }
+
+ if done {
+ let router = self
+ .fut
+ .drain(..)
+ .fold(Router::build(), |mut router, item| {
+ match item {
+ CreateServiceItem::Service(path, service) => {
+ router.path(&path, service);
+ }
+ CreateServiceItem::Future(_, _) => unreachable!(),
+ }
+ router
+ });
+ Ok(Async::Ready(CloneableService::new(FramedAppService {
+ router: router.finish(),
+ state: self.state.clone(),
+ })))
+ } else {
+ Ok(Async::NotReady)
+ }
+ }
+}
+
+pub struct FramedAppService {
+ state: State,
+ router: Router>>,
+}
+
+impl Service for FramedAppService
+where
+ T: AsyncRead + AsyncWrite,
+{
+ type Request = (Request, Framed);
+ type Response = ();
+ type Error = Error;
+ type Future = BoxedResponse;
+
+ fn poll_ready(&mut self) -> Poll<(), Self::Error> {
+ Ok(Async::Ready(()))
+ }
+
+ fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future {
+ let mut path = Path::new(Url::new(req.uri().clone()));
+
+ if let Some((srv, _info)) = self.router.recognize_mut(&mut path) {
+ return srv.call(FramedRequest::new(req, framed, path, self.state.clone()));
+ }
+ Box::new(
+ SendResponse::new(framed, Response::NotFound().finish()).then(|_| Ok(())),
+ )
+ }
+}
diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs
new file mode 100644
index 000000000..c2c7dbd8b
--- /dev/null
+++ b/actix-framed/src/helpers.rs
@@ -0,0 +1,88 @@
+use actix_http::Error;
+use actix_service::{NewService, Service};
+use futures::{Future, Poll};
+
+pub(crate) type BoxedHttpService = Box<
+ Service<
+ Request = Req,
+ Response = (),
+ Error = Error,
+ Future = Box>,
+ >,
+>;
+
+pub(crate) type BoxedHttpNewService = Box<
+ NewService<
+ Request = Req,
+ Response = (),
+ Error = Error,
+ InitError = (),
+ Service = BoxedHttpService,
+ Future = Box, Error = ()>>,
+ >,
+>;
+
+pub(crate) struct HttpNewService(T);
+
+impl HttpNewService
+where
+ T: NewService,
+ T::Response: 'static,
+ T::Future: 'static,
+ T::Service: Service>> + 'static,
+ ::Future: 'static,
+{
+ pub fn new(service: T) -> Self {
+ HttpNewService(service)
+ }
+}
+
+impl NewService for HttpNewService
+where
+ T: NewService,
+ T::Request: 'static,
+ T::Future: 'static,
+ T::Service: Service>> + 'static,
+ ::Future: 'static,
+{
+ type Request = T::Request;
+ type Response = ();
+ type Error = Error;
+ type InitError = ();
+ type Service = BoxedHttpService;
+ type Future = Box>;
+
+ fn new_service(&self, _: &()) -> Self::Future {
+ Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| {
+ let service: BoxedHttpService<_> = Box::new(HttpServiceWrapper { service });
+ Ok(service)
+ }))
+ }
+}
+
+struct HttpServiceWrapper {
+ service: T,
+}
+
+impl Service for HttpServiceWrapper
+where
+ T: Service<
+ Response = (),
+ Future = Box>,
+ Error = Error,
+ >,
+ T::Request: 'static,
+{
+ type Request = T::Request;
+ type Response = ();
+ type Error = Error;
+ type Future = Box>;
+
+ fn poll_ready(&mut self) -> Poll<(), Self::Error> {
+ self.service.poll_ready()
+ }
+
+ fn call(&mut self, req: Self::Request) -> Self::Future {
+ self.service.call(req)
+ }
+}
diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs
new file mode 100644
index 000000000..c6405e20b
--- /dev/null
+++ b/actix-framed/src/lib.rs
@@ -0,0 +1,16 @@
+mod app;
+mod helpers;
+mod request;
+mod route;
+mod service;
+mod state;
+pub mod test;
+
+// re-export for convinience
+pub use actix_http::{http, Error, HttpMessage, Response, ResponseError};
+
+pub use self::app::{FramedApp, FramedAppService};
+pub use self::request::FramedRequest;
+pub use self::route::FramedRoute;
+pub use self::service::{SendError, VerifyWebSockets};
+pub use self::state::State;
diff --git a/actix-framed/src/request.rs b/actix-framed/src/request.rs
new file mode 100644
index 000000000..bdcdd7026
--- /dev/null
+++ b/actix-framed/src/request.rs
@@ -0,0 +1,170 @@
+use std::cell::{Ref, RefMut};
+
+use actix_codec::Framed;
+use actix_http::http::{HeaderMap, Method, Uri, Version};
+use actix_http::{h1::Codec, Extensions, Request, RequestHead};
+use actix_router::{Path, Url};
+
+use crate::state::State;
+
+pub struct FramedRequest {
+ req: Request,
+ framed: Framed,
+ state: State,
+ pub(crate) path: Path,
+}
+
+impl FramedRequest {
+ pub fn new(
+ req: Request,
+ framed: Framed,
+ path: Path,
+ state: State,
+ ) -> Self {
+ Self {
+ req,
+ framed,
+ state,
+ path,
+ }
+ }
+}
+
+impl FramedRequest {
+ /// Split request into a parts
+ pub fn into_parts(self) -> (Request, Framed, State) {
+ (self.req, self.framed, self.state)
+ }
+
+ /// This method returns reference to the request head
+ #[inline]
+ pub fn head(&self) -> &RequestHead {
+ self.req.head()
+ }
+
+ /// This method returns muttable reference to the request head.
+ /// panics if multiple references of http request exists.
+ #[inline]
+ pub fn head_mut(&mut self) -> &mut RequestHead {
+ self.req.head_mut()
+ }
+
+ /// Shared application state
+ #[inline]
+ pub fn state(&self) -> &S {
+ self.state.get_ref()
+ }
+
+ /// Request's uri.
+ #[inline]
+ pub fn uri(&self) -> &Uri {
+ &self.head().uri
+ }
+
+ /// Read the Request method.
+ #[inline]
+ pub fn method(&self) -> &Method {
+ &self.head().method
+ }
+
+ /// Read the Request Version.
+ #[inline]
+ pub fn version(&self) -> Version {
+ self.head().version
+ }
+
+ #[inline]
+ /// Returns request's headers.
+ pub fn headers(&self) -> &HeaderMap {
+ &self.head().headers
+ }
+
+ /// The target path of this Request.
+ #[inline]
+ pub fn path(&self) -> &str {
+ self.head().uri.path()
+ }
+
+ /// The query string in the URL.
+ ///
+ /// E.g., id=10
+ #[inline]
+ pub fn query_string(&self) -> &str {
+ if let Some(query) = self.uri().query().as_ref() {
+ query
+ } else {
+ ""
+ }
+ }
+
+ /// Get a reference to the Path parameters.
+ ///
+ /// Params is a container for url parameters.
+ /// A variable segment is specified in the form `{identifier}`,
+ /// where the identifier can be used later in a request handler to
+ /// access the matched value for that segment.
+ #[inline]
+ pub fn match_info(&self) -> &Path {
+ &self.path
+ }
+
+ /// Request extensions
+ #[inline]
+ pub fn extensions(&self) -> Ref {
+ self.head().extensions()
+ }
+
+ /// Mutable reference to a the request's extensions
+ #[inline]
+ pub fn extensions_mut(&self) -> RefMut {
+ self.head().extensions_mut()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use actix_http::http::{HeaderName, HeaderValue, HttpTryFrom};
+ use actix_http::test::{TestBuffer, TestRequest};
+
+ use super::*;
+
+ #[test]
+ fn test_reqest() {
+ let buf = TestBuffer::empty();
+ let framed = Framed::new(buf, Codec::default());
+ let req = TestRequest::with_uri("/index.html?q=1")
+ .header("content-type", "test")
+ .finish();
+ let path = Path::new(Url::new(req.uri().clone()));
+
+ let mut freq = FramedRequest::new(req, framed, path, State::new(10u8));
+ assert_eq!(*freq.state(), 10);
+ assert_eq!(freq.version(), Version::HTTP_11);
+ assert_eq!(freq.method(), Method::GET);
+ assert_eq!(freq.path(), "/index.html");
+ assert_eq!(freq.query_string(), "q=1");
+ assert_eq!(
+ freq.headers()
+ .get("content-type")
+ .unwrap()
+ .to_str()
+ .unwrap(),
+ "test"
+ );
+
+ freq.head_mut().headers.insert(
+ HeaderName::try_from("x-hdr").unwrap(),
+ HeaderValue::from_static("test"),
+ );
+ assert_eq!(
+ freq.headers().get("x-hdr").unwrap().to_str().unwrap(),
+ "test"
+ );
+
+ freq.extensions_mut().insert(100usize);
+ assert_eq!(*freq.extensions().get::().unwrap(), 100usize);
+
+ let (_, _, state) = freq.into_parts();
+ assert_eq!(*state, 10);
+ }
+}
diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs
new file mode 100644
index 000000000..c8d9d4326
--- /dev/null
+++ b/actix-framed/src/route.rs
@@ -0,0 +1,156 @@
+use std::fmt;
+use std::marker::PhantomData;
+
+use actix_codec::{AsyncRead, AsyncWrite};
+use actix_http::{http::Method, Error};
+use actix_service::{NewService, Service};
+use futures::future::{ok, FutureResult};
+use futures::{Async, Future, IntoFuture, Poll};
+use log::error;
+
+use crate::app::HttpServiceFactory;
+use crate::request::FramedRequest;
+
+/// Resource route definition
+///
+/// Route uses builder-like pattern for configuration.
+/// If handler is not explicitly set, default *404 Not Found* handler is used.
+pub struct FramedRoute {
+ handler: F,
+ pattern: String,
+ methods: Vec,
+ state: PhantomData<(Io, S, R)>,
+}
+
+impl FramedRoute {
+ pub fn new(pattern: &str) -> Self {
+ FramedRoute {
+ handler: (),
+ pattern: pattern.to_string(),
+ methods: Vec::new(),
+ state: PhantomData,
+ }
+ }
+
+ pub fn get(path: &str) -> FramedRoute {
+ FramedRoute::new(path).method(Method::GET)
+ }
+
+ pub fn post(path: &str) -> FramedRoute {
+ FramedRoute::new(path).method(Method::POST)
+ }
+
+ pub fn put(path: &str) -> FramedRoute {
+ FramedRoute::new(path).method(Method::PUT)
+ }
+
+ pub fn delete(path: &str) -> FramedRoute {
+ FramedRoute::new(path).method(Method::DELETE)
+ }
+
+ pub fn method(mut self, method: Method) -> Self {
+ self.methods.push(method);
+ self
+ }
+
+ pub fn to(self, handler: F) -> FramedRoute
+ where
+ F: FnMut(FramedRequest) -> R,
+ R: IntoFuture- ,
+ R::Future: 'static,
+ R::Error: fmt::Debug,
+ {
+ FramedRoute {
+ handler,
+ pattern: self.pattern,
+ methods: self.methods,
+ state: PhantomData,
+ }
+ }
+}
+
+impl
HttpServiceFactory for FramedRoute
+where
+ Io: AsyncRead + AsyncWrite + 'static,
+ F: FnMut(FramedRequest) -> R + Clone,
+ R: IntoFuture- ,
+ R::Future: 'static,
+ R::Error: fmt::Display,
+{
+ type Factory = FramedRouteFactory
;
+
+ fn path(&self) -> &str {
+ &self.pattern
+ }
+
+ fn create(self) -> Self::Factory {
+ FramedRouteFactory {
+ handler: self.handler,
+ methods: self.methods,
+ _t: PhantomData,
+ }
+ }
+}
+
+pub struct FramedRouteFactory {
+ handler: F,
+ methods: Vec,
+ _t: PhantomData<(Io, S, R)>,
+}
+
+impl NewService for FramedRouteFactory
+where
+ Io: AsyncRead + AsyncWrite + 'static,
+ F: FnMut(FramedRequest) -> R + Clone,
+ R: IntoFuture- ,
+ R::Future: 'static,
+ R::Error: fmt::Display,
+{
+ type Request = FramedRequest
;
+ type Response = ();
+ type Error = Error;
+ type InitError = ();
+ type Service = FramedRouteService;
+ type Future = FutureResult;
+
+ fn new_service(&self, _: &()) -> Self::Future {
+ ok(FramedRouteService {
+ handler: self.handler.clone(),
+ methods: self.methods.clone(),
+ _t: PhantomData,
+ })
+ }
+}
+
+pub struct FramedRouteService {
+ handler: F,
+ methods: Vec,
+ _t: PhantomData<(Io, S, R)>,
+}
+
+impl Service for FramedRouteService
+where
+ Io: AsyncRead + AsyncWrite + 'static,
+ F: FnMut(FramedRequest) -> R + Clone,
+ R: IntoFuture- ,
+ R::Future: 'static,
+ R::Error: fmt::Display,
+{
+ type Request = FramedRequest
;
+ type Response = ();
+ type Error = Error;
+ type Future = Box>;
+
+ fn poll_ready(&mut self) -> Poll<(), Self::Error> {
+ Ok(Async::Ready(()))
+ }
+
+ fn call(&mut self, req: FramedRequest) -> Self::Future {
+ Box::new((self.handler)(req).into_future().then(|res| {
+ if let Err(e) = res {
+ error!("Error in request handler: {}", e);
+ }
+ Ok(())
+ }))
+ }
+}
diff --git a/actix-framed/src/service.rs b/actix-framed/src/service.rs
new file mode 100644
index 000000000..6e5c7a543
--- /dev/null
+++ b/actix-framed/src/service.rs
@@ -0,0 +1,147 @@
+use std::marker::PhantomData;
+
+use actix_codec::{AsyncRead, AsyncWrite, Framed};
+use actix_http::body::BodySize;
+use actix_http::error::ResponseError;
+use actix_http::h1::{Codec, Message};
+use actix_http::ws::{verify_handshake, HandshakeError};
+use actix_http::{Request, Response};
+use actix_service::{NewService, Service};
+use futures::future::{ok, Either, FutureResult};
+use futures::{Async, Future, IntoFuture, Poll, Sink};
+
+/// Service that verifies incoming request if it is valid websocket
+/// upgrade request. In case of error returns `HandshakeError`
+pub struct VerifyWebSockets {
+ _t: PhantomData,
+}
+
+impl Default for VerifyWebSockets {
+ fn default() -> Self {
+ VerifyWebSockets { _t: PhantomData }
+ }
+}
+
+impl NewService for VerifyWebSockets {
+ type Request = (Request, Framed);
+ type Response = (Request, Framed);
+ type Error = (HandshakeError, Framed);
+ type InitError = ();
+ type Service = VerifyWebSockets;
+ type Future = FutureResult;
+
+ fn new_service(&self, _: &C) -> Self::Future {
+ ok(VerifyWebSockets { _t: PhantomData })
+ }
+}
+
+impl Service for VerifyWebSockets {
+ type Request = (Request, Framed);
+ type Response = (Request, Framed);
+ type Error = (HandshakeError, Framed);
+ type Future = FutureResult;
+
+ fn poll_ready(&mut self) -> Poll<(), Self::Error> {
+ Ok(Async::Ready(()))
+ }
+
+ fn call(&mut self, (req, framed): (Request, Framed)) -> Self::Future {
+ match verify_handshake(req.head()) {
+ Err(e) => Err((e, framed)).into_future(),
+ Ok(_) => Ok((req, framed)).into_future(),
+ }
+ }
+}
+
+/// Send http/1 error response
+pub struct SendError(PhantomData<(T, R, E)>);
+
+impl Default for SendError
+where
+ T: AsyncRead + AsyncWrite,
+ E: ResponseError,
+{
+ fn default() -> Self {
+ SendError(PhantomData)
+ }
+}
+
+impl NewService for SendError
+where
+ T: AsyncRead + AsyncWrite + 'static,
+ R: 'static,
+ E: ResponseError + 'static,
+{
+ type Request = Result)>;
+ type Response = R;
+ type Error = (E, Framed);
+ type InitError = ();
+ type Service = SendError;
+ type Future = FutureResult;
+
+ fn new_service(&self, _: &C) -> Self::Future {
+ ok(SendError(PhantomData))
+ }
+}
+
+impl Service for SendError
+where
+ T: AsyncRead + AsyncWrite + 'static,
+ R: 'static,
+ E: ResponseError + 'static,
+{
+ type Request = Result)>;
+ type Response = R;
+ type Error = (E, Framed);
+ type Future = Either)>, SendErrorFut>;
+
+ fn poll_ready(&mut self) -> Poll<(), Self::Error> {
+ Ok(Async::Ready(()))
+ }
+
+ fn call(&mut self, req: Result)>) -> Self::Future {
+ match req {
+ Ok(r) => Either::A(ok(r)),
+ Err((e, framed)) => {
+ let res = e.error_response().drop_body();
+ Either::B(SendErrorFut {
+ framed: Some(framed),
+ res: Some((res, BodySize::Empty).into()),
+ err: Some(e),
+ _t: PhantomData,
+ })
+ }
+ }
+ }
+}
+
+pub struct SendErrorFut {
+ res: Option, BodySize)>>,
+ framed: Option>,
+ err: Option,
+ _t: PhantomData,
+}
+
+impl Future for SendErrorFut
+where
+ E: ResponseError,
+ T: AsyncRead + AsyncWrite,
+{
+ type Item = R;
+ type Error = (E, Framed);
+
+ fn poll(&mut self) -> Poll {
+ if let Some(res) = self.res.take() {
+ if self.framed.as_mut().unwrap().force_send(res).is_err() {
+ return Err((self.err.take().unwrap(), self.framed.take().unwrap()));
+ }
+ }
+ match self.framed.as_mut().unwrap().poll_complete() {
+ Ok(Async::Ready(_)) => {
+ Err((self.err.take().unwrap(), self.framed.take().unwrap()))
+ }
+ Ok(Async::NotReady) => Ok(Async::NotReady),
+ Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())),
+ }
+ }
+}
diff --git a/actix-framed/src/state.rs b/actix-framed/src/state.rs
new file mode 100644
index 000000000..600a639ca
--- /dev/null
+++ b/actix-framed/src/state.rs
@@ -0,0 +1,29 @@
+use std::ops::Deref;
+use std::sync::Arc;
+
+/// Application state
+pub struct State(Arc);
+
+impl State {
+ pub fn new(state: S) -> State {
+ State(Arc::new(state))
+ }
+
+ pub fn get_ref(&self) -> &S {
+ self.0.as_ref()
+ }
+}
+
+impl Deref for State {
+ type Target = S;
+
+ fn deref(&self) -> &S {
+ self.0.as_ref()
+ }
+}
+
+impl Clone for State {
+ fn clone(&self) -> State {
+ State(self.0.clone())
+ }
+}
diff --git a/actix-framed/src/test.rs b/actix-framed/src/test.rs
new file mode 100644
index 000000000..3bc828df4
--- /dev/null
+++ b/actix-framed/src/test.rs
@@ -0,0 +1,153 @@
+//! Various helpers for Actix applications to use during testing.
+use actix_codec::Framed;
+use actix_http::h1::Codec;
+use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
+use actix_http::http::{HttpTryFrom, Method, Uri, Version};
+use actix_http::test::{TestBuffer, TestRequest as HttpTestRequest};
+use actix_router::{Path, Url};
+use actix_rt::Runtime;
+use futures::IntoFuture;
+
+use crate::{FramedRequest, State};
+
+/// Test `Request` builder.
+pub struct TestRequest {
+ req: HttpTestRequest,
+ path: Path,
+ state: State,
+}
+
+impl Default for TestRequest<()> {
+ fn default() -> TestRequest {
+ TestRequest {
+ req: HttpTestRequest::default(),
+ path: Path::new(Url::new(Uri::default())),
+ state: State::new(()),
+ }
+ }
+}
+
+impl TestRequest<()> {
+ /// Create TestRequest and set request uri
+ pub fn with_uri(path: &str) -> Self {
+ Self::get().uri(path)
+ }
+
+ /// Create TestRequest and set header
+ pub fn with_hdr(hdr: H) -> Self {
+ Self::default().set(hdr)
+ }
+
+ /// Create TestRequest and set header
+ pub fn with_header(key: K, value: V) -> Self
+ where
+ HeaderName: HttpTryFrom,
+ V: IntoHeaderValue,
+ {
+ Self::default().header(key, value)
+ }
+
+ /// Create TestRequest and set method to `Method::GET`
+ pub fn get() -> Self {
+ Self::default().method(Method::GET)
+ }
+
+ /// Create TestRequest and set method to `Method::POST`
+ pub fn post() -> Self {
+ Self::default().method(Method::POST)
+ }
+}
+
+impl TestRequest {
+ /// Create TestRequest and set request uri
+ pub fn with_state(state: S) -> TestRequest {
+ let req = TestRequest::get();
+ TestRequest {
+ state: State::new(state),
+ req: req.req,
+ path: req.path,
+ }
+ }
+
+ /// Set HTTP version of this request
+ pub fn version(mut self, ver: Version) -> Self {
+ self.req.version(ver);
+ self
+ }
+
+ /// Set HTTP method of this request
+ pub fn method(mut self, meth: Method) -> Self {
+ self.req.method(meth);
+ self
+ }
+
+ /// Set HTTP Uri of this request
+ pub fn uri(mut self, path: &str) -> Self {
+ self.req.uri(path);
+ self
+ }
+
+ /// Set a header
+ pub fn set(mut self, hdr: H) -> Self {
+ self.req.set(hdr);
+ self
+ }
+
+ /// Set a header
+ pub fn header(mut self, key: K, value: V) -> Self
+ where
+ HeaderName: HttpTryFrom,
+ V: IntoHeaderValue,
+ {
+ self.req.header(key, value);
+ self
+ }
+
+ /// Set request path pattern parameter
+ pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
+ self.path.add_static(name, value);
+ self
+ }
+
+ /// Complete request creation and generate `Request` instance
+ pub fn finish(mut self) -> FramedRequest {
+ let req = self.req.finish();
+ self.path.get_mut().update(req.uri());
+ let framed = Framed::new(TestBuffer::empty(), Codec::default());
+ FramedRequest::new(req, framed, self.path, self.state)
+ }
+
+ /// This method generates `FramedRequest` instance and executes async handler
+ pub fn run(self, f: F) -> Result
+ where
+ F: FnOnce(FramedRequest) -> R,
+ R: IntoFuture- ,
+ {
+ let mut rt = Runtime::new().unwrap();
+ rt.block_on(f(self.finish()).into_future())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test() {
+ let req = TestRequest::with_uri("/index.html")
+ .header("x-test", "test")
+ .param("test", "123")
+ .finish();
+
+ assert_eq!(*req.state(), ());
+ assert_eq!(req.version(), Version::HTTP_11);
+ assert_eq!(req.method(), Method::GET);
+ assert_eq!(req.path(), "/index.html");
+ assert_eq!(req.query_string(), "");
+ assert_eq!(
+ req.headers().get("x-test").unwrap().to_str().unwrap(),
+ "test"
+ );
+ assert_eq!(&req.match_info()["test"], "123");
+ }
+}
diff --git a/actix-framed/tests/test_server.rs b/actix-framed/tests/test_server.rs
new file mode 100644
index 000000000..00f6a97d8
--- /dev/null
+++ b/actix-framed/tests/test_server.rs
@@ -0,0 +1,141 @@
+use actix_codec::{AsyncRead, AsyncWrite};
+use actix_http::{body, http::StatusCode, ws, Error, HttpService, Response};
+use actix_http_test::TestServer;
+use actix_service::{IntoNewService, NewService};
+use actix_utils::framed::FramedTransport;
+use bytes::{Bytes, BytesMut};
+use futures::future::{self, ok};
+use futures::{Future, Sink, Stream};
+
+use actix_framed::{FramedApp, FramedRequest, FramedRoute, SendError, VerifyWebSockets};
+
+fn ws_service
(
+ req: FramedRequest,
+) -> impl Future- {
+ let (req, framed, _) = req.into_parts();
+ let res = ws::handshake(req.head()).unwrap().message_body(());
+
+ framed
+ .send((res, body::BodySize::None).into())
+ .map_err(|_| panic!())
+ .and_then(|framed| {
+ FramedTransport::new(framed.into_framed(ws::Codec::new()), service)
+ .map_err(|_| panic!())
+ })
+}
+
+fn service(msg: ws::Frame) -> impl Future
- {
+ let msg = match msg {
+ ws::Frame::Ping(msg) => ws::Message::Pong(msg),
+ ws::Frame::Text(text) => {
+ ws::Message::Text(String::from_utf8_lossy(&text.unwrap()).to_string())
+ }
+ ws::Frame::Binary(bin) => ws::Message::Binary(bin.unwrap().freeze()),
+ ws::Frame::Close(reason) => ws::Message::Close(reason),
+ _ => panic!(),
+ };
+ ok(msg)
+}
+
+#[test]
+fn test_simple() {
+ let mut srv = TestServer::new(|| {
+ HttpService::build()
+ .upgrade(
+ FramedApp::new().service(FramedRoute::get("/index.html").to(ws_service)),
+ )
+ .finish(|_| future::ok::<_, Error>(Response::NotFound()))
+ });
+
+ assert!(srv.ws_at("/test").is_err());
+
+ // client service
+ let framed = srv.ws_at("/index.html").unwrap();
+ let framed = srv
+ .block_on(framed.send(ws::Message::Text("text".to_string())))
+ .unwrap();
+ let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
+
+ let framed = srv
+ .block_on(framed.send(ws::Message::Binary("text".into())))
+ .unwrap();
+ let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(
+ item,
+ Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
+ );
+
+ let framed = srv
+ .block_on(framed.send(ws::Message::Ping("text".into())))
+ .unwrap();
+ let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
+
+ let framed = srv
+ .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
+ .unwrap();
+
+ let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(
+ item,
+ Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
+ );
+}
+
+#[test]
+fn test_service() {
+ let mut srv = TestServer::new(|| {
+ actix_http::h1::OneRequest::new().map_err(|_| ()).and_then(
+ VerifyWebSockets::default()
+ .then(SendError::default())
+ .map_err(|_| ())
+ .and_then(
+ FramedApp::new()
+ .service(FramedRoute::get("/index.html").to(ws_service))
+ .into_new_service()
+ .map_err(|_| ()),
+ ),
+ )
+ });
+
+ // non ws request
+ let res = srv.block_on(srv.get("/index.html").send()).unwrap();
+ assert_eq!(res.status(), StatusCode::BAD_REQUEST);
+
+ // not found
+ assert!(srv.ws_at("/test").is_err());
+
+ // client service
+ let framed = srv.ws_at("/index.html").unwrap();
+ let framed = srv
+ .block_on(framed.send(ws::Message::Text("text".to_string())))
+ .unwrap();
+ let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(item, Some(ws::Frame::Text(Some(BytesMut::from("text")))));
+
+ let framed = srv
+ .block_on(framed.send(ws::Message::Binary("text".into())))
+ .unwrap();
+ let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(
+ item,
+ Some(ws::Frame::Binary(Some(Bytes::from_static(b"text").into())))
+ );
+
+ let framed = srv
+ .block_on(framed.send(ws::Message::Ping("text".into())))
+ .unwrap();
+ let (item, framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(item, Some(ws::Frame::Pong("text".to_string().into())));
+
+ let framed = srv
+ .block_on(framed.send(ws::Message::Close(Some(ws::CloseCode::Normal.into()))))
+ .unwrap();
+
+ let (item, _framed) = srv.block_on(framed.into_future()).map_err(|_| ()).unwrap();
+ assert_eq!(
+ item,
+ Some(ws::Frame::Close(Some(ws::CloseCode::Normal.into())))
+ );
+}
diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md
index 79995b77f..37d0eec65 100644
--- a/actix-http/CHANGES.md
+++ b/actix-http/CHANGES.md
@@ -1,5 +1,81 @@
# Changes
+## [0.1.3] - 2019-04-23
+
+### Fixed
+
+* Fix http client pool management
+
+* Fix http client wait queue management #794
+
+
+## [0.1.2] - 2019-04-23
+
+### Fixed
+
+* Fix BorrowMutError panic in client connector #793
+
+
+## [0.1.1] - 2019-04-19
+
+### Changes
+
+* Cookie::max_age() accepts value in seconds
+
+* Cookie::max_age_time() accepts value in time::Duration
+
+* Allow to specify server address for client connector
+
+
+## [0.1.0] - 2019-04-16
+
+### Added
+
+* Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr`
+
+### Changed
+
+* `actix_http::encoding` always available
+
+* use trust-dns-resolver 0.11.0
+
+
+## [0.1.0-alpha.5] - 2019-04-12
+
+### Added
+
+* Allow to use custom service for upgrade requests
+
+* Added `h1::SendResponse` future.
+
+### Changed
+
+* MessageBody::length() renamed to MessageBody::size() for consistency
+
+* ws handshake verification functions take RequestHead instead of Request
+
+
+## [0.1.0-alpha.4] - 2019-04-08
+
+### Added
+
+* Allow to use custom `Expect` handler
+
+* Add minimal `std::error::Error` impl for `Error`
+
+### Changed
+
+* Export IntoHeaderValue
+
+* Render error and return as response body
+
+* Use thread pool for response body comression
+
+### Deleted
+
+* Removed PayloadBuffer
+
+
## [0.1.0-alpha.3] - 2019-04-02
### Added
diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml
index 9d4e15216..438754b3f 100644
--- a/actix-http/Cargo.toml
+++ b/actix-http/Cargo.toml
@@ -1,12 +1,12 @@
[package]
name = "actix-http"
-version = "0.1.0-alpha.3"
+version = "0.1.3"
authors = ["Nikolay Kim
"]
description = "Actix http primitives"
readme = "README.md"
keywords = ["actix", "http", "framework", "async", "futures"]
homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-http.git"
+repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-http/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server",
@@ -18,10 +18,6 @@ workspace = ".."
[package.metadata.docs.rs]
features = ["ssl", "fail", "brotli", "flate2-zlib", "secure-cookies"]
-[badges]
-travis-ci = { repository = "actix/actix-web", branch = "master" }
-codecov = { repository = "actix/actix-web", branch = "master", service = "github" }
-
[lib]
name = "actix_http"
path = "src/lib.rs"
@@ -48,23 +44,25 @@ fail = ["failure"]
secure-cookies = ["ring"]
[dependencies]
-actix-service = "0.3.4"
+actix-service = "0.3.6"
actix-codec = "0.1.2"
-actix-connect = "0.1.0"
-actix-utils = "0.3.4"
-actix-server-config = "0.1.0"
+actix-connect = "0.1.5"
+actix-utils = "0.3.5"
+actix-server-config = "0.1.1"
actix-threadpool = "0.1.0"
base64 = "0.10"
bitflags = "1.0"
bytes = "0.4"
byteorder = "1.2"
+copyless = "0.1.2"
derive_more = "0.14"
+either = "1.5.2"
encoding = "0.2"
futures = "0.1"
-hashbrown = "0.1.8"
+hashbrown = "0.2.2"
h2 = "0.1.16"
-http = "0.1.16"
+http = "0.1.17"
httparse = "1.3"
indexmap = "1.0"
lazy_static = "1.0"
@@ -78,29 +76,30 @@ serde = "1.0"
serde_json = "1.0"
sha1 = "0.6"
slab = "0.4"
-serde_urlencoded = "0.5.3"
-time = "0.1"
+serde_urlencoded = "0.5.5"
+time = "0.1.42"
tokio-tcp = "0.1.3"
-tokio-timer = "0.2"
+tokio-timer = "0.2.8"
tokio-current-thread = "0.1"
-trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false }
+trust-dns-resolver = { version="0.11.0", default-features = false }
# for secure cookie
ring = { version = "0.14.6", optional = true }
# compression
-brotli2 = { version="^0.3.2", optional = true }
-flate2 = { version="^1.0.2", optional = true, default-features = false }
+brotli2 = { version="0.3.2", optional = true }
+flate2 = { version="1.0.7", optional = true, default-features = false }
# optional deps
failure = { version = "0.1.5", optional = true }
openssl = { version="0.10", optional = true }
+chrono = "0.4.6"
[dev-dependencies]
actix-rt = "0.2.2"
-actix-server = { version = "0.4.1", features=["ssl"] }
-actix-connect = { version = "0.1.0", features=["ssl"] }
-actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
+actix-server = { version = "0.4.3", features=["ssl"] }
+actix-connect = { version = "0.1.4", features=["ssl"] }
+actix-http-test = { version = "0.1.0", features=["ssl"] }
env_logger = "0.6"
serde_derive = "1.0"
openssl = { version="0.10" }
diff --git a/actix-http/README.md b/actix-http/README.md
index 467e67a9d..d75e822ba 100644
--- a/actix-http/README.md
+++ b/actix-http/README.md
@@ -1,4 +1,4 @@
-# Actix http [](https://travis-ci.org/actix/actix-http) [](https://codecov.io/gh/actix/actix-http) [](https://crates.io/crates/actix-web) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+# Actix http [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-http) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Actix http
@@ -8,7 +8,7 @@ Actix http
* [API Documentation](https://docs.rs/actix-http/)
* [Chat on gitter](https://gitter.im/actix/actix)
* Cargo package: [actix-http](https://crates.io/crates/actix-http)
-* Minimum supported Rust version: 1.26 or later
+* Minimum supported Rust version: 1.31 or later
## Example
diff --git a/actix-http/examples/framed_hello.rs b/actix-http/examples/framed_hello.rs
deleted file mode 100644
index 7d4c13d34..000000000
--- a/actix-http/examples/framed_hello.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::{env, io};
-
-use actix_codec::Framed;
-use actix_http::{h1, Response, SendResponse, ServiceConfig};
-use actix_server::{Io, Server};
-use actix_service::{fn_service, NewService};
-use actix_utils::framed::IntoFramed;
-use actix_utils::stream::TakeItem;
-use futures::Future;
-use tokio_tcp::TcpStream;
-
-fn main() -> io::Result<()> {
- env::set_var("RUST_LOG", "framed_hello=info");
- env_logger::init();
-
- Server::build()
- .bind("framed_hello", "127.0.0.1:8080", || {
- fn_service(|io: Io| Ok(io.into_parts().0))
- .and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())))
- .and_then(TakeItem::new().map_err(|_| ()))
- .and_then(|(_req, _framed): (_, Framed<_, _>)| {
- SendResponse::send(_framed, Response::Ok().body("Hello world!"))
- .map_err(|_| ())
- .map(|_| ())
- })
- })?
- .run()
-}
diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs
index 85717ba85..0652dd274 100644
--- a/actix-http/src/body.rs
+++ b/actix-http/src/body.rs
@@ -30,13 +30,13 @@ impl BodySize {
/// Type that provides this trait can be streamed to a peer.
pub trait MessageBody {
- fn length(&self) -> BodySize;
+ fn size(&self) -> BodySize;
fn poll_next(&mut self) -> Poll, Error>;
}
impl MessageBody for () {
- fn length(&self) -> BodySize {
+ fn size(&self) -> BodySize {
BodySize::Empty
}
@@ -46,8 +46,8 @@ impl MessageBody for () {
}
impl MessageBody for Box {
- fn length(&self) -> BodySize {
- self.as_ref().length()
+ fn size(&self) -> BodySize {
+ self.as_ref().size()
}
fn poll_next(&mut self) -> Poll, Error> {
@@ -86,10 +86,10 @@ impl ResponseBody