mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-26 18:42:23 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ed9971b212 | ||
|
75861a21ae | ||
|
40b01df846 | ||
|
8495e92660 | ||
|
2e7d323e1a | ||
|
b66566f610 | ||
|
2477afcf30 | ||
|
bcd03a9c62 | ||
|
f8af3ef7f4 | ||
|
f89b7a9bb8 | ||
|
59244b203c | ||
|
2adf8a3a48 | ||
|
805dbea8e7 | ||
|
dc9a24a189 | ||
|
5528cf62f0 | ||
|
9880a95603 | ||
|
2579c49865 | ||
|
01a0f3f5a0 | ||
|
2c8d987241 | ||
|
813d1d6e66 | ||
|
48b02abee7 | ||
|
ce1081432b | ||
|
e9bdba57a0 | ||
|
f907be585e | ||
|
022f9800ed | ||
|
a9a54ac4c6 | ||
|
50b9fee3a7 | ||
|
bf9a90293f | ||
|
17ec3a3a26 | ||
|
5b4b885fd6 | ||
|
65b8197876 | ||
|
a826d113ee | ||
|
3a79505a44 | ||
|
5f3a7a6a52 | ||
|
6a7b097bcf |
@@ -8,7 +8,6 @@ cache:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- rust: 1.21.0
|
|
||||||
- rust: stable
|
- rust: stable
|
||||||
- rust: beta
|
- rust: beta
|
||||||
- rust: nightly
|
- rust: nightly
|
||||||
|
34
CHANGES.md
34
CHANGES.md
@@ -1,5 +1,39 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## 0.5.8 (2018-05-11)
|
||||||
|
|
||||||
|
* Fix segfault in ServerSettings::get_response_builder()
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.7 (2018-05-09)
|
||||||
|
|
||||||
|
* Fix http/2 payload streaming #215
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.6 (2018-04-24)
|
||||||
|
|
||||||
|
* Make flate2 crate optional #200
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.5 (2018-04-24)
|
||||||
|
|
||||||
|
* Fix panic when Websocket is closed with no error code #191
|
||||||
|
|
||||||
|
* Allow to use rust backend for flate2 crate #199
|
||||||
|
|
||||||
|
## 0.5.4 (2018-04-19)
|
||||||
|
|
||||||
|
* Add identity service middleware
|
||||||
|
|
||||||
|
* Middleware response() is not invoked if there was an error in async handler #187
|
||||||
|
|
||||||
|
* Use Display formatting for InternalError Display implementation #188
|
||||||
|
|
||||||
|
|
||||||
|
## 0.5.3 (2018-04-18)
|
||||||
|
|
||||||
|
* Impossible to quote slashes in path parameters #182
|
||||||
|
|
||||||
|
|
||||||
## 0.5.2 (2018-04-16)
|
## 0.5.2 (2018-04-16)
|
||||||
|
|
||||||
|
17
Cargo.toml
17
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "0.5.2"
|
version = "0.5.8"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -26,7 +26,7 @@ name = "actix_web"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["session", "brotli"]
|
default = ["session", "brotli", "flate2-c"]
|
||||||
|
|
||||||
# tls
|
# tls
|
||||||
tls = ["native-tls", "tokio-tls"]
|
tls = ["native-tls", "tokio-tls"]
|
||||||
@@ -34,19 +34,24 @@ tls = ["native-tls", "tokio-tls"]
|
|||||||
# openssl
|
# openssl
|
||||||
alpn = ["openssl", "tokio-openssl"]
|
alpn = ["openssl", "tokio-openssl"]
|
||||||
|
|
||||||
# sessions
|
# sessions feature, session require "ring" crate and c compiler
|
||||||
session = ["cookie/secure"]
|
session = ["cookie/secure"]
|
||||||
|
|
||||||
# brotli encoding
|
# brotli encoding, requires c compiler
|
||||||
brotli = ["brotli2"]
|
brotli = ["brotli2"]
|
||||||
|
|
||||||
|
# miniz-sys backend for flate2 crate
|
||||||
|
flate2-c = ["flate2/miniz-sys"]
|
||||||
|
|
||||||
|
# rust backend for flate2 crate
|
||||||
|
flate2-rust = ["flate2/rust_backend"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix = "^0.5.5"
|
actix = "^0.5.5"
|
||||||
|
|
||||||
base64 = "0.9"
|
base64 = "0.9"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
failure = "0.1.1"
|
failure = "0.1.1"
|
||||||
flate2 = "1.0"
|
|
||||||
h2 = "0.1"
|
h2 = "0.1"
|
||||||
http = "^0.1.5"
|
http = "^0.1.5"
|
||||||
httparse = "1.2"
|
httparse = "1.2"
|
||||||
@@ -71,6 +76,7 @@ lazy_static = "1.0"
|
|||||||
url = { version="1.7", features=["query_encoding"] }
|
url = { version="1.7", features=["query_encoding"] }
|
||||||
cookie = { version="0.10", features=["percent-encode"] }
|
cookie = { version="0.10", features=["percent-encode"] }
|
||||||
brotli2 = { version="^0.3.2", optional = true }
|
brotli2 = { version="^0.3.2", optional = true }
|
||||||
|
flate2 = { version="1.0", optional = true, default-features = false }
|
||||||
|
|
||||||
# io
|
# io
|
||||||
mio = "^0.6.13"
|
mio = "^0.6.13"
|
||||||
@@ -81,7 +87,6 @@ futures = "0.1"
|
|||||||
futures-cpupool = "0.1"
|
futures-cpupool = "0.1"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
tokio-core = "0.1"
|
tokio-core = "0.1"
|
||||||
trust-dns-resolver = "0.8"
|
|
||||||
|
|
||||||
# native-tls
|
# native-tls
|
||||||
native-tls = { version="0.1", optional = true }
|
native-tls = { version="0.1", optional = true }
|
||||||
|
12
README.md
12
README.md
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
||||||
|
|
||||||
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/actix-web/guide/qs_13.html) protocols
|
* Supported *HTTP/1.x* and [*HTTP/2.0*](https://actix.rs/book/actix-web/sec-12-http2.html) protocols
|
||||||
* Streaming and pipelining
|
* Streaming and pipelining
|
||||||
* Keep-alive and slow requests handling
|
* Keep-alive and slow requests handling
|
||||||
* Client/server [WebSockets](https://actix.rs/actix-web/guide/qs_9.html) support
|
* Client/server [WebSockets](https://actix.rs/book/actix-web/sec-11-websockets.html) support
|
||||||
* Transparent content compression/decompression (br, gzip, deflate)
|
* Transparent content compression/decompression (br, gzip, deflate)
|
||||||
* Configurable [request routing](https://actix.rs/actix-web/guide/qs_5.html)
|
* Configurable [request routing](https://actix.rs/book/actix-web/sec-6-url-dispatch.html)
|
||||||
* Graceful server shutdown
|
* Graceful server shutdown
|
||||||
* Multipart streams
|
* Multipart streams
|
||||||
* Static assets
|
* Static assets
|
||||||
@@ -54,9 +54,11 @@ fn main() {
|
|||||||
* [Stateful](https://github.com/actix/examples/tree/master/state/)
|
* [Stateful](https://github.com/actix/examples/tree/master/state/)
|
||||||
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
* [Protobuf support](https://github.com/actix/examples/tree/master/protobuf/)
|
||||||
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
* [Multipart streams](https://github.com/actix/examples/tree/master/multipart/)
|
||||||
* [Simple websocket session](https://github.com/actix/examples/tree/master/websocket/)
|
* [Simple websocket](https://github.com/actix/examples/tree/master/websocket/)
|
||||||
* [Tera templates](https://github.com/actix/examples/tree/master/template_tera/)
|
* [Tera](https://github.com/actix/examples/tree/master/template_tera/) /
|
||||||
|
[Askama](https://github.com/actix/examples/tree/master/template_askama/) templates
|
||||||
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
* [Diesel integration](https://github.com/actix/examples/tree/master/diesel/)
|
||||||
|
* [r2d2](https://github.com/actix/examples/tree/master/r2d2/)
|
||||||
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
|
* [SSL / HTTP/2.0](https://github.com/actix/examples/tree/master/tls/)
|
||||||
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
|
* [Tcp/Websocket chat](https://github.com/actix/examples/tree/master/websocket-chat/)
|
||||||
* [Json](https://github.com/actix/examples/tree/master/json/)
|
* [Json](https://github.com/actix/examples/tree/master/json/)
|
||||||
|
@@ -177,6 +177,13 @@ where
|
|||||||
///
|
///
|
||||||
/// State is shared with all resources within same application and
|
/// State is shared with all resources within same application and
|
||||||
/// could be accessed with `HttpRequest::state()` method.
|
/// could be accessed with `HttpRequest::state()` method.
|
||||||
|
///
|
||||||
|
/// **Note**: http server accepts an application factory rather than
|
||||||
|
/// an application instance. Http server constructs an application
|
||||||
|
/// instance for each thread, thus application state must be constructed
|
||||||
|
/// multiple times. If you want to share state between different
|
||||||
|
/// threads, a shared object should be used, e.g. `Arc`. Application
|
||||||
|
/// state does not need to be `Send` and `Sync`.
|
||||||
pub fn with_state(state: S) -> App<S> {
|
pub fn with_state(state: S) -> App<S> {
|
||||||
App {
|
App {
|
||||||
parts: Some(ApplicationParts {
|
parts: Some(ApplicationParts {
|
||||||
@@ -314,7 +321,7 @@ where
|
|||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .resource("/test", |r| {
|
/// .resource("/users/{userid}/{friend}", |r| {
|
||||||
/// r.get().f(|_| HttpResponse::Ok());
|
/// r.get().f(|_| HttpResponse::Ok());
|
||||||
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
||||||
/// });
|
/// });
|
||||||
@@ -418,6 +425,8 @@ where
|
|||||||
/// `/app/test` would match, but the path `/application` would
|
/// `/app/test` would match, but the path `/application` would
|
||||||
/// not.
|
/// not.
|
||||||
///
|
///
|
||||||
|
/// Path tail is available as `tail` parameter in request's match_dict.
|
||||||
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// use actix_web::{http, App, HttpRequest, HttpResponse};
|
/// use actix_web::{http, App, HttpRequest, HttpResponse};
|
||||||
|
@@ -7,8 +7,10 @@ use std::io::{self, Write};
|
|||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
use brotli2::write::BrotliEncoder;
|
use brotli2::write::BrotliEncoder;
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use flate2::Compression;
|
#[cfg(feature = "flate2")]
|
||||||
use flate2::write::{DeflateEncoder, GzEncoder};
|
use flate2::write::{DeflateEncoder, GzEncoder};
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
|
use flate2::Compression;
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE,
|
use http::header::{HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE,
|
||||||
TRANSFER_ENCODING};
|
TRANSFER_ENCODING};
|
||||||
@@ -18,9 +20,9 @@ use tokio_io::AsyncWrite;
|
|||||||
|
|
||||||
use body::{Binary, Body};
|
use body::{Binary, Body};
|
||||||
use header::ContentEncoding;
|
use header::ContentEncoding;
|
||||||
use server::WriterState;
|
|
||||||
use server::encoding::{ContentEncoder, TransferEncoding};
|
use server::encoding::{ContentEncoder, TransferEncoding};
|
||||||
use server::shared::SharedBytes;
|
use server::shared::SharedBytes;
|
||||||
|
use server::WriterState;
|
||||||
|
|
||||||
use client::ClientRequest;
|
use client::ClientRequest;
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ impl HttpClientWriter {
|
|||||||
// !self.flags.contains(Flags::UPGRADE) }
|
// !self.flags.contains(Flags::UPGRADE) }
|
||||||
|
|
||||||
fn write_to_stream<T: AsyncWrite>(
|
fn write_to_stream<T: AsyncWrite>(
|
||||||
&mut self, stream: &mut T
|
&mut self, stream: &mut T,
|
||||||
) -> io::Result<WriterState> {
|
) -> io::Result<WriterState> {
|
||||||
while !self.buffer.is_empty() {
|
while !self.buffer.is_empty() {
|
||||||
match stream.write(self.buffer.as_ref()) {
|
match stream.write(self.buffer.as_ref()) {
|
||||||
@@ -191,7 +193,7 @@ impl HttpClientWriter {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn poll_completed<T: AsyncWrite>(
|
pub fn poll_completed<T: AsyncWrite>(
|
||||||
&mut self, stream: &mut T, shutdown: bool
|
&mut self, stream: &mut T, shutdown: bool,
|
||||||
) -> Poll<(), io::Error> {
|
) -> Poll<(), io::Error> {
|
||||||
match self.write_to_stream(stream) {
|
match self.write_to_stream(stream) {
|
||||||
Ok(WriterState::Done) => {
|
Ok(WriterState::Done) => {
|
||||||
@@ -222,9 +224,11 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
|||||||
let tmp = SharedBytes::default();
|
let tmp = SharedBytes::default();
|
||||||
let transfer = TransferEncoding::eof(tmp.clone());
|
let transfer = TransferEncoding::eof(tmp.clone());
|
||||||
let mut enc = match encoding {
|
let mut enc = match encoding {
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||||
DeflateEncoder::new(transfer, Compression::default()),
|
DeflateEncoder::new(transfer, Compression::default()),
|
||||||
),
|
),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||||
transfer,
|
transfer,
|
||||||
Compression::default(),
|
Compression::default(),
|
||||||
@@ -283,10 +287,12 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
|||||||
|
|
||||||
req.replace_body(body);
|
req.replace_body(body);
|
||||||
match encoding {
|
match encoding {
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
||||||
transfer,
|
transfer,
|
||||||
Compression::default(),
|
Compression::default(),
|
||||||
)),
|
)),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => {
|
ContentEncoding::Gzip => {
|
||||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
|
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::default()))
|
||||||
}
|
}
|
||||||
@@ -299,7 +305,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn streaming_encoding(
|
fn streaming_encoding(
|
||||||
buf: SharedBytes, version: Version, req: &mut ClientRequest
|
buf: SharedBytes, version: Version, req: &mut ClientRequest,
|
||||||
) -> TransferEncoding {
|
) -> TransferEncoding {
|
||||||
if req.chunked() {
|
if req.chunked() {
|
||||||
// Enable transfer encoding
|
// Enable transfer encoding
|
||||||
|
36
src/error.rs
36
src/error.rs
@@ -580,7 +580,7 @@ impl<T> InternalError<T> {
|
|||||||
|
|
||||||
impl<T> Fail for InternalError<T>
|
impl<T> Fail for InternalError<T>
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
fn backtrace(&self) -> Option<&Backtrace> {
|
fn backtrace(&self) -> Option<&Backtrace> {
|
||||||
Some(&self.backtrace)
|
Some(&self.backtrace)
|
||||||
@@ -598,16 +598,16 @@ where
|
|||||||
|
|
||||||
impl<T> fmt::Display for InternalError<T>
|
impl<T> fmt::Display for InternalError<T>
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt::Debug::fmt(&self.cause, f)
|
fmt::Display::fmt(&self.cause, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ResponseError for InternalError<T>
|
impl<T> ResponseError for InternalError<T>
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
match self.status {
|
match self.status {
|
||||||
@@ -625,7 +625,7 @@ where
|
|||||||
|
|
||||||
impl<T> Responder for InternalError<T>
|
impl<T> Responder for InternalError<T>
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
type Item = HttpResponse;
|
type Item = HttpResponse;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
@@ -640,7 +640,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorBadRequest<T>(err: T) -> Error
|
pub fn ErrorBadRequest<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::BAD_REQUEST).into()
|
InternalError::new(err, StatusCode::BAD_REQUEST).into()
|
||||||
}
|
}
|
||||||
@@ -650,7 +650,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorUnauthorized<T>(err: T) -> Error
|
pub fn ErrorUnauthorized<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
||||||
}
|
}
|
||||||
@@ -660,7 +660,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorForbidden<T>(err: T) -> Error
|
pub fn ErrorForbidden<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::FORBIDDEN).into()
|
InternalError::new(err, StatusCode::FORBIDDEN).into()
|
||||||
}
|
}
|
||||||
@@ -670,7 +670,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorNotFound<T>(err: T) -> Error
|
pub fn ErrorNotFound<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::NOT_FOUND).into()
|
InternalError::new(err, StatusCode::NOT_FOUND).into()
|
||||||
}
|
}
|
||||||
@@ -680,7 +680,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
|
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
||||||
}
|
}
|
||||||
@@ -690,7 +690,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorRequestTimeout<T>(err: T) -> Error
|
pub fn ErrorRequestTimeout<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
|
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
|
||||||
}
|
}
|
||||||
@@ -700,7 +700,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorConflict<T>(err: T) -> Error
|
pub fn ErrorConflict<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::CONFLICT).into()
|
InternalError::new(err, StatusCode::CONFLICT).into()
|
||||||
}
|
}
|
||||||
@@ -710,7 +710,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorGone<T>(err: T) -> Error
|
pub fn ErrorGone<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::GONE).into()
|
InternalError::new(err, StatusCode::GONE).into()
|
||||||
}
|
}
|
||||||
@@ -720,7 +720,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
|
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
||||||
}
|
}
|
||||||
@@ -730,7 +730,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorExpectationFailed<T>(err: T) -> Error
|
pub fn ErrorExpectationFailed<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
||||||
}
|
}
|
||||||
@@ -740,7 +740,7 @@ where
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn ErrorInternalServerError<T>(err: T) -> Error
|
pub fn ErrorInternalServerError<T>(err: T) -> Error
|
||||||
where
|
where
|
||||||
T: Send + Sync + fmt::Debug + 'static,
|
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||||
{
|
{
|
||||||
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
|
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
|
||||||
}
|
}
|
||||||
@@ -888,7 +888,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_internal_error() {
|
fn test_internal_error() {
|
||||||
let err = InternalError::from_response(
|
let err = InternalError::from_response(
|
||||||
ExpectError::Encoding, HttpResponse::Ok().into());
|
ExpectError::Encoding,
|
||||||
|
HttpResponse::Ok().into(),
|
||||||
|
);
|
||||||
let resp: HttpResponse = err.error_response();
|
let resp: HttpResponse = err.error_response();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ use httprequest::HttpRequest;
|
|||||||
/// # extern crate futures;
|
/// # extern crate futures;
|
||||||
/// use actix_web::{App, Path, Result, http};
|
/// use actix_web::{App, Path, Result, http};
|
||||||
///
|
///
|
||||||
/// /// extract path info from "/{username}/{count}/?index.html" url
|
/// /// extract path info from "/{username}/{count}/index.html" url
|
||||||
/// /// {username} - deserializes to a String
|
/// /// {username} - deserializes to a String
|
||||||
/// /// {count} - - deserializes to a u32
|
/// /// {count} - - deserializes to a u32
|
||||||
/// fn index(info: Path<(String, u32)>) -> Result<String> {
|
/// fn index(info: Path<(String, u32)>) -> Result<String> {
|
||||||
@@ -34,7 +34,7 @@ use httprequest::HttpRequest;
|
|||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::new().resource(
|
/// let app = App::new().resource(
|
||||||
/// "/{username}/{count}/?index.html", // <- define path parameters
|
/// "/{username}/{count}/index.html", // <- define path parameters
|
||||||
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
@@ -195,9 +195,6 @@ where
|
|||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// It is possible to extract path information to a specific type that
|
|
||||||
/// implements `Deserialize` trait from *serde*.
|
|
||||||
///
|
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// #[macro_use] extern crate serde_derive;
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
@@ -15,7 +15,6 @@ use bytes::{BufMut, Bytes, BytesMut};
|
|||||||
use futures::{Async, Future, Poll, Stream};
|
use futures::{Async, Future, Poll, Stream};
|
||||||
use futures_cpupool::{CpuFuture, CpuPool};
|
use futures_cpupool::{CpuFuture, CpuPool};
|
||||||
use mime_guess::get_mime_type;
|
use mime_guess::get_mime_type;
|
||||||
use percent_encoding::percent_decode;
|
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler};
|
use handler::{Handler, Reply, Responder, RouteHandler, WrapHandler};
|
||||||
@@ -457,7 +456,7 @@ lazy_static! {
|
|||||||
error!("Can not parse ACTIX_FS_POOL value");
|
error!("Can not parse ACTIX_FS_POOL value");
|
||||||
20
|
20
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Err(_) => 20,
|
Err(_) => 20,
|
||||||
};
|
};
|
||||||
Mutex::new(CpuPool::new(default))
|
Mutex::new(CpuPool::new(default))
|
||||||
@@ -477,7 +476,8 @@ impl<S: 'static> StaticFiles<S> {
|
|||||||
StaticFiles::with_pool(dir, pool)
|
StaticFiles::with_pool(dir, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create new `StaticFiles` instance for specified base directory and `CpuPool`.
|
/// Create new `StaticFiles` instance for specified base directory and
|
||||||
|
/// `CpuPool`.
|
||||||
pub fn with_pool<T: Into<PathBuf>>(dir: T, pool: CpuPool) -> StaticFiles<S> {
|
pub fn with_pool<T: Into<PathBuf>>(dir: T, pool: CpuPool) -> StaticFiles<S> {
|
||||||
let dir = dir.into();
|
let dir = dir.into();
|
||||||
|
|
||||||
@@ -543,8 +543,7 @@ impl<S: 'static> Handler<S> for StaticFiles<S> {
|
|||||||
} else {
|
} else {
|
||||||
let relpath = match req.match_info()
|
let relpath = match req.match_info()
|
||||||
.get("tail")
|
.get("tail")
|
||||||
.map(|tail| percent_decode(tail.as_bytes()).decode_utf8().unwrap())
|
.map(|tail| PathBuf::from_param(tail))
|
||||||
.map(|tail| PathBuf::from_param(tail.as_ref()))
|
|
||||||
{
|
{
|
||||||
Some(Ok(path)) => path,
|
Some(Ok(path)) => path,
|
||||||
_ => return Ok(self.default.handle(req)),
|
_ => return Ok(self.default.handle(req)),
|
||||||
|
@@ -171,16 +171,16 @@ impl Display for ContentRangeSpec {
|
|||||||
range,
|
range,
|
||||||
instance_length,
|
instance_length,
|
||||||
} => {
|
} => {
|
||||||
try!(f.write_str("bytes "));
|
f.write_str("bytes ")?;
|
||||||
match range {
|
match range {
|
||||||
Some((first_byte, last_byte)) => {
|
Some((first_byte, last_byte)) => {
|
||||||
try!(write!(f, "{}-{}", first_byte, last_byte));
|
write!(f, "{}-{}", first_byte, last_byte)?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
try!(f.write_str("*"));
|
f.write_str("*")?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try!(f.write_str("/"));
|
f.write_str("/")?;
|
||||||
if let Some(v) = instance_length {
|
if let Some(v) = instance_length {
|
||||||
write!(f, "{}", v)
|
write!(f, "{}", v)
|
||||||
} else {
|
} else {
|
||||||
@@ -191,8 +191,8 @@ impl Display for ContentRangeSpec {
|
|||||||
ref unit,
|
ref unit,
|
||||||
ref resp,
|
ref resp,
|
||||||
} => {
|
} => {
|
||||||
try!(f.write_str(unit));
|
f.write_str(unit)?;
|
||||||
try!(f.write_str(" "));
|
f.write_str(" ")?;
|
||||||
f.write_str(resp)
|
f.write_str(resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,8 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use modhttp::Error as HttpError;
|
|
||||||
use modhttp::header::GetAll;
|
use modhttp::header::GetAll;
|
||||||
|
use modhttp::Error as HttpError;
|
||||||
|
|
||||||
pub use modhttp::header::*;
|
pub use modhttp::header::*;
|
||||||
|
|
||||||
@@ -116,8 +116,10 @@ pub enum ContentEncoding {
|
|||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
Br,
|
Br,
|
||||||
/// A format using the zlib structure with deflate algorithm
|
/// A format using the zlib structure with deflate algorithm
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Deflate,
|
Deflate,
|
||||||
/// Gzip algorithm
|
/// Gzip algorithm
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Gzip,
|
Gzip,
|
||||||
/// Indicates the identity function (i.e. no compression, nor modification)
|
/// Indicates the identity function (i.e. no compression, nor modification)
|
||||||
Identity,
|
Identity,
|
||||||
@@ -137,7 +139,9 @@ impl ContentEncoding {
|
|||||||
match *self {
|
match *self {
|
||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
ContentEncoding::Br => "br",
|
ContentEncoding::Br => "br",
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => "gzip",
|
ContentEncoding::Gzip => "gzip",
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => "deflate",
|
ContentEncoding::Deflate => "deflate",
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||||
}
|
}
|
||||||
@@ -149,7 +153,9 @@ impl ContentEncoding {
|
|||||||
match *self {
|
match *self {
|
||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
ContentEncoding::Br => 1.1,
|
ContentEncoding::Br => 1.1,
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => 1.0,
|
ContentEncoding::Gzip => 1.0,
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => 0.9,
|
ContentEncoding::Deflate => 0.9,
|
||||||
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
|
||||||
}
|
}
|
||||||
@@ -159,10 +165,12 @@ impl ContentEncoding {
|
|||||||
// TODO: remove memory allocation
|
// TODO: remove memory allocation
|
||||||
impl<'a> From<&'a str> for ContentEncoding {
|
impl<'a> From<&'a str> for ContentEncoding {
|
||||||
fn from(s: &'a str) -> ContentEncoding {
|
fn from(s: &'a str) -> ContentEncoding {
|
||||||
match s.trim().to_lowercase().as_ref() {
|
match AsRef::<str>::as_ref(&s.trim().to_lowercase()) {
|
||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
"br" => ContentEncoding::Br,
|
"br" => ContentEncoding::Br,
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
"gzip" => ContentEncoding::Gzip,
|
"gzip" => ContentEncoding::Gzip,
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
"deflate" => ContentEncoding::Deflate,
|
"deflate" => ContentEncoding::Deflate,
|
||||||
_ => ContentEncoding::Identity,
|
_ => ContentEncoding::Identity,
|
||||||
}
|
}
|
||||||
@@ -202,7 +210,7 @@ impl fmt::Write for Writer {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// Reads a comma-delimited raw header into a Vec.
|
/// Reads a comma-delimited raw header into a Vec.
|
||||||
pub fn from_comma_delimited<T: FromStr>(
|
pub fn from_comma_delimited<T: FromStr>(
|
||||||
all: GetAll<HeaderValue>
|
all: GetAll<HeaderValue>,
|
||||||
) -> Result<Vec<T>, ParseError> {
|
) -> Result<Vec<T>, ParseError> {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for h in all {
|
for h in all {
|
||||||
|
@@ -59,7 +59,7 @@ impl<T: PartialEq> cmp::PartialOrd for QualityItem<T> {
|
|||||||
|
|
||||||
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
impl<T: fmt::Display> fmt::Display for QualityItem<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
try!(fmt::Display::fmt(&self.item, f));
|
fmt::Display::fmt(&self.item, f)?;
|
||||||
match self.quality.0 {
|
match self.quality.0 {
|
||||||
1000 => Ok(()),
|
1000 => Ok(()),
|
||||||
0 => f.write_str("; q=0"),
|
0 => f.write_str("; q=0"),
|
||||||
|
@@ -6,8 +6,6 @@ use futures::future::{result, FutureResult};
|
|||||||
use futures::{Async, Poll, Stream};
|
use futures::{Async, Poll, Stream};
|
||||||
use futures_cpupool::CpuPool;
|
use futures_cpupool::CpuPool;
|
||||||
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
|
use http::{header, Extensions, HeaderMap, Method, StatusCode, Uri, Version};
|
||||||
use percent_encoding::percent_decode;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{cmp, fmt, io, mem, str};
|
use std::{cmp, fmt, io, mem, str};
|
||||||
@@ -24,11 +22,12 @@ use param::Params;
|
|||||||
use payload::Payload;
|
use payload::Payload;
|
||||||
use router::{Resource, Router};
|
use router::{Resource, Router};
|
||||||
use server::helpers::SharedHttpInnerMessage;
|
use server::helpers::SharedHttpInnerMessage;
|
||||||
|
use uri::Url as InnerUrl;
|
||||||
|
|
||||||
pub struct HttpInnerMessage {
|
pub struct HttpInnerMessage {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
pub uri: Uri,
|
pub(crate) url: InnerUrl,
|
||||||
pub headers: HeaderMap,
|
pub headers: HeaderMap,
|
||||||
pub extensions: Extensions,
|
pub extensions: Extensions,
|
||||||
pub params: Params<'static>,
|
pub params: Params<'static>,
|
||||||
@@ -38,6 +37,7 @@ pub struct HttpInnerMessage {
|
|||||||
pub addr: Option<SocketAddr>,
|
pub addr: Option<SocketAddr>,
|
||||||
pub payload: Option<Payload>,
|
pub payload: Option<Payload>,
|
||||||
pub info: Option<ConnectionInfo<'static>>,
|
pub info: Option<ConnectionInfo<'static>>,
|
||||||
|
pub keep_alive: bool,
|
||||||
resource: RouterResource,
|
resource: RouterResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,17 +51,18 @@ impl Default for HttpInnerMessage {
|
|||||||
fn default() -> HttpInnerMessage {
|
fn default() -> HttpInnerMessage {
|
||||||
HttpInnerMessage {
|
HttpInnerMessage {
|
||||||
method: Method::GET,
|
method: Method::GET,
|
||||||
uri: Uri::default(),
|
url: InnerUrl::default(),
|
||||||
version: Version::HTTP_11,
|
version: Version::HTTP_11,
|
||||||
headers: HeaderMap::with_capacity(16),
|
headers: HeaderMap::with_capacity(16),
|
||||||
params: Params::new(),
|
params: Params::new(),
|
||||||
query: Params::new(),
|
query: Params::new(),
|
||||||
query_loaded: false,
|
query_loaded: false,
|
||||||
cookies: None,
|
|
||||||
addr: None,
|
addr: None,
|
||||||
|
cookies: None,
|
||||||
payload: None,
|
payload: None,
|
||||||
extensions: Extensions::new(),
|
extensions: Extensions::new(),
|
||||||
info: None,
|
info: None,
|
||||||
|
keep_alive: true,
|
||||||
resource: RouterResource::Notset,
|
resource: RouterResource::Notset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,20 +72,7 @@ impl HttpInnerMessage {
|
|||||||
/// Checks if a connection should be kept alive.
|
/// Checks if a connection should be kept alive.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn keep_alive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
if let Some(conn) = self.headers.get(header::CONNECTION) {
|
self.keep_alive
|
||||||
if let Ok(conn) = conn.to_str() {
|
|
||||||
if self.version == Version::HTTP_10 && conn.contains("keep-alive") {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.version == Version::HTTP_11
|
|
||||||
&& !(conn.contains("close") || conn.contains("upgrade"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.version != Version::HTTP_10
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -92,12 +80,12 @@ impl HttpInnerMessage {
|
|||||||
self.headers.clear();
|
self.headers.clear();
|
||||||
self.extensions.clear();
|
self.extensions.clear();
|
||||||
self.params.clear();
|
self.params.clear();
|
||||||
self.query.clear();
|
|
||||||
self.query_loaded = false;
|
|
||||||
self.cookies = None;
|
|
||||||
self.addr = None;
|
self.addr = None;
|
||||||
self.info = None;
|
self.info = None;
|
||||||
|
self.query_loaded = false;
|
||||||
|
self.cookies = None;
|
||||||
self.payload = None;
|
self.payload = None;
|
||||||
|
self.keep_alive = true;
|
||||||
self.resource = RouterResource::Notset;
|
self.resource = RouterResource::Notset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,20 +104,22 @@ impl HttpRequest<()> {
|
|||||||
method: Method, uri: Uri, version: Version, headers: HeaderMap,
|
method: Method, uri: Uri, version: Version, headers: HeaderMap,
|
||||||
payload: Option<Payload>,
|
payload: Option<Payload>,
|
||||||
) -> HttpRequest {
|
) -> HttpRequest {
|
||||||
|
let url = InnerUrl::new(uri);
|
||||||
HttpRequest(
|
HttpRequest(
|
||||||
SharedHttpInnerMessage::from_message(HttpInnerMessage {
|
SharedHttpInnerMessage::from_message(HttpInnerMessage {
|
||||||
method,
|
method,
|
||||||
uri,
|
url,
|
||||||
version,
|
version,
|
||||||
headers,
|
headers,
|
||||||
payload,
|
payload,
|
||||||
params: Params::new(),
|
params: Params::new(),
|
||||||
query: Params::new(),
|
query: Params::new(),
|
||||||
query_loaded: false,
|
query_loaded: false,
|
||||||
|
extensions: Extensions::new(),
|
||||||
cookies: None,
|
cookies: None,
|
||||||
addr: None,
|
addr: None,
|
||||||
extensions: Extensions::new(),
|
|
||||||
info: None,
|
info: None,
|
||||||
|
keep_alive: true,
|
||||||
resource: RouterResource::Notset,
|
resource: RouterResource::Notset,
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
@@ -201,6 +191,19 @@ impl<S> HttpRequest<S> {
|
|||||||
&mut self.as_mut().extensions
|
&mut self.as_mut().extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request extensions
|
||||||
|
#[inline]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn extensions_ro(&self) -> &Extensions {
|
||||||
|
&self.as_ref().extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable refernece to a the request's extensions
|
||||||
|
#[inline]
|
||||||
|
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
||||||
|
&mut self.as_mut().extensions
|
||||||
|
}
|
||||||
|
|
||||||
/// Default `CpuPool`
|
/// Default `CpuPool`
|
||||||
#[inline]
|
#[inline]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@@ -241,15 +244,17 @@ impl<S> HttpRequest<S> {
|
|||||||
/// Read the Request Uri.
|
/// Read the Request Uri.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri(&self) -> &Uri {
|
pub fn uri(&self) -> &Uri {
|
||||||
&self.as_ref().uri
|
self.as_ref().url.uri()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "0.5.3")]
|
||||||
/// Returns mutable the Request Uri.
|
/// Returns mutable the Request Uri.
|
||||||
///
|
///
|
||||||
/// This might be useful for middlewares, e.g. path normalization.
|
/// This might be useful for middlewares, e.g. path normalization.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||||
&mut self.as_mut().uri
|
self.as_mut().url.uri_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the Request method.
|
/// Read the Request method.
|
||||||
@@ -275,15 +280,7 @@ impl<S> HttpRequest<S> {
|
|||||||
/// The target path of this Request.
|
/// The target path of this Request.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
self.uri().path()
|
self.as_ref().url.path()
|
||||||
}
|
|
||||||
|
|
||||||
/// Percent decoded path of this Request.
|
|
||||||
#[inline]
|
|
||||||
pub fn path_decoded(&self) -> Cow<str> {
|
|
||||||
percent_decode(self.uri().path().as_bytes())
|
|
||||||
.decode_utf8()
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get *ConnectionInfo* for correct request.
|
/// Get *ConnectionInfo* for correct request.
|
||||||
@@ -370,13 +367,13 @@ impl<S> HttpRequest<S> {
|
|||||||
/// To get client connection information `connection_info()` method should
|
/// To get client connection information `connection_info()` method should
|
||||||
/// be used.
|
/// be used.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn peer_addr(&self) -> Option<&SocketAddr> {
|
pub fn peer_addr(&self) -> Option<SocketAddr> {
|
||||||
self.as_ref().addr.as_ref()
|
self.as_ref().addr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
|
pub(crate) fn set_peer_addr(&mut self, addr: Option<SocketAddr>) {
|
||||||
self.as_mut().addr = addr
|
self.as_mut().addr = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the Params object.
|
/// Get a reference to the Params object.
|
||||||
@@ -385,6 +382,7 @@ impl<S> HttpRequest<S> {
|
|||||||
if !self.as_ref().query_loaded {
|
if !self.as_ref().query_loaded {
|
||||||
let params: &mut Params =
|
let params: &mut Params =
|
||||||
unsafe { mem::transmute(&mut self.as_mut().query) };
|
unsafe { mem::transmute(&mut self.as_mut().query) };
|
||||||
|
params.clear();
|
||||||
self.as_mut().query_loaded = true;
|
self.as_mut().query_loaded = true;
|
||||||
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
|
||||||
params.add(key, val);
|
params.add(key, val);
|
||||||
@@ -418,9 +416,9 @@ impl<S> HttpRequest<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg.cookies = Some(cookies)
|
msg.cookies = Some(cookies);
|
||||||
}
|
}
|
||||||
Ok(self.as_ref().cookies.as_ref().unwrap())
|
Ok(&self.as_ref().cookies.as_ref().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return request cookie.
|
/// Return request cookie.
|
||||||
@@ -438,9 +436,9 @@ impl<S> HttpRequest<S> {
|
|||||||
/// Get a reference to the Params object.
|
/// Get a reference to the Params object.
|
||||||
///
|
///
|
||||||
/// Params is a container for url parameters.
|
/// Params is a container for url parameters.
|
||||||
/// Route supports glob patterns: * for a single wildcard segment and :param
|
/// A variable segment is specified in the form `{identifier}`,
|
||||||
/// for matching storing that segment of the request url in the Params
|
/// where the identifier can be used later in a request handler to
|
||||||
/// object.
|
/// access the matched value for that segment.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn match_info(&self) -> &Params {
|
pub fn match_info(&self) -> &Params {
|
||||||
unsafe { mem::transmute(&self.as_ref().params) }
|
unsafe { mem::transmute(&self.as_ref().params) }
|
||||||
@@ -578,7 +576,7 @@ impl<S> fmt::Debug for HttpRequest<S> {
|
|||||||
"\nHttpRequest {:?} {}:{}",
|
"\nHttpRequest {:?} {}:{}",
|
||||||
self.as_ref().version,
|
self.as_ref().version,
|
||||||
self.as_ref().method,
|
self.as_ref().method,
|
||||||
self.path_decoded()
|
self.path()
|
||||||
);
|
);
|
||||||
if !self.query_string().is_empty() {
|
if !self.query_string().is_empty() {
|
||||||
let _ = writeln!(f, " query: ?{:?}", self.query_string());
|
let _ = writeln!(f, " query: ?{:?}", self.query_string());
|
||||||
@@ -596,6 +594,8 @@ impl<S> fmt::Debug for HttpRequest<S> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use http::{HttpTryFrom, Uri};
|
use http::{HttpTryFrom, Uri};
|
||||||
use resource::ResourceHandler;
|
use resource::ResourceHandler;
|
||||||
|
10
src/json.rs
10
src/json.rs
@@ -2,8 +2,8 @@ use bytes::{Bytes, BytesMut};
|
|||||||
use futures::{Future, Poll, Stream};
|
use futures::{Future, Poll, Stream};
|
||||||
use http::header::CONTENT_LENGTH;
|
use http::header::CONTENT_LENGTH;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use mime;
|
use mime;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -193,7 +193,7 @@ impl<S> JsonConfig<S> {
|
|||||||
/// Set custom error handler
|
/// Set custom error handler
|
||||||
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
pub fn error_handler<F>(&mut self, f: F) -> &mut Self
|
||||||
where
|
where
|
||||||
F: Fn(JsonPayloadError, HttpRequest<S>) -> Error + 'static
|
F: Fn(JsonPayloadError, HttpRequest<S>) -> Error + 'static,
|
||||||
{
|
{
|
||||||
self.ehandler = Rc::new(f);
|
self.ehandler = Rc::new(f);
|
||||||
self
|
self
|
||||||
@@ -202,8 +202,10 @@ impl<S> JsonConfig<S> {
|
|||||||
|
|
||||||
impl<S> Default for JsonConfig<S> {
|
impl<S> Default for JsonConfig<S> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
JsonConfig { limit: 262_144,
|
JsonConfig {
|
||||||
ehandler: Rc::new(|e, _| e.into()) }
|
limit: 262_144,
|
||||||
|
ehandler: Rc::new(|e, _| e.into()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -64,8 +64,10 @@
|
|||||||
#![cfg_attr(actix_nightly, feature(
|
#![cfg_attr(actix_nightly, feature(
|
||||||
specialization, // for impl ErrorResponse for std::error::Error
|
specialization, // for impl ErrorResponse for std::error::Error
|
||||||
))]
|
))]
|
||||||
#![cfg_attr(feature = "cargo-clippy",
|
#![cfg_attr(
|
||||||
allow(decimal_literal_representation, suspicious_arithmetic_impl))]
|
feature = "cargo-clippy",
|
||||||
|
allow(decimal_literal_representation, suspicious_arithmetic_impl)
|
||||||
|
)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
@@ -103,6 +105,7 @@ extern crate serde;
|
|||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
extern crate brotli2;
|
extern crate brotli2;
|
||||||
extern crate encoding;
|
extern crate encoding;
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
extern crate flate2;
|
extern crate flate2;
|
||||||
extern crate h2 as http2;
|
extern crate h2 as http2;
|
||||||
extern crate num_cpus;
|
extern crate num_cpus;
|
||||||
@@ -110,7 +113,6 @@ extern crate percent_encoding;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate serde_urlencoded;
|
extern crate serde_urlencoded;
|
||||||
extern crate smallvec;
|
extern crate smallvec;
|
||||||
extern crate trust_dns_resolver;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
|
|
||||||
@@ -147,6 +149,7 @@ mod pipeline;
|
|||||||
mod resource;
|
mod resource;
|
||||||
mod route;
|
mod route;
|
||||||
mod router;
|
mod router;
|
||||||
|
mod uri;
|
||||||
mod with;
|
mod with;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
@@ -7,8 +7,8 @@
|
|||||||
//!
|
//!
|
||||||
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
|
||||||
//! 2. Use any of the builder methods to set fields in the backend.
|
//! 2. Use any of the builder methods to set fields in the backend.
|
||||||
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
|
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
|
||||||
//! constructed backend.
|
//! constructed backend.
|
||||||
//!
|
//!
|
||||||
//! Cors middleware could be used as parameter for `App::middleware()` or
|
//! Cors middleware could be used as parameter for `App::middleware()` or
|
||||||
//! `ResourceHandler::middleware()` methods. But you have to use
|
//! `ResourceHandler::middleware()` methods. But you have to use
|
||||||
|
@@ -150,8 +150,8 @@ impl CsrfFilter {
|
|||||||
|
|
||||||
/// Add an origin that is allowed to make requests. Will be verified
|
/// Add an origin that is allowed to make requests. Will be verified
|
||||||
/// against the `Origin` request header.
|
/// against the `Origin` request header.
|
||||||
pub fn allowed_origin(mut self, origin: &str) -> CsrfFilter {
|
pub fn allowed_origin<T: Into<String>>(mut self, origin: T) -> CsrfFilter {
|
||||||
self.origins.insert(origin.to_owned());
|
self.origins.insert(origin.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
389
src/middleware/identity.rs
Normal file
389
src/middleware/identity.rs
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
//! Request identity service for Actix applications.
|
||||||
|
//!
|
||||||
|
//! [**IdentityService**](struct.IdentityService.html) middleware can be
|
||||||
|
//! used with different policies types to store identity information.
|
||||||
|
//!
|
||||||
|
//! Bu default, only cookie identity policy is implemented. Other backend
|
||||||
|
//! implementations can be added separately.
|
||||||
|
//!
|
||||||
|
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
|
||||||
|
//! uses cookies as identity storage.
|
||||||
|
//!
|
||||||
|
//! To access current request identity
|
||||||
|
//! [**RequestIdentity**](trait.RequestIdentity.html) should be used.
|
||||||
|
//! *HttpRequest* implements *RequestIdentity* trait.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use actix_web::middleware::identity::RequestIdentity;
|
||||||
|
//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
|
||||||
|
//! use actix_web::*;
|
||||||
|
//!
|
||||||
|
//! fn index(req: HttpRequest) -> Result<String> {
|
||||||
|
//! // access request identity
|
||||||
|
//! if let Some(id) = req.identity() {
|
||||||
|
//! Ok(format!("Welcome! {}", id))
|
||||||
|
//! } else {
|
||||||
|
//! Ok("Welcome Anonymous!".to_owned())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn login(mut req: HttpRequest) -> HttpResponse {
|
||||||
|
//! req.remember("User1".to_owned()); // <- remember identity
|
||||||
|
//! HttpResponse::Ok().finish()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn logout(mut req: HttpRequest) -> HttpResponse {
|
||||||
|
//! req.forget(); // <- remove identity
|
||||||
|
//! HttpResponse::Ok().finish()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! let app = App::new().middleware(IdentityService::new(
|
||||||
|
//! // <- create identity middleware
|
||||||
|
//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
|
||||||
|
//! .name("auth-cookie")
|
||||||
|
//! .secure(false),
|
||||||
|
//! ));
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use cookie::{Cookie, CookieJar, Key};
|
||||||
|
use futures::Future;
|
||||||
|
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
|
||||||
|
use time::Duration;
|
||||||
|
|
||||||
|
use error::{Error, Result};
|
||||||
|
use http::header::{self, HeaderValue};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
use middleware::{Middleware, Response, Started};
|
||||||
|
|
||||||
|
/// The helper trait to obtain your identity from a request.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use actix_web::*;
|
||||||
|
/// use actix_web::middleware::identity::RequestIdentity;
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> Result<String> {
|
||||||
|
/// // access request identity
|
||||||
|
/// if let Some(id) = req.identity() {
|
||||||
|
/// Ok(format!("Welcome! {}", id))
|
||||||
|
/// } else {
|
||||||
|
/// Ok("Welcome Anonymous!".to_owned())
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn login(mut req: HttpRequest) -> HttpResponse {
|
||||||
|
/// req.remember("User1".to_owned()); // <- remember identity
|
||||||
|
/// HttpResponse::Ok().finish()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn logout(mut req: HttpRequest) -> HttpResponse {
|
||||||
|
/// req.forget(); // <- remove identity
|
||||||
|
/// HttpResponse::Ok().finish()
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub trait RequestIdentity {
|
||||||
|
/// Return the claimed identity of the user associated request or
|
||||||
|
/// ``None`` if no identity can be found associated with the request.
|
||||||
|
fn identity(&self) -> Option<&str>;
|
||||||
|
|
||||||
|
/// Remember identity.
|
||||||
|
fn remember(&mut self, identity: String);
|
||||||
|
|
||||||
|
/// This method is used to 'forget' the current identity on subsequent
|
||||||
|
/// requests.
|
||||||
|
fn forget(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> RequestIdentity for HttpRequest<S> {
|
||||||
|
fn identity(&self) -> Option<&str> {
|
||||||
|
if let Some(id) = self.extensions_ro().get::<IdentityBox>() {
|
||||||
|
return id.0.identity();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remember(&mut self, identity: String) {
|
||||||
|
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
|
||||||
|
return id.0.remember(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forget(&mut self) {
|
||||||
|
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
|
||||||
|
return id.0.forget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identity
|
||||||
|
pub trait Identity: 'static {
|
||||||
|
fn identity(&self) -> Option<&str>;
|
||||||
|
|
||||||
|
fn remember(&mut self, key: String);
|
||||||
|
|
||||||
|
fn forget(&mut self);
|
||||||
|
|
||||||
|
/// Write session to storage backend.
|
||||||
|
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identity policy definition.
|
||||||
|
pub trait IdentityPolicy<S>: Sized + 'static {
|
||||||
|
type Identity: Identity;
|
||||||
|
type Future: Future<Item = Self::Identity, Error = Error>;
|
||||||
|
|
||||||
|
/// Parse the session from request and load data from a service identity.
|
||||||
|
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request identity middleware
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::App;
|
||||||
|
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().middleware(
|
||||||
|
/// IdentityService::new( // <- create identity middleware
|
||||||
|
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
|
||||||
|
/// .name("auth-cookie")
|
||||||
|
/// .secure(false))
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct IdentityService<T> {
|
||||||
|
backend: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IdentityService<T> {
|
||||||
|
/// Create new identity service with specified backend.
|
||||||
|
pub fn new(backend: T) -> Self {
|
||||||
|
IdentityService { backend }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IdentityBox(Box<Identity>);
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
unsafe impl Send for IdentityBox {}
|
||||||
|
#[doc(hidden)]
|
||||||
|
unsafe impl Sync for IdentityBox {}
|
||||||
|
|
||||||
|
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
|
||||||
|
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
||||||
|
let mut req = req.clone();
|
||||||
|
|
||||||
|
let fut = self.backend
|
||||||
|
.from_request(&mut req)
|
||||||
|
.then(move |res| match res {
|
||||||
|
Ok(id) => {
|
||||||
|
req.extensions().insert(IdentityBox(Box::new(id)));
|
||||||
|
FutOk(None)
|
||||||
|
}
|
||||||
|
Err(err) => FutErr(err),
|
||||||
|
});
|
||||||
|
Ok(Started::Future(Box::new(fut)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response(
|
||||||
|
&self, req: &mut HttpRequest<S>, resp: HttpResponse
|
||||||
|
) -> Result<Response> {
|
||||||
|
if let Some(mut id) = req.extensions().remove::<IdentityBox>() {
|
||||||
|
id.0.write(resp)
|
||||||
|
} else {
|
||||||
|
Ok(Response::Done(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
/// Identity that uses private cookies as identity storage.
|
||||||
|
pub struct CookieIdentity {
|
||||||
|
changed: bool,
|
||||||
|
identity: Option<String>,
|
||||||
|
inner: Rc<CookieIdentityInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Identity for CookieIdentity {
|
||||||
|
fn identity(&self) -> Option<&str> {
|
||||||
|
self.identity.as_ref().map(|s| s.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remember(&mut self, value: String) {
|
||||||
|
self.changed = true;
|
||||||
|
self.identity = Some(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forget(&mut self) {
|
||||||
|
self.changed = true;
|
||||||
|
self.identity = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> {
|
||||||
|
if self.changed {
|
||||||
|
let _ = self.inner.set_cookie(&mut resp, self.identity.take());
|
||||||
|
}
|
||||||
|
Ok(Response::Done(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CookieIdentityInner {
|
||||||
|
key: Key,
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
domain: Option<String>,
|
||||||
|
secure: bool,
|
||||||
|
max_age: Option<Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CookieIdentityInner {
|
||||||
|
fn new(key: &[u8]) -> CookieIdentityInner {
|
||||||
|
CookieIdentityInner {
|
||||||
|
key: Key::from_master(key),
|
||||||
|
name: "actix-identity".to_owned(),
|
||||||
|
path: "/".to_owned(),
|
||||||
|
domain: None,
|
||||||
|
secure: true,
|
||||||
|
max_age: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> {
|
||||||
|
let some = id.is_some();
|
||||||
|
{
|
||||||
|
let id = id.unwrap_or_else(String::new);
|
||||||
|
let mut cookie = Cookie::new(self.name.clone(), id);
|
||||||
|
cookie.set_path(self.path.clone());
|
||||||
|
cookie.set_secure(self.secure);
|
||||||
|
cookie.set_http_only(true);
|
||||||
|
|
||||||
|
if let Some(ref domain) = self.domain {
|
||||||
|
cookie.set_domain(domain.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(max_age) = self.max_age {
|
||||||
|
cookie.set_max_age(max_age);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut jar = CookieJar::new();
|
||||||
|
if some {
|
||||||
|
jar.private(&self.key).add(cookie);
|
||||||
|
} else {
|
||||||
|
jar.add_original(cookie.clone());
|
||||||
|
jar.private(&self.key).remove(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
for cookie in jar.delta() {
|
||||||
|
let val = HeaderValue::from_str(&cookie.to_string())?;
|
||||||
|
resp.headers_mut().append(header::SET_COOKIE, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
|
||||||
|
if let Ok(cookies) = req.cookies() {
|
||||||
|
for cookie in cookies {
|
||||||
|
if cookie.name() == self.name {
|
||||||
|
let mut jar = CookieJar::new();
|
||||||
|
jar.add_original(cookie.clone());
|
||||||
|
|
||||||
|
let cookie_opt = jar.private(&self.key).get(&self.name);
|
||||||
|
if let Some(cookie) = cookie_opt {
|
||||||
|
return Some(cookie.value().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use cookies for request identity storage.
|
||||||
|
///
|
||||||
|
/// The constructors take a key as an argument.
|
||||||
|
/// This is the private key for cookie - when this value is changed,
|
||||||
|
/// all identities are lost. The constructors will panic if the key is less
|
||||||
|
/// than 32 bytes in length.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::App;
|
||||||
|
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().middleware(
|
||||||
|
/// IdentityService::new( // <- create identity middleware
|
||||||
|
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
|
||||||
|
/// .domain("www.rust-lang.org")
|
||||||
|
/// .name("actix_auth")
|
||||||
|
/// .path("/")
|
||||||
|
/// .secure(true)));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
|
||||||
|
|
||||||
|
impl CookieIdentityPolicy {
|
||||||
|
/// Construct new `CookieIdentityPolicy` instance.
|
||||||
|
///
|
||||||
|
/// Panics if key length is less than 32 bytes.
|
||||||
|
pub fn new(key: &[u8]) -> CookieIdentityPolicy {
|
||||||
|
CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `path` field in the session cookie being built.
|
||||||
|
pub fn path<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
|
||||||
|
Rc::get_mut(&mut self.0).unwrap().path = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `name` field in the session cookie being built.
|
||||||
|
pub fn name<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
|
||||||
|
Rc::get_mut(&mut self.0).unwrap().name = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `domain` field in the session cookie being built.
|
||||||
|
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
|
||||||
|
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `secure` field in the session cookie being built.
|
||||||
|
///
|
||||||
|
/// If the `secure` field is set, a cookie will only be transmitted when the
|
||||||
|
/// connection is secure - i.e. `https`
|
||||||
|
pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
|
||||||
|
Rc::get_mut(&mut self.0).unwrap().secure = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the `max-age` field in the session cookie being built.
|
||||||
|
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
|
||||||
|
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
|
||||||
|
type Identity = CookieIdentity;
|
||||||
|
type Future = FutureResult<CookieIdentity, Error>;
|
||||||
|
|
||||||
|
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
|
||||||
|
let identity = self.0.load(req);
|
||||||
|
FutOk(CookieIdentity {
|
||||||
|
identity,
|
||||||
|
changed: false,
|
||||||
|
inner: Rc::clone(&self.0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ use httpresponse::HttpResponse;
|
|||||||
use middleware::{Finished, Middleware, Started};
|
use middleware::{Finished, Middleware, Started};
|
||||||
|
|
||||||
/// `Middleware` for logging request and response info to the terminal.
|
/// `Middleware` for logging request and response info to the terminal.
|
||||||
|
///
|
||||||
/// `Logger` middleware uses standard log crate to log information. You should
|
/// `Logger` middleware uses standard log crate to log information. You should
|
||||||
/// enable logger for `actix_web` package to see access log.
|
/// enable logger for `actix_web` package to see access log.
|
||||||
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
|
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
|
||||||
|
@@ -12,12 +12,17 @@ pub mod csrf;
|
|||||||
mod defaultheaders;
|
mod defaultheaders;
|
||||||
mod errhandlers;
|
mod errhandlers;
|
||||||
#[cfg(feature = "session")]
|
#[cfg(feature = "session")]
|
||||||
mod session;
|
pub mod identity;
|
||||||
|
#[cfg(feature = "session")]
|
||||||
|
pub mod session;
|
||||||
pub use self::defaultheaders::DefaultHeaders;
|
pub use self::defaultheaders::DefaultHeaders;
|
||||||
pub use self::errhandlers::ErrorHandlers;
|
pub use self::errhandlers::ErrorHandlers;
|
||||||
pub use self::logger::Logger;
|
pub use self::logger::Logger;
|
||||||
|
|
||||||
#[cfg(feature = "session")]
|
#[cfg(feature = "session")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated(since = "0.5.4",
|
||||||
|
note = "please use `actix_web::middleware::session` instead")]
|
||||||
pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession,
|
pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession,
|
||||||
Session, SessionBackend, SessionImpl, SessionStorage};
|
Session, SessionBackend, SessionImpl, SessionStorage};
|
||||||
|
|
||||||
|
@@ -1,3 +1,68 @@
|
|||||||
|
//! User sessions.
|
||||||
|
//!
|
||||||
|
//! Actix provides a general solution for session management. The
|
||||||
|
//! [**SessionStorage**](struct.SessionStorage.html)
|
||||||
|
//! middleware can be used with different backend types to store session
|
||||||
|
//! data in different backends.
|
||||||
|
//!
|
||||||
|
//! By default, only cookie session backend is implemented. Other
|
||||||
|
//! backend implementations can be added.
|
||||||
|
//!
|
||||||
|
//! [**CookieSessionBackend**](struct.CookieSessionBackend.html)
|
||||||
|
//! uses cookies as session storage. `CookieSessionBackend` creates sessions
|
||||||
|
//! which are limited to storing fewer than 4000 bytes of data, as the payload
|
||||||
|
//! must fit into a single cookie. An internal server error is generated if a
|
||||||
|
//! session contains more than 4000 bytes.
|
||||||
|
//!
|
||||||
|
//! A cookie may have a security policy of *signed* or *private*. Each has
|
||||||
|
//! a respective `CookieSessionBackend` constructor.
|
||||||
|
//!
|
||||||
|
//! A *signed* cookie may be viewed but not modified by the client. A *private*
|
||||||
|
//! cookie may neither be viewed nor modified by the client.
|
||||||
|
//!
|
||||||
|
//! The constructors take a key as an argument. This is the private key
|
||||||
|
//! for cookie session - when this value is changed, all session data is lost.
|
||||||
|
//!
|
||||||
|
//! In general, you create a `SessionStorage` middleware and initialize it
|
||||||
|
//! with specific backend implementation, such as a `CookieSessionBackend`.
|
||||||
|
//! To access session data,
|
||||||
|
//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session)
|
||||||
|
//! must be used. This method returns a
|
||||||
|
//! [*Session*](struct.Session.html) object, which allows us to get or set
|
||||||
|
//! session data.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # extern crate actix;
|
||||||
|
//! # extern crate actix_web;
|
||||||
|
//! use actix_web::{server, App, HttpRequest, Result};
|
||||||
|
//! use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
|
||||||
|
//!
|
||||||
|
//! fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||||
|
//! // access session data
|
||||||
|
//! if let Some(count) = req.session().get::<i32>("counter")? {
|
||||||
|
//! println!("SESSION value: {}", count);
|
||||||
|
//! req.session().set("counter", count+1)?;
|
||||||
|
//! } else {
|
||||||
|
//! req.session().set("counter", 1)?;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! Ok("Welcome!")
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! let sys = actix::System::new("basic-example");
|
||||||
|
//! server::new(
|
||||||
|
//! || App::new().middleware(
|
||||||
|
//! SessionStorage::new( // <- create session middleware
|
||||||
|
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
|
||||||
|
//! .secure(false)
|
||||||
|
//! )))
|
||||||
|
//! .bind("127.0.0.1:59880").unwrap()
|
||||||
|
//! .start();
|
||||||
|
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||||
|
//! let _ = sys.run();
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@@ -328,7 +328,7 @@ impl<S: 'static, H> WaitingResponse<S, H> {
|
|||||||
match self.fut.poll() {
|
match self.fut.poll() {
|
||||||
Ok(Async::NotReady) => None,
|
Ok(Async::NotReady) => None,
|
||||||
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
|
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
|
||||||
Err(err) => Some(ProcessResponse::init(err.into())),
|
Err(err) => Some(RunMiddlewares::init(info, err.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -491,8 +491,8 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
|||||||
if let Some(err) = self.resp.error() {
|
if let Some(err) = self.resp.error() {
|
||||||
if self.resp.status().is_server_error() {
|
if self.resp.status().is_server_error() {
|
||||||
error!(
|
error!(
|
||||||
"Error occured during request handling: {}",
|
"Error occured during request handling, status: {} {}",
|
||||||
err
|
self.resp.status(), err
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
|
@@ -420,7 +420,7 @@ impl<S: 'static> WaitingResponse<S> {
|
|||||||
match self.fut.poll() {
|
match self.fut.poll() {
|
||||||
Ok(Async::NotReady) => None,
|
Ok(Async::NotReady) => None,
|
||||||
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
|
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
|
||||||
Err(err) => Some(Response::init(err.into())),
|
Err(err) => Some(RunMiddlewares::init(info, err.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use percent_encoding::percent_decode;
|
|
||||||
use regex::{escape, Regex};
|
use regex::{escape, Regex};
|
||||||
|
|
||||||
use error::UrlGenerationError;
|
use error::UrlGenerationError;
|
||||||
@@ -82,12 +81,9 @@ impl Router {
|
|||||||
}
|
}
|
||||||
let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) };
|
let path: &str = unsafe { mem::transmute(&req.path()[self.0.prefix_len..]) };
|
||||||
let route_path = if path.is_empty() { "/" } else { path };
|
let route_path = if path.is_empty() { "/" } else { path };
|
||||||
let p = percent_decode(route_path.as_bytes())
|
|
||||||
.decode_utf8()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
for (idx, pattern) in self.0.patterns.iter().enumerate() {
|
for (idx, pattern) in self.0.patterns.iter().enumerate() {
|
||||||
if pattern.match_with_params(p.as_ref(), req.match_info_mut()) {
|
if pattern.match_with_params(route_path, req.match_info_mut()) {
|
||||||
req.set_resource(idx);
|
req.set_resource(idx);
|
||||||
return Some(idx);
|
return Some(idx);
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,14 @@ use std::{cmp, io, mem};
|
|||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use flate2::Compression;
|
#[cfg(feature = "flate2")]
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||||
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONNECTION,
|
#[cfg(feature = "flate2")]
|
||||||
CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
use flate2::Compression;
|
||||||
|
use http::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING, CONTENT_ENCODING,
|
||||||
|
CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||||
use http::{HttpTryFrom, Method, Version};
|
use http::{HttpTryFrom, Method, Version};
|
||||||
|
|
||||||
use body::{Binary, Body};
|
use body::{Binary, Body};
|
||||||
@@ -144,7 +147,9 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Decoder {
|
pub(crate) enum Decoder {
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Deflate(Box<DeflateDecoder<Writer>>),
|
Deflate(Box<DeflateDecoder<Writer>>),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Gzip(Option<Box<GzDecoder<Wrapper>>>),
|
Gzip(Option<Box<GzDecoder<Wrapper>>>),
|
||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
Br(Box<BrotliDecoder<Writer>>),
|
Br(Box<BrotliDecoder<Writer>>),
|
||||||
@@ -223,9 +228,11 @@ impl PayloadStream {
|
|||||||
ContentEncoding::Br => {
|
ContentEncoding::Br => {
|
||||||
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
|
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => {
|
ContentEncoding::Deflate => {
|
||||||
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
|
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => Decoder::Gzip(None),
|
ContentEncoding::Gzip => Decoder::Gzip(None),
|
||||||
_ => Decoder::Identity,
|
_ => Decoder::Identity,
|
||||||
};
|
};
|
||||||
@@ -251,6 +258,7 @@ impl PayloadStream {
|
|||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Decoder::Gzip(ref mut decoder) => {
|
Decoder::Gzip(ref mut decoder) => {
|
||||||
if let Some(ref mut decoder) = *decoder {
|
if let Some(ref mut decoder) = *decoder {
|
||||||
decoder.as_mut().get_mut().eof = true;
|
decoder.as_mut().get_mut().eof = true;
|
||||||
@@ -267,6 +275,7 @@ impl PayloadStream {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let b = decoder.get_mut().take();
|
let b = decoder.get_mut().take();
|
||||||
@@ -297,6 +306,7 @@ impl PayloadStream {
|
|||||||
}
|
}
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Decoder::Gzip(ref mut decoder) => {
|
Decoder::Gzip(ref mut decoder) => {
|
||||||
if decoder.is_none() {
|
if decoder.is_none() {
|
||||||
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
|
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
|
||||||
@@ -334,6 +344,7 @@ impl PayloadStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
decoder.flush()?;
|
decoder.flush()?;
|
||||||
@@ -352,7 +363,9 @@ impl PayloadStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum ContentEncoder {
|
pub(crate) enum ContentEncoder {
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Deflate(DeflateEncoder<TransferEncoding>),
|
Deflate(DeflateEncoder<TransferEncoding>),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
Gzip(GzEncoder<TransferEncoding>),
|
Gzip(GzEncoder<TransferEncoding>),
|
||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
Br(BrotliEncoder<TransferEncoding>),
|
Br(BrotliEncoder<TransferEncoding>),
|
||||||
@@ -422,9 +435,11 @@ impl ContentEncoder {
|
|||||||
let tmp = SharedBytes::default();
|
let tmp = SharedBytes::default();
|
||||||
let transfer = TransferEncoding::eof(tmp.clone());
|
let transfer = TransferEncoding::eof(tmp.clone());
|
||||||
let mut enc = match encoding {
|
let mut enc = match encoding {
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||||
DeflateEncoder::new(transfer, Compression::fast()),
|
DeflateEncoder::new(transfer, Compression::fast()),
|
||||||
),
|
),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
ContentEncoding::Gzip => ContentEncoder::Gzip(GzEncoder::new(
|
||||||
transfer,
|
transfer,
|
||||||
Compression::fast(),
|
Compression::fast(),
|
||||||
@@ -459,9 +474,6 @@ impl ContentEncoder {
|
|||||||
if resp.upgrade() {
|
if resp.upgrade() {
|
||||||
if version == Version::HTTP_2 {
|
if version == Version::HTTP_2 {
|
||||||
error!("Connection upgrade is forbidden for HTTP/2");
|
error!("Connection upgrade is forbidden for HTTP/2");
|
||||||
} else {
|
|
||||||
resp.headers_mut()
|
|
||||||
.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
|
||||||
}
|
}
|
||||||
if encoding != ContentEncoding::Identity {
|
if encoding != ContentEncoding::Identity {
|
||||||
encoding = ContentEncoding::Identity;
|
encoding = ContentEncoding::Identity;
|
||||||
@@ -481,10 +493,12 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match encoding {
|
match encoding {
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
ContentEncoding::Deflate => ContentEncoder::Deflate(DeflateEncoder::new(
|
||||||
transfer,
|
transfer,
|
||||||
Compression::fast(),
|
Compression::fast(),
|
||||||
)),
|
)),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoding::Gzip => {
|
ContentEncoding::Gzip => {
|
||||||
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
|
ContentEncoder::Gzip(GzEncoder::new(transfer, Compression::fast()))
|
||||||
}
|
}
|
||||||
@@ -497,7 +511,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn streaming_encoding(
|
fn streaming_encoding(
|
||||||
buf: SharedBytes, version: Version, resp: &mut HttpResponse
|
buf: SharedBytes, version: Version, resp: &mut HttpResponse,
|
||||||
) -> TransferEncoding {
|
) -> TransferEncoding {
|
||||||
match resp.chunked() {
|
match resp.chunked() {
|
||||||
Some(true) => {
|
Some(true) => {
|
||||||
@@ -566,7 +580,9 @@ impl ContentEncoder {
|
|||||||
match *self {
|
match *self {
|
||||||
#[cfg(feature = "brotli")]
|
#[cfg(feature = "brotli")]
|
||||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
|
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
|
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
|
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
|
||||||
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
|
ContentEncoder::Identity(ref encoder) => encoder.is_eof(),
|
||||||
}
|
}
|
||||||
@@ -590,6 +606,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||||
Ok(mut writer) => {
|
Ok(mut writer) => {
|
||||||
writer.encode_eof();
|
writer.encode_eof();
|
||||||
@@ -598,6 +615,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||||
Ok(mut writer) => {
|
Ok(mut writer) => {
|
||||||
writer.encode_eof();
|
writer.encode_eof();
|
||||||
@@ -628,6 +646,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoder::Gzip(ref mut encoder) => {
|
ContentEncoder::Gzip(ref mut encoder) => {
|
||||||
match encoder.write_all(data.as_ref()) {
|
match encoder.write_all(data.as_ref()) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
@@ -637,6 +656,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "flate2")]
|
||||||
ContentEncoder::Deflate(ref mut encoder) => {
|
ContentEncoder::Deflate(ref mut encoder) => {
|
||||||
match encoder.write_all(data.as_ref()) {
|
match encoder.write_all(data.as_ref()) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@@ -19,6 +19,7 @@ use httprequest::HttpRequest;
|
|||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use payload::{Payload, PayloadStatus, PayloadWriter};
|
use payload::{Payload, PayloadStatus, PayloadWriter};
|
||||||
use pipeline::Pipeline;
|
use pipeline::Pipeline;
|
||||||
|
use uri::Url;
|
||||||
|
|
||||||
use super::encoding::PayloadType;
|
use super::encoding::PayloadType;
|
||||||
use super::h1writer::H1Writer;
|
use super::h1writer::H1Writer;
|
||||||
@@ -509,9 +510,10 @@ impl Reader {
|
|||||||
buf: &mut BytesMut, settings: &WorkerSettings<H>
|
buf: &mut BytesMut, settings: &WorkerSettings<H>
|
||||||
) -> Poll<(HttpRequest, Option<PayloadInfo>), ParseError> {
|
) -> Poll<(HttpRequest, Option<PayloadInfo>), ParseError> {
|
||||||
// Parse http message
|
// Parse http message
|
||||||
let mut has_te = false;
|
|
||||||
let mut has_upgrade = false;
|
let mut has_upgrade = false;
|
||||||
let mut has_length = false;
|
let mut chunked = false;
|
||||||
|
let mut content_length = None;
|
||||||
|
|
||||||
let msg = {
|
let msg = {
|
||||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||||
let mut headers: [httparse::Header; MAX_HEADERS] =
|
let mut headers: [httparse::Header; MAX_HEADERS] =
|
||||||
@@ -527,7 +529,7 @@ impl Reader {
|
|||||||
httparse::Status::Complete(len) => {
|
httparse::Status::Complete(len) => {
|
||||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||||
.map_err(|_| ParseError::Method)?;
|
.map_err(|_| ParseError::Method)?;
|
||||||
let path = Uri::try_from(req.path.unwrap())?;
|
let path = Url::new(Uri::try_from(req.path.unwrap())?);
|
||||||
let version = if req.version.unwrap() == 1 {
|
let version = if req.version.unwrap() == 1 {
|
||||||
Version::HTTP_11
|
Version::HTTP_11
|
||||||
} else {
|
} else {
|
||||||
@@ -545,10 +547,10 @@ impl Reader {
|
|||||||
let msg = settings.get_http_message();
|
let msg = settings.get_http_message();
|
||||||
{
|
{
|
||||||
let msg_mut = msg.get_mut();
|
let msg_mut = msg.get_mut();
|
||||||
|
msg_mut.keep_alive = version != Version::HTTP_10;
|
||||||
|
|
||||||
for header in headers[..headers_len].iter() {
|
for header in headers[..headers_len].iter() {
|
||||||
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
|
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
|
||||||
has_te = has_te || name == header::TRANSFER_ENCODING;
|
|
||||||
has_length = has_length || name == header::CONTENT_LENGTH;
|
|
||||||
has_upgrade = has_upgrade || name == header::UPGRADE;
|
has_upgrade = has_upgrade || name == header::UPGRADE;
|
||||||
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||||
let v_end = v_start + header.value.len();
|
let v_end = v_start + header.value.len();
|
||||||
@@ -557,13 +559,54 @@ impl Reader {
|
|||||||
slice.slice(v_start, v_end),
|
slice.slice(v_start, v_end),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
match name {
|
||||||
|
header::CONTENT_LENGTH => {
|
||||||
|
if let Ok(s) = value.to_str() {
|
||||||
|
if let Ok(len) = s.parse::<u64>() {
|
||||||
|
content_length = Some(len)
|
||||||
|
} else {
|
||||||
|
debug!("illegal Content-Length: {:?}", len);
|
||||||
|
return Err(ParseError::Header);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("illegal Content-Length: {:?}", len);
|
||||||
|
return Err(ParseError::Header);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// transfer-encoding
|
||||||
|
header::TRANSFER_ENCODING => {
|
||||||
|
if let Ok(s) = value.to_str() {
|
||||||
|
chunked = s.to_lowercase().contains("chunked");
|
||||||
|
} else {
|
||||||
|
return Err(ParseError::Header)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// connection keep-alive state
|
||||||
|
header::CONNECTION => {
|
||||||
|
msg_mut.keep_alive = if let Ok(conn) = value.to_str() {
|
||||||
|
if version == Version::HTTP_10
|
||||||
|
&& conn.contains("keep-alive")
|
||||||
|
{
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
version == Version::HTTP_11
|
||||||
|
&& !(conn.contains("close")
|
||||||
|
|| conn.contains("upgrade"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
msg_mut.headers.append(name, value);
|
msg_mut.headers.append(name, value);
|
||||||
} else {
|
} else {
|
||||||
return Err(ParseError::Header);
|
return Err(ParseError::Header);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_mut.uri = path;
|
msg_mut.url = path;
|
||||||
msg_mut.method = method;
|
msg_mut.method = method;
|
||||||
msg_mut.version = version;
|
msg_mut.version = version;
|
||||||
}
|
}
|
||||||
@@ -571,26 +614,12 @@ impl Reader {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||||
let decoder = if has_te && chunked(&msg.get_mut().headers)? {
|
let decoder = if chunked {
|
||||||
// Chunked encoding
|
// Chunked encoding
|
||||||
Some(Decoder::chunked())
|
Some(Decoder::chunked())
|
||||||
} else if has_length {
|
} else if let Some(len) = content_length {
|
||||||
// Content-Length
|
// Content-Length
|
||||||
let len = msg.get_ref()
|
Some(Decoder::length(len))
|
||||||
.headers
|
|
||||||
.get(header::CONTENT_LENGTH)
|
|
||||||
.unwrap();
|
|
||||||
if let Ok(s) = len.to_str() {
|
|
||||||
if let Ok(len) = s.parse::<u64>() {
|
|
||||||
Some(Decoder::length(len))
|
|
||||||
} else {
|
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
|
||||||
return Err(ParseError::Header);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
|
||||||
return Err(ParseError::Header);
|
|
||||||
}
|
|
||||||
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
|
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
|
||||||
// upgrade(websocket) or connect
|
// upgrade(websocket) or connect
|
||||||
Some(Decoder::eof())
|
Some(Decoder::eof())
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
use bytes::BufMut;
|
use bytes::BufMut;
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
|
|
||||||
use http::{Method, Version};
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{io, mem};
|
use std::{io, mem};
|
||||||
use tokio_io::AsyncWrite;
|
use tokio_io::AsyncWrite;
|
||||||
@@ -17,6 +15,8 @@ use body::{Binary, Body};
|
|||||||
use header::ContentEncoding;
|
use header::ContentEncoding;
|
||||||
use httprequest::HttpInnerMessage;
|
use httprequest::HttpInnerMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use http::{Method, Version};
|
||||||
|
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
|
||||||
|
|
||||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ use httprequest::HttpRequest;
|
|||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use payload::{Payload, PayloadStatus, PayloadWriter};
|
use payload::{Payload, PayloadStatus, PayloadWriter};
|
||||||
use pipeline::Pipeline;
|
use pipeline::Pipeline;
|
||||||
|
use uri::Url;
|
||||||
|
|
||||||
use super::encoding::PayloadType;
|
use super::encoding::PayloadType;
|
||||||
use super::h2writer::H2Writer;
|
use super::h2writer::H2Writer;
|
||||||
@@ -304,7 +305,7 @@ impl<H: 'static> Entry<H> {
|
|||||||
let (psender, payload) = Payload::new(false);
|
let (psender, payload) = Payload::new(false);
|
||||||
|
|
||||||
let msg = settings.get_http_message();
|
let msg = settings.get_http_message();
|
||||||
msg.get_mut().uri = parts.uri;
|
msg.get_mut().url = Url::new(parts.uri);
|
||||||
msg.get_mut().method = parts.method;
|
msg.get_mut().method = parts.method;
|
||||||
msg.get_mut().version = parts.version;
|
msg.get_mut().version = parts.version;
|
||||||
msg.get_mut().headers = parts.headers;
|
msg.get_mut().headers = parts.headers;
|
||||||
@@ -342,24 +343,27 @@ impl<H: 'static> Entry<H> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn poll_payload(&mut self) {
|
fn poll_payload(&mut self) {
|
||||||
if !self.flags.contains(EntryFlags::REOF) {
|
while !self.flags.contains(EntryFlags::REOF)
|
||||||
if self.payload.need_read() == PayloadStatus::Read {
|
&& self.payload.need_read() == PayloadStatus::Read
|
||||||
if let Err(err) = self.recv.release_capacity().release_capacity(32_768) {
|
{
|
||||||
self.payload.set_error(PayloadError::Http2(err))
|
|
||||||
}
|
|
||||||
} else if let Err(err) = self.recv.release_capacity().release_capacity(0) {
|
|
||||||
self.payload.set_error(PayloadError::Http2(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.recv.poll() {
|
match self.recv.poll() {
|
||||||
Ok(Async::Ready(Some(chunk))) => {
|
Ok(Async::Ready(Some(chunk))) => {
|
||||||
|
let l = chunk.len();
|
||||||
self.payload.feed_data(chunk);
|
self.payload.feed_data(chunk);
|
||||||
|
if let Err(err) = self.recv.release_capacity().release_capacity(l) {
|
||||||
|
self.payload.set_error(PayloadError::Http2(err));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
self.flags.insert(EntryFlags::REOF);
|
self.flags.insert(EntryFlags::REOF);
|
||||||
|
self.payload.feed_eof();
|
||||||
|
}
|
||||||
|
Ok(Async::NotReady) => break,
|
||||||
|
Err(err) => {
|
||||||
|
self.payload.set_error(PayloadError::Http2(err));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
|
||||||
Err(err) => self.payload.set_error(PayloadError::Http2(err)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,15 +8,14 @@ use std::sync::Arc;
|
|||||||
use std::{fmt, mem, net};
|
use std::{fmt, mem, net};
|
||||||
use time;
|
use time;
|
||||||
|
|
||||||
use super::KeepAlive;
|
|
||||||
use super::channel::Node;
|
use super::channel::Node;
|
||||||
use super::helpers;
|
use super::helpers;
|
||||||
use super::shared::{SharedBytes, SharedBytesPool};
|
use super::shared::{SharedBytes, SharedBytesPool};
|
||||||
|
use super::KeepAlive;
|
||||||
use body::Body;
|
use body::Body;
|
||||||
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
use httpresponse::{HttpResponse, HttpResponseBuilder, HttpResponsePool};
|
||||||
|
|
||||||
/// Various server settings
|
/// Various server settings
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ServerSettings {
|
pub struct ServerSettings {
|
||||||
addr: Option<net::SocketAddr>,
|
addr: Option<net::SocketAddr>,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
@@ -28,6 +27,18 @@ pub struct ServerSettings {
|
|||||||
unsafe impl Sync for ServerSettings {}
|
unsafe impl Sync for ServerSettings {}
|
||||||
unsafe impl Send for ServerSettings {}
|
unsafe impl Send for ServerSettings {}
|
||||||
|
|
||||||
|
impl Clone for ServerSettings {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ServerSettings {
|
||||||
|
addr: self.addr,
|
||||||
|
secure: self.secure,
|
||||||
|
host: self.host.clone(),
|
||||||
|
cpu_pool: self.cpu_pool.clone(),
|
||||||
|
responses: HttpResponsePool::pool(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InnerCpuPool {
|
struct InnerCpuPool {
|
||||||
cpu_pool: UnsafeCell<Option<CpuPool>>,
|
cpu_pool: UnsafeCell<Option<CpuPool>>,
|
||||||
}
|
}
|
||||||
@@ -72,7 +83,7 @@ impl Default for ServerSettings {
|
|||||||
impl ServerSettings {
|
impl ServerSettings {
|
||||||
/// Crate server settings instance
|
/// Crate server settings instance
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool
|
addr: Option<net::SocketAddr>, host: &Option<String>, secure: bool,
|
||||||
) -> ServerSettings {
|
) -> ServerSettings {
|
||||||
let host = if let Some(ref host) = *host {
|
let host = if let Some(ref host) = *host {
|
||||||
host.clone()
|
host.clone()
|
||||||
@@ -119,7 +130,7 @@ impl ServerSettings {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_response_builder(
|
pub(crate) fn get_response_builder(
|
||||||
&self, status: StatusCode
|
&self, status: StatusCode,
|
||||||
) -> HttpResponseBuilder {
|
) -> HttpResponseBuilder {
|
||||||
HttpResponsePool::get_builder(&self.responses, status)
|
HttpResponsePool::get_builder(&self.responses, status)
|
||||||
}
|
}
|
||||||
|
175
src/uri.rs
Normal file
175
src/uri.rs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
use http::Uri;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
1234567890
|
||||||
|
-._~";
|
||||||
|
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
1234567890
|
||||||
|
-._~
|
||||||
|
!$'()*,";
|
||||||
|
const QS: &[u8] = b"+&=;b";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn bit_at(array: &[u8], ch: u8) -> bool {
|
||||||
|
array[(ch >> 3) as usize] & (1 << (ch & 7)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_bit(array: &mut [u8], ch: u8) {
|
||||||
|
array[(ch >> 3) as usize] |= 1 << (ch & 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct Url {
|
||||||
|
uri: Uri,
|
||||||
|
path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Url {
|
||||||
|
pub fn new(uri: Uri) -> Url {
|
||||||
|
let path = DEFAULT_QUOTER.requote(uri.path().as_bytes());
|
||||||
|
|
||||||
|
Url { uri, path }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uri(&self) -> &Uri {
|
||||||
|
&self.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||||
|
&mut self.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
if let Some(ref s) = self.path {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
self.uri.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Quoter {
|
||||||
|
safe_table: [u8; 16],
|
||||||
|
protected_table: [u8; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Quoter {
|
||||||
|
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
||||||
|
let mut q = Quoter {
|
||||||
|
safe_table: [0; 16],
|
||||||
|
protected_table: [0; 16],
|
||||||
|
};
|
||||||
|
|
||||||
|
// prepare safe table
|
||||||
|
for i in 0..128 {
|
||||||
|
if ALLOWED.contains(&i) {
|
||||||
|
set_bit(&mut q.safe_table, i);
|
||||||
|
}
|
||||||
|
if QS.contains(&i) {
|
||||||
|
set_bit(&mut q.safe_table, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ch in safe {
|
||||||
|
set_bit(&mut q.safe_table, *ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare protected table
|
||||||
|
for ch in protected {
|
||||||
|
set_bit(&mut q.safe_table, *ch);
|
||||||
|
set_bit(&mut q.protected_table, *ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
q
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
||||||
|
let mut has_pct = 0;
|
||||||
|
let mut pct = [b'%', 0, 0];
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut cloned: Option<Vec<u8>> = None;
|
||||||
|
|
||||||
|
let len = val.len();
|
||||||
|
while idx < len {
|
||||||
|
let ch = val[idx];
|
||||||
|
|
||||||
|
if has_pct != 0 {
|
||||||
|
pct[has_pct] = val[idx];
|
||||||
|
has_pct += 1;
|
||||||
|
if has_pct == 3 {
|
||||||
|
has_pct = 0;
|
||||||
|
let buf = cloned.as_mut().unwrap();
|
||||||
|
|
||||||
|
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
||||||
|
if ch < 128 {
|
||||||
|
if bit_at(&self.protected_table, ch) {
|
||||||
|
buf.extend_from_slice(&pct);
|
||||||
|
idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if bit_at(&self.safe_table, ch) {
|
||||||
|
buf.push(ch);
|
||||||
|
idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.push(ch);
|
||||||
|
} else {
|
||||||
|
buf.extend_from_slice(&pct[..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ch == b'%' {
|
||||||
|
has_pct = 1;
|
||||||
|
if cloned.is_none() {
|
||||||
|
let mut c = Vec::with_capacity(len);
|
||||||
|
c.extend_from_slice(&val[..idx]);
|
||||||
|
cloned = Some(c);
|
||||||
|
}
|
||||||
|
} else if let Some(ref mut cloned) = cloned {
|
||||||
|
cloned.push(ch)
|
||||||
|
}
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(data) = cloned {
|
||||||
|
Some(unsafe { String::from_utf8_unchecked(data) })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from_hex(v: u8) -> Option<u8> {
|
||||||
|
if v >= b'0' && v <= b'9' {
|
||||||
|
Some(v - 0x30) // ord('0') == 0x30
|
||||||
|
} else if v >= b'A' && v <= b'F' {
|
||||||
|
Some(v - 0x41 + 10) // ord('A') == 0x41
|
||||||
|
} else if v > b'a' && v <= b'f' {
|
||||||
|
Some(v - 0x61 + 10) // ord('a') == 0x61
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
||||||
|
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
|
||||||
|
}
|
@@ -310,10 +310,15 @@ where
|
|||||||
}
|
}
|
||||||
OpCode::Close => {
|
OpCode::Close => {
|
||||||
self.closed = true;
|
self.closed = true;
|
||||||
let code = NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
|
let close_code = if payload.len() >= 2 {
|
||||||
Ok(Async::Ready(Some(Message::Close(CloseCode::from(
|
let raw_code =
|
||||||
code,
|
NetworkEndian::read_uint(payload.as_ref(), 2) as u16;
|
||||||
)))))
|
CloseCode::from(raw_code)
|
||||||
|
} else {
|
||||||
|
CloseCode::Status
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Async::Ready(Some(Message::Close(close_code))))
|
||||||
}
|
}
|
||||||
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
|
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
|
||||||
String::from_utf8_lossy(payload.as_ref()).into(),
|
String::from_utf8_lossy(payload.as_ref()).into(),
|
||||||
|
@@ -148,3 +148,27 @@ fn test_non_ascii_route() {
|
|||||||
let bytes = srv.execute(response.body()).unwrap();
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
assert_eq!(bytes, Bytes::from_static(b"success"));
|
assert_eq!(bytes, Bytes::from_static(b"success"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unsafe_path_route() {
|
||||||
|
let mut srv = test::TestServer::new(|app| {
|
||||||
|
app.resource("/test/{url}", |r| {
|
||||||
|
r.f(|r| format!("success: {}", &r.match_info()["url"]))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// client request
|
||||||
|
let request = srv.get()
|
||||||
|
.uri(srv.url("/test/http%3A%2F%2Fexample.com"))
|
||||||
|
.finish()
|
||||||
|
.unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
// read response
|
||||||
|
let bytes = srv.execute(response.body()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
bytes,
|
||||||
|
Bytes::from_static(b"success: http:%2F%2Fexample.com")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@@ -18,7 +18,7 @@ use flate2::Compression;
|
|||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
|
||||||
use futures::stream::once;
|
use futures::stream::once;
|
||||||
use futures::{Future, Stream};
|
use futures::{future, Future, Stream};
|
||||||
use h2::client as h2client;
|
use h2::client as h2client;
|
||||||
use modhttp::Request;
|
use modhttp::Request;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@@ -814,7 +814,7 @@ fn test_h2() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
let _res = core.run(tcp);
|
let _res = core.run(tcp);
|
||||||
// assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref()));
|
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -915,3 +915,34 @@ fn test_resource_middlewares() {
|
|||||||
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
assert_eq!(num2.load(Ordering::Relaxed), 1);
|
||||||
// assert_eq!(num3.load(Ordering::Relaxed), 1);
|
// assert_eq!(num3.load(Ordering::Relaxed), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
|
||||||
|
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_middleware_async_error() {
|
||||||
|
let req = Arc::new(AtomicUsize::new(0));
|
||||||
|
let resp = Arc::new(AtomicUsize::new(0));
|
||||||
|
let fin = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
let act_req = Arc::clone(&req);
|
||||||
|
let act_resp = Arc::clone(&resp);
|
||||||
|
let act_fin = Arc::clone(&fin);
|
||||||
|
|
||||||
|
let mut srv = test::TestServer::new(move |app| {
|
||||||
|
app.middleware(MiddlewareTest {
|
||||||
|
start: Arc::clone(&act_req),
|
||||||
|
response: Arc::clone(&act_resp),
|
||||||
|
finish: Arc::clone(&act_fin),
|
||||||
|
}).handler(index_test_middleware_async_error)
|
||||||
|
});
|
||||||
|
|
||||||
|
let request = srv.get().finish().unwrap();
|
||||||
|
let response = srv.execute(request.send()).unwrap();
|
||||||
|
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
|
assert_eq!(req.load(Ordering::Relaxed), 1);
|
||||||
|
assert_eq!(resp.load(Ordering::Relaxed), 1);
|
||||||
|
assert_eq!(fin.load(Ordering::Relaxed), 1);
|
||||||
|
}
|
||||||
|
@@ -60,6 +60,16 @@ fn test_simple() {
|
|||||||
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal)));
|
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_close_code() {
|
||||||
|
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
|
||||||
|
let (reader, mut writer) = srv.ws().unwrap();
|
||||||
|
|
||||||
|
writer.close(ws::CloseCode::Empty, "");
|
||||||
|
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||||
|
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Status)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_large_text() {
|
fn test_large_text() {
|
||||||
let data = rand::thread_rng()
|
let data = rand::thread_rng()
|
||||||
|
Reference in New Issue
Block a user