1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-08 03:46:30 +02:00

Compare commits

...

12 Commits

Author SHA1 Message Date
c96068e78d bump version 2019-07-20 11:46:21 +06:00
7bca1f7d8d Allow to disable Content-Disposition header #686 2019-07-20 11:43:49 +06:00
3618a84164 update changes 2019-07-20 11:27:21 +06:00
03ca408e94 add support for specifying protocols on websocket handshake (#835)
* add support for specifying protocols on websocket handshake

* separated the handshake function with and without protocols
changed protocols type from Vec<&str> to [&str]
2019-07-20 11:22:06 +06:00
e53e9c8ba3 Add the start_with_addr() function, to obtain an addr to the target websocket actor (#988) 2019-07-20 11:17:58 +06:00
941241c5f0 Remove unneeded actix-utils dependency 2019-07-20 10:50:36 +06:00
f8320fedd8 add note about Query decoding (#992) 2019-07-19 17:37:49 +06:00
c808364c07 make Query payload public (#991) 2019-07-19 15:47:44 +06:00
cccd829656 update changes 2019-07-19 11:07:52 +06:00
3650f6d7b8 Re-implement Host predicate (#989)
* update HostGuard implementation

* update/add tests for new HostGuard implementation
2019-07-19 10:28:43 +06:00
6b7df6b242 prep actix-web release 2019-07-18 17:51:51 +06:00
b6ff786ed3 update dependencies 2019-07-18 17:50:10 +06:00
16 changed files with 428 additions and 111 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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")

View File

@ -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);

View File

@ -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"

View File

@ -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

View File

@ -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)
} }

View File

@ -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;

View File

@ -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" }

View File

@ -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

View File

@ -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"] }

View File

@ -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)
);
} }
} }

View File

@ -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() {

View File

@ -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};