mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-08 03:46:30 +02:00
Compare commits
12 Commits
http-v0.2.
...
files-v0.1
Author | SHA1 | Date | |
---|---|---|---|
c96068e78d | |||
7bca1f7d8d | |||
3618a84164 | |||
03ca408e94 | |||
e53e9c8ba3 | |||
941241c5f0 | |||
f8320fedd8 | |||
c808364c07 | |||
cccd829656 | |||
3650f6d7b8 | |||
6b7df6b242 | |||
b6ff786ed3 |
13
CHANGES.md
13
CHANGES.md
@ -1,6 +1,17 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
## [1.0.5] - 2019-07-xx
|
## [1.0.6] - 2019-xx-xx
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* Re-implement Host predicate (#989)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
* `Query` payload made `pub`. Allows user to pattern-match the payload.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.5] - 2019-07-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web"
|
name = "actix-web"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
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"
|
||||||
@ -78,11 +78,11 @@ actix-utils = "0.4.4"
|
|||||||
actix-router = "0.1.5"
|
actix-router = "0.1.5"
|
||||||
actix-rt = "0.2.4"
|
actix-rt = "0.2.4"
|
||||||
actix-web-codegen = "0.1.2"
|
actix-web-codegen = "0.1.2"
|
||||||
actix-http = "0.2.6"
|
actix-http = "0.2.7"
|
||||||
actix-server = "0.6.0"
|
actix-server = "0.6.0"
|
||||||
actix-server-config = "0.1.2"
|
actix-server-config = "0.1.2"
|
||||||
actix-threadpool = "0.1.1"
|
actix-threadpool = "0.1.1"
|
||||||
awc = { version = "0.2.1", optional = true }
|
awc = { version = "0.2.2", optional = true }
|
||||||
|
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
derive_more = "0.15.0"
|
derive_more = "0.15.0"
|
||||||
@ -107,7 +107,7 @@ rustls = { version = "0.15", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix = { version = "0.8.3" }
|
actix = { version = "0.8.3" }
|
||||||
actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] }
|
actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] }
|
||||||
actix-http-test = { version = "0.2.2", features=["ssl"] }
|
actix-http-test = { version = "0.2.4", features=["ssl"] }
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
env_logger = "0.6"
|
env_logger = "0.6"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.1.4] - 2019-07-20
|
||||||
|
|
||||||
|
* Allow to disable `Content-Disposition` header #686
|
||||||
|
|
||||||
|
|
||||||
## [0.1.3] - 2019-06-28
|
## [0.1.3] - 2019-06-28
|
||||||
|
|
||||||
* Do not set `Content-Length` header, let actix-http set it #930
|
* Do not set `Content-Length` header, let actix-http set it #930
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-files"
|
name = "actix-files"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Static files support for actix web."
|
description = "Static files support for actix web."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -314,23 +314,32 @@ impl Files {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
///Specifies whether to use ETag or not.
|
/// Specifies whether to use ETag or not.
|
||||||
///
|
///
|
||||||
///Default is true.
|
/// Default is true.
|
||||||
pub fn use_etag(mut self, value: bool) -> Self {
|
pub fn use_etag(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::ETAG, value);
|
self.file_flags.set(named::Flags::ETAG, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
///Specifies whether to use Last-Modified or not.
|
/// Specifies whether to use Last-Modified or not.
|
||||||
///
|
///
|
||||||
///Default is true.
|
/// Default is true.
|
||||||
pub fn use_last_modified(mut self, value: bool) -> Self {
|
pub fn use_last_modified(mut self, value: bool) -> Self {
|
||||||
self.file_flags.set(named::Flags::LAST_MD, value);
|
self.file_flags.set(named::Flags::LAST_MD, value);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable `Content-Disposition` header.
|
||||||
|
///
|
||||||
|
/// By default Content-Disposition` header is enabled.
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_content_disposition(mut self) -> Self {
|
||||||
|
self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets default handler which is used when no matched file could be found.
|
/// Sets default handler which is used when no matched file could be found.
|
||||||
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
pub fn default_handler<F, U>(mut self, f: F) -> Self
|
||||||
where
|
where
|
||||||
@ -638,6 +647,33 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_named_file_content_disposition() {
|
||||||
|
assert!(NamedFile::open("test--").is_err());
|
||||||
|
let mut file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
{
|
||||||
|
file.file();
|
||||||
|
let _f: &File = &file;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let _f: &mut File = &mut file;
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
let resp = file.respond_to(&req).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get(header::CONTENT_DISPOSITION).unwrap(),
|
||||||
|
"inline; filename=\"Cargo.toml\""
|
||||||
|
);
|
||||||
|
|
||||||
|
let file = NamedFile::open("Cargo.toml")
|
||||||
|
.unwrap()
|
||||||
|
.disable_content_disposition();
|
||||||
|
let req = TestRequest::default().to_http_request();
|
||||||
|
let resp = file.respond_to(&req).unwrap();
|
||||||
|
assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_named_file_set_content_type() {
|
fn test_named_file_set_content_type() {
|
||||||
let mut file = NamedFile::open("Cargo.toml")
|
let mut file = NamedFile::open("Cargo.toml")
|
||||||
|
@ -23,9 +23,10 @@ use crate::range::HttpRange;
|
|||||||
use crate::ChunkedReadFile;
|
use crate::ChunkedReadFile;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub(crate) struct Flags: u32 {
|
pub(crate) struct Flags: u8 {
|
||||||
const ETAG = 0b0000_0001;
|
const ETAG = 0b0000_0001;
|
||||||
const LAST_MD = 0b0000_0010;
|
const LAST_MD = 0b0000_0010;
|
||||||
|
const CONTENT_DISPOSITION = 0b0000_0100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,13 +41,13 @@ impl Default for Flags {
|
|||||||
pub struct NamedFile {
|
pub struct NamedFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
file: File,
|
file: File,
|
||||||
|
modified: Option<SystemTime>,
|
||||||
|
pub(crate) md: Metadata,
|
||||||
|
pub(crate) flags: Flags,
|
||||||
|
pub(crate) status_code: StatusCode,
|
||||||
pub(crate) content_type: mime::Mime,
|
pub(crate) content_type: mime::Mime,
|
||||||
pub(crate) content_disposition: header::ContentDisposition,
|
pub(crate) content_disposition: header::ContentDisposition,
|
||||||
pub(crate) md: Metadata,
|
|
||||||
modified: Option<SystemTime>,
|
|
||||||
pub(crate) encoding: Option<ContentEncoding>,
|
pub(crate) encoding: Option<ContentEncoding>,
|
||||||
pub(crate) status_code: StatusCode,
|
|
||||||
pub(crate) flags: Flags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NamedFile {
|
impl NamedFile {
|
||||||
@ -172,11 +173,21 @@ impl NamedFile {
|
|||||||
/// sent to the peer. By default the disposition is `inline` for text,
|
/// sent to the peer. By default the disposition is `inline` for text,
|
||||||
/// image, and video content types, and `attachment` otherwise, and
|
/// image, and video content types, and `attachment` otherwise, and
|
||||||
/// the filename is taken from the path provided in the `open` method
|
/// the filename is taken from the path provided in the `open` method
|
||||||
/// after converting it to UTF-8 using
|
/// after converting it to UTF-8 using.
|
||||||
/// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy).
|
/// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
|
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
|
||||||
self.content_disposition = cd;
|
self.content_disposition = cd;
|
||||||
|
self.flags.insert(Flags::CONTENT_DISPOSITION);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable `Content-Disposition` header.
|
||||||
|
///
|
||||||
|
/// By default Content-Disposition` header is enabled.
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_content_disposition(mut self) -> Self {
|
||||||
|
self.flags.remove(Flags::CONTENT_DISPOSITION);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,10 +305,12 @@ impl Responder for NamedFile {
|
|||||||
if self.status_code != StatusCode::OK {
|
if self.status_code != StatusCode::OK {
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut resp = HttpResponse::build(self.status_code);
|
||||||
resp.set(header::ContentType(self.content_type.clone()))
|
resp.set(header::ContentType(self.content_type.clone()))
|
||||||
.header(
|
.if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
|
||||||
header::CONTENT_DISPOSITION,
|
res.header(
|
||||||
self.content_disposition.to_string(),
|
header::CONTENT_DISPOSITION,
|
||||||
);
|
self.content_disposition.to_string(),
|
||||||
|
);
|
||||||
|
});
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.encoding(current_encoding);
|
resp.encoding(current_encoding);
|
||||||
}
|
}
|
||||||
@ -368,10 +381,12 @@ impl Responder for NamedFile {
|
|||||||
|
|
||||||
let mut resp = HttpResponse::build(self.status_code);
|
let mut resp = HttpResponse::build(self.status_code);
|
||||||
resp.set(header::ContentType(self.content_type.clone()))
|
resp.set(header::ContentType(self.content_type.clone()))
|
||||||
.header(
|
.if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
|
||||||
header::CONTENT_DISPOSITION,
|
res.header(
|
||||||
self.content_disposition.to_string(),
|
header::CONTENT_DISPOSITION,
|
||||||
);
|
self.content_disposition.to_string(),
|
||||||
|
);
|
||||||
|
});
|
||||||
// default compressing
|
// default compressing
|
||||||
if let Some(current_encoding) = self.encoding {
|
if let Some(current_encoding) = self.encoding {
|
||||||
resp.encoding(current_encoding);
|
resp.encoding(current_encoding);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-framed"
|
name = "actix-framed"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix framed app server"
|
description = "Actix framed app server"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -21,18 +21,18 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-codec = "0.1.2"
|
actix-codec = "0.1.2"
|
||||||
actix-service = "0.4.0"
|
actix-service = "0.4.1"
|
||||||
actix-utils = "0.4.0"
|
|
||||||
actix-router = "0.1.2"
|
actix-router = "0.1.2"
|
||||||
actix-rt = "0.2.2"
|
actix-rt = "0.2.2"
|
||||||
actix-http = "0.2.0"
|
actix-http = "0.2.7"
|
||||||
actix-server-config = "0.1.1"
|
actix-server-config = "0.1.2"
|
||||||
|
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
futures = "0.1.25"
|
futures = "0.1.25"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-server = { version = "0.5.0", features=["ssl"] }
|
actix-server = { version = "0.6.0", features=["ssl"] }
|
||||||
actix-connect = { version = "0.2.0", features=["ssl"] }
|
actix-connect = { version = "0.2.0", features=["ssl"] }
|
||||||
actix-http-test = { version = "0.2.0", features=["ssl"] }
|
actix-http-test = { version = "0.2.4", features=["ssl"] }
|
||||||
|
actix-utils = "0.4.4"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [0.2.1] - 2019-07-20
|
||||||
|
|
||||||
|
* Remove unneeded actix-utils dependency
|
||||||
|
|
||||||
|
|
||||||
## [0.2.0] - 2019-05-12
|
## [0.2.0] - 2019-05-12
|
||||||
|
|
||||||
* Update dependencies
|
* Update dependencies
|
||||||
|
@ -6,7 +6,6 @@ use actix_http::{Error, Request, Response};
|
|||||||
use actix_router::{Path, Router, Url};
|
use actix_router::{Path, Router, Url};
|
||||||
use actix_server_config::ServerConfig;
|
use actix_server_config::ServerConfig;
|
||||||
use actix_service::{IntoNewService, NewService, Service};
|
use actix_service::{IntoNewService, NewService, Service};
|
||||||
use actix_utils::cloneable::CloneableService;
|
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
|
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
|
||||||
@ -100,7 +99,7 @@ where
|
|||||||
type Response = ();
|
type Response = ();
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type InitError = ();
|
type InitError = ();
|
||||||
type Service = CloneableService<FramedAppService<T, S>>;
|
type Service = FramedAppService<T, S>;
|
||||||
type Future = CreateService<T, S>;
|
type Future = CreateService<T, S>;
|
||||||
|
|
||||||
fn new_service(&self, _: &ServerConfig) -> Self::Future {
|
fn new_service(&self, _: &ServerConfig) -> Self::Future {
|
||||||
@ -138,7 +137,7 @@ impl<S: 'static, T: 'static> Future for CreateService<T, S>
|
|||||||
where
|
where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
{
|
{
|
||||||
type Item = CloneableService<FramedAppService<T, S>>;
|
type Item = FramedAppService<T, S>;
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
@ -177,10 +176,10 @@ where
|
|||||||
}
|
}
|
||||||
router
|
router
|
||||||
});
|
});
|
||||||
Ok(Async::Ready(CloneableService::new(FramedAppService {
|
Ok(Async::Ready(FramedAppService {
|
||||||
router: router.finish(),
|
router: router.finish(),
|
||||||
state: self.state.clone(),
|
state: self.state.clone(),
|
||||||
})))
|
}))
|
||||||
} else {
|
} else {
|
||||||
Ok(Async::NotReady)
|
Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
#![allow(
|
#![allow(clippy::type_complexity, clippy::new_without_default, dead_code)]
|
||||||
clippy::type_complexity,
|
|
||||||
clippy::new_without_default,
|
|
||||||
dead_code,
|
|
||||||
deprecated
|
|
||||||
)]
|
|
||||||
mod app;
|
mod app;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod request;
|
mod request;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-http"
|
name = "actix-http"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix http primitives"
|
description = "Actix http primitives"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -48,7 +48,7 @@ actix-service = "0.4.1"
|
|||||||
actix-codec = "0.1.2"
|
actix-codec = "0.1.2"
|
||||||
actix-connect = "0.2.1"
|
actix-connect = "0.2.1"
|
||||||
actix-utils = "0.4.4"
|
actix-utils = "0.4.4"
|
||||||
actix-server-config = "0.1.1"
|
actix-server-config = "0.1.2"
|
||||||
actix-threadpool = "0.1.1"
|
actix-threadpool = "0.1.1"
|
||||||
|
|
||||||
base64 = "0.10"
|
base64 = "0.10"
|
||||||
@ -97,9 +97,9 @@ chrono = "0.4.6"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-rt = "0.2.2"
|
actix-rt = "0.2.2"
|
||||||
actix-server = { version = "0.5.0", features=["ssl"] }
|
actix-server = { version = "0.6.0", features=["ssl"] }
|
||||||
actix-connect = { version = "0.2.0", features=["ssl"] }
|
actix-connect = { version = "0.2.0", features=["ssl"] }
|
||||||
actix-http-test = { version = "0.2.0", features=["ssl"] }
|
actix-http-test = { version = "0.2.4", features=["ssl"] }
|
||||||
env_logger = "0.6"
|
env_logger = "0.6"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
openssl = { version="0.10" }
|
openssl = { version="0.10" }
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
## [1.0.2] - 2019-07-20
|
||||||
|
|
||||||
|
* Add `ws::start_with_addr()`, returning the address of the created actor, along
|
||||||
|
with the `HttpResponse`.
|
||||||
|
|
||||||
|
* Add support for specifying protocols on websocket handshake #835
|
||||||
|
|
||||||
## [1.0.1] - 2019-06-28
|
## [1.0.1] - 2019-06-28
|
||||||
|
|
||||||
* Allow to use custom ws codec with `WebsocketContext` #925
|
* Allow to use custom ws codec with `WebsocketContext` #925
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "actix-web-actors"
|
name = "actix-web-actors"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix actors support for actix web framework."
|
description = "Actix actors support for actix web framework."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -27,4 +27,4 @@ futures = "0.1.25"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.6"
|
env_logger = "0.6"
|
||||||
actix-http-test = { version = "0.2.0", features=["ssl"] }
|
actix-http-test = { version = "0.2.4", features=["ssl"] }
|
||||||
|
@ -35,15 +35,68 @@ where
|
|||||||
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Do websocket handshake and start ws actor.
|
||||||
|
///
|
||||||
|
/// `req` is an HTTP Request that should be requesting a websocket protocol
|
||||||
|
/// change. `stream` should be a `Bytes` stream (such as
|
||||||
|
/// `actix_web::web::Payload`) that contains a stream of the body request.
|
||||||
|
///
|
||||||
|
/// If there is a problem with the handshake, an error is returned.
|
||||||
|
///
|
||||||
|
/// If successful, returns a pair where the first item is an address for the
|
||||||
|
/// created actor and the second item is the response that should be returned
|
||||||
|
/// from the websocket request.
|
||||||
|
pub fn start_with_addr<A, T>(
|
||||||
|
actor: A,
|
||||||
|
req: &HttpRequest,
|
||||||
|
stream: T,
|
||||||
|
) -> Result<(Addr<A>, HttpResponse), Error>
|
||||||
|
where
|
||||||
|
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Message, ProtocolError>,
|
||||||
|
T: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||||
|
{
|
||||||
|
let mut res = handshake(req)?;
|
||||||
|
let (addr, out_stream) = WebsocketContext::create_with_addr(actor, stream);
|
||||||
|
Ok((addr, res.streaming(out_stream)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do websocket handshake and start ws actor.
|
||||||
|
///
|
||||||
|
/// `protocols` is a sequence of known protocols.
|
||||||
|
pub fn start_with_protocols<A, T>(
|
||||||
|
actor: A,
|
||||||
|
protocols: &[&str],
|
||||||
|
req: &HttpRequest,
|
||||||
|
stream: T,
|
||||||
|
) -> Result<HttpResponse, Error>
|
||||||
|
where
|
||||||
|
A: Actor<Context = WebsocketContext<A>> + StreamHandler<Message, ProtocolError>,
|
||||||
|
T: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||||
|
{
|
||||||
|
let mut res = handshake_with_protocols(req, protocols)?;
|
||||||
|
Ok(res.streaming(WebsocketContext::create(actor, stream)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepare `WebSocket` handshake response.
|
||||||
|
///
|
||||||
|
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||||
|
/// It does not perform any IO.
|
||||||
|
pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeError> {
|
||||||
|
handshake_with_protocols(req, &[])
|
||||||
|
}
|
||||||
|
|
||||||
/// Prepare `WebSocket` handshake response.
|
/// Prepare `WebSocket` handshake response.
|
||||||
///
|
///
|
||||||
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||||
/// It does not perform any IO.
|
/// It does not perform any IO.
|
||||||
///
|
///
|
||||||
// /// `protocols` is a sequence of known protocols. On successful handshake,
|
/// `protocols` is a sequence of known protocols. On successful handshake,
|
||||||
// /// the returned response headers contain the first protocol in this list
|
/// the returned response headers contain the first protocol in this list
|
||||||
// /// which the server also knows.
|
/// which the server also knows.
|
||||||
pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeError> {
|
pub fn handshake_with_protocols(
|
||||||
|
req: &HttpRequest,
|
||||||
|
protocols: &[&str],
|
||||||
|
) -> Result<HttpResponseBuilder, HandshakeError> {
|
||||||
// WebSocket accepts only GET
|
// WebSocket accepts only GET
|
||||||
if *req.method() != Method::GET {
|
if *req.method() != Method::GET {
|
||||||
return Err(HandshakeError::GetMethodRequired);
|
return Err(HandshakeError::GetMethodRequired);
|
||||||
@ -92,11 +145,28 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
|
|||||||
hash_key(key.as_ref())
|
hash_key(key.as_ref())
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
// check requested protocols
|
||||||
|
let protocol =
|
||||||
|
req.headers()
|
||||||
|
.get(&header::SEC_WEBSOCKET_PROTOCOL)
|
||||||
|
.and_then(|req_protocols| {
|
||||||
|
let req_protocols = req_protocols.to_str().ok()?;
|
||||||
|
req_protocols
|
||||||
|
.split(", ")
|
||||||
|
.find(|req_p| protocols.iter().any(|p| p == req_p))
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
.upgrade("websocket")
|
.upgrade("websocket")
|
||||||
.header(header::TRANSFER_ENCODING, "chunked")
|
.header(header::TRANSFER_ENCODING, "chunked")
|
||||||
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
.header(header::SEC_WEBSOCKET_ACCEPT, key.as_str())
|
||||||
.take())
|
.take();
|
||||||
|
|
||||||
|
if let Some(protocol) = protocol {
|
||||||
|
response.header(&header::SEC_WEBSOCKET_PROTOCOL, protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execution context for `WebSockets` actors
|
/// Execution context for `WebSockets` actors
|
||||||
@ -168,6 +238,24 @@ where
|
|||||||
#[inline]
|
#[inline]
|
||||||
/// Create a new Websocket context from a request and an actor
|
/// Create a new Websocket context from a request and an actor
|
||||||
pub fn create<S>(actor: A, stream: S) -> impl Stream<Item = Bytes, Error = Error>
|
pub fn create<S>(actor: A, stream: S) -> impl Stream<Item = Bytes, Error = Error>
|
||||||
|
where
|
||||||
|
A: StreamHandler<Message, ProtocolError>,
|
||||||
|
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||||
|
{
|
||||||
|
let (_, stream) = WebsocketContext::create_with_addr(actor, stream);
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
/// Create a new Websocket context from a request and an actor.
|
||||||
|
///
|
||||||
|
/// Returns a pair, where the first item is an addr for the created actor,
|
||||||
|
/// and the second item is a stream intended to be set as part of the
|
||||||
|
/// response via `HttpResponseBuilder::streaming()`.
|
||||||
|
pub fn create_with_addr<S>(
|
||||||
|
actor: A,
|
||||||
|
stream: S,
|
||||||
|
) -> (Addr<A>, impl Stream<Item = Bytes, Error = Error>)
|
||||||
where
|
where
|
||||||
A: StreamHandler<Message, ProtocolError>,
|
A: StreamHandler<Message, ProtocolError>,
|
||||||
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||||
@ -179,7 +267,9 @@ where
|
|||||||
};
|
};
|
||||||
ctx.add_stream(WsStream::new(stream, Codec::new()));
|
ctx.add_stream(WsStream::new(stream, Codec::new()));
|
||||||
|
|
||||||
WebsocketContextFut::new(ctx, actor, mb, Codec::new())
|
let addr = ctx.address();
|
||||||
|
|
||||||
|
(addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -564,5 +654,87 @@ mod tests {
|
|||||||
StatusCode::SWITCHING_PROTOCOLS,
|
StatusCode::SWITCHING_PROTOCOLS,
|
||||||
handshake(&req).unwrap().finish().status()
|
handshake(&req).unwrap().finish().status()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(
|
||||||
|
header::UPGRADE,
|
||||||
|
header::HeaderValue::from_static("websocket"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::CONNECTION,
|
||||||
|
header::HeaderValue::from_static("upgrade"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
|
header::HeaderValue::from_static("13"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::SEC_WEBSOCKET_KEY,
|
||||||
|
header::HeaderValue::from_static("13"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::SEC_WEBSOCKET_PROTOCOL,
|
||||||
|
header::HeaderValue::from_static("graphql"),
|
||||||
|
)
|
||||||
|
.to_http_request();
|
||||||
|
|
||||||
|
let protocols = ["graphql"];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
StatusCode::SWITCHING_PROTOCOLS,
|
||||||
|
handshake_with_protocols(&req, &protocols)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.status()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Some(&header::HeaderValue::from_static("graphql")),
|
||||||
|
handshake_with_protocols(&req, &protocols)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.headers()
|
||||||
|
.get(&header::SEC_WEBSOCKET_PROTOCOL)
|
||||||
|
);
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(
|
||||||
|
header::UPGRADE,
|
||||||
|
header::HeaderValue::from_static("websocket"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::CONNECTION,
|
||||||
|
header::HeaderValue::from_static("upgrade"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::SEC_WEBSOCKET_VERSION,
|
||||||
|
header::HeaderValue::from_static("13"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::SEC_WEBSOCKET_KEY,
|
||||||
|
header::HeaderValue::from_static("13"),
|
||||||
|
)
|
||||||
|
.header(
|
||||||
|
header::SEC_WEBSOCKET_PROTOCOL,
|
||||||
|
header::HeaderValue::from_static("p1, p2, p3"),
|
||||||
|
)
|
||||||
|
.to_http_request();
|
||||||
|
|
||||||
|
let protocols = vec!["p3", "p2"];
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
StatusCode::SWITCHING_PROTOCOLS,
|
||||||
|
handshake_with_protocols(&req, &protocols)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.status()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Some(&header::HeaderValue::from_static("p2")),
|
||||||
|
handshake_with_protocols(&req, &protocols)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.headers()
|
||||||
|
.get(&header::SEC_WEBSOCKET_PROTOCOL)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
src/guard.rs
166
src/guard.rs
@ -26,7 +26,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use actix_http::http::{self, header, HttpTryFrom};
|
use actix_http::http::{self, header, uri::Uri, HttpTryFrom};
|
||||||
use actix_http::RequestHead;
|
use actix_http::RequestHead;
|
||||||
|
|
||||||
/// Trait defines resource guards. Guards are used for routes selection.
|
/// Trait defines resource guards. Guards are used for routes selection.
|
||||||
@ -256,45 +256,68 @@ impl Guard for HeaderGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Return predicate that matches if request contains specified Host name.
|
/// Return predicate that matches if request contains specified Host name.
|
||||||
// ///
|
///
|
||||||
// /// ```rust,ignore
|
/// ```rust,ignore
|
||||||
// /// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
// /// use actix_web::{pred, App, HttpResponse};
|
/// use actix_web::{guard::Host, App, HttpResponse};
|
||||||
// ///
|
///
|
||||||
// /// fn main() {
|
/// fn main() {
|
||||||
// /// App::new().resource("/index.html", |r| {
|
/// App::new().resource("/index.html", |r| {
|
||||||
// /// r.route()
|
/// r.route()
|
||||||
// /// .guard(pred::Host("www.rust-lang.org"))
|
/// .guard(Host("www.rust-lang.org"))
|
||||||
// /// .f(|_| HttpResponse::MethodNotAllowed())
|
/// .f(|_| HttpResponse::MethodNotAllowed())
|
||||||
// /// });
|
/// });
|
||||||
// /// }
|
/// }
|
||||||
// /// ```
|
/// ```
|
||||||
// pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
|
pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
|
||||||
// HostGuard(host.as_ref().to_string(), None)
|
HostGuard(host.as_ref().to_string(), None)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// #[doc(hidden)]
|
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
|
||||||
// pub struct HostGuard(String, Option<String>);
|
use core::str::FromStr;
|
||||||
|
let host_value = req.headers.get(header::HOST)?;
|
||||||
|
let host = host_value.to_str().ok()?;
|
||||||
|
let uri = Uri::from_str(host).ok()?;
|
||||||
|
Some(uri)
|
||||||
|
}
|
||||||
|
|
||||||
// impl HostGuard {
|
#[doc(hidden)]
|
||||||
// /// Set reuest scheme to match
|
pub struct HostGuard(String, Option<String>);
|
||||||
// pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
|
|
||||||
// self.1 = Some(scheme.as_ref().to_string())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Guard for HostGuard {
|
impl HostGuard {
|
||||||
// fn check(&self, _req: &RequestHead) -> bool {
|
/// Set request scheme to match
|
||||||
// // let info = req.connection_info();
|
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
|
||||||
// // if let Some(ref scheme) = self.1 {
|
self.1 = Some(scheme.as_ref().to_string());
|
||||||
// // self.0 == info.host() && scheme == info.scheme()
|
self
|
||||||
// // } else {
|
}
|
||||||
// // self.0 == info.host()
|
}
|
||||||
// // }
|
|
||||||
// false
|
impl Guard for HostGuard {
|
||||||
// }
|
fn check(&self, req: &RequestHead) -> bool {
|
||||||
// }
|
let req_host_uri = if let Some(uri) = get_host_uri(req) {
|
||||||
|
uri
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(uri_host) = req_host_uri.host() {
|
||||||
|
if self.0 != uri_host {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref scheme) = self.1 {
|
||||||
|
if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() {
|
||||||
|
return scheme == req_host_uri_scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@ -318,21 +341,64 @@ mod tests {
|
|||||||
assert!(!pred.check(req.head()));
|
assert!(!pred.check(req.head()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn test_host() {
|
fn test_host() {
|
||||||
// let req = TestServiceRequest::default()
|
let req = TestRequest::default()
|
||||||
// .header(
|
.header(
|
||||||
// header::HOST,
|
header::HOST,
|
||||||
// header::HeaderValue::from_static("www.rust-lang.org"),
|
header::HeaderValue::from_static("www.rust-lang.org"),
|
||||||
// )
|
)
|
||||||
// .request();
|
.to_http_request();
|
||||||
|
|
||||||
// let pred = Host("www.rust-lang.org");
|
let pred = Host("www.rust-lang.org");
|
||||||
// assert!(pred.check(&req));
|
assert!(pred.check(req.head()));
|
||||||
|
|
||||||
// let pred = Host("localhost");
|
let pred = Host("www.rust-lang.org").scheme("https");
|
||||||
// assert!(!pred.check(&req));
|
assert!(pred.check(req.head()));
|
||||||
// }
|
|
||||||
|
let pred = Host("blog.rust-lang.org");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("crates.io");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("localhost");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_host_scheme() {
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.header(
|
||||||
|
header::HOST,
|
||||||
|
header::HeaderValue::from_static("https://www.rust-lang.org"),
|
||||||
|
)
|
||||||
|
.to_http_request();
|
||||||
|
|
||||||
|
let pred = Host("www.rust-lang.org").scheme("https");
|
||||||
|
assert!(pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("www.rust-lang.org");
|
||||||
|
assert!(pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("www.rust-lang.org").scheme("http");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("blog.rust-lang.org");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("blog.rust-lang.org").scheme("https");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("crates.io").scheme("https");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
|
||||||
|
let pred = Host("localhost");
|
||||||
|
assert!(!pred.check(req.head()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_methods() {
|
fn test_methods() {
|
||||||
|
@ -12,9 +12,12 @@ use crate::error::QueryPayloadError;
|
|||||||
use crate::extract::FromRequest;
|
use crate::extract::FromRequest;
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
/// Extract typed information from the request's query.
|
/// Extract typed information from the request's query.
|
||||||
///
|
///
|
||||||
|
/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot
|
||||||
|
/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs.
|
||||||
|
/// Attempts to do so will *fail at runtime*.
|
||||||
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
@ -33,10 +36,10 @@ use crate::request::HttpRequest;
|
|||||||
/// response_type: ResponseType,
|
/// response_type: ResponseType,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Use `Query` extractor for query information.
|
/// // Use `Query` extractor for query information (and destructure it within the signature).
|
||||||
/// // This handler get called only if request's query contains `username` field
|
/// // This handler gets called only if the request's query string contains a `username` field.
|
||||||
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
|
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`.
|
||||||
/// fn index(info: web::Query<AuthRequest>) -> String {
|
/// fn index(web::Query(info): web::Query<AuthRequest>) -> String {
|
||||||
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
|
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
@ -45,7 +48,8 @@ use crate::request::HttpRequest;
|
|||||||
/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
|
/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Query<T>(T);
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Query<T>(pub T);
|
||||||
|
|
||||||
impl<T> Query<T> {
|
impl<T> Query<T> {
|
||||||
/// Deconstruct to a inner value
|
/// Deconstruct to a inner value
|
||||||
@ -162,6 +166,8 @@ where
|
|||||||
|
|
||||||
/// Query extractor configuration
|
/// Query extractor configuration
|
||||||
///
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use] extern crate serde_derive;
|
/// #[macro_use] extern crate serde_derive;
|
||||||
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||||
|
Reference in New Issue
Block a user