1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-24 01:38:20 +02:00

Compare commits

..

17 Commits

Author SHA1 Message Date
Nikolay Kim
c96068e78d bump version 2019-07-20 11:46:21 +06:00
Nikolay Kim
7bca1f7d8d Allow to disable Content-Disposition header #686 2019-07-20 11:43:49 +06:00
Nikolay Kim
3618a84164 update changes 2019-07-20 11:27:21 +06:00
jairinhohw
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
naerbnic
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
Nikolay Kim
941241c5f0 Remove unneeded actix-utils dependency 2019-07-20 10:50:36 +06:00
jesskfullwood
f8320fedd8 add note about Query decoding (#992) 2019-07-19 17:37:49 +06:00
jesskfullwood
c808364c07 make Query payload public (#991) 2019-07-19 15:47:44 +06:00
Nikolay Kim
cccd829656 update changes 2019-07-19 11:07:52 +06:00
Anton Lazarev
3650f6d7b8 Re-implement Host predicate (#989)
* update HostGuard implementation

* update/add tests for new HostGuard implementation
2019-07-19 10:28:43 +06:00
Nikolay Kim
6b7df6b242 prep actix-web release 2019-07-18 17:51:51 +06:00
Nikolay Kim
b6ff786ed3 update dependencies 2019-07-18 17:50:10 +06:00
Nikolay Kim
9c3789cbd0 revert DateServiceInner changes 2019-07-18 17:37:41 +06:00
Armin Ronacher
29098f8397 Add support for downcasting response errors (#986)
* Add support for downcasting response errors

* Added test for error casting
2019-07-18 17:25:50 +06:00
Nikolay Kim
fbdda8acb1 Unix domain sockets (HttpServer::bind_uds) #92 2019-07-18 17:24:12 +06:00
Rotem Yaari
d03296237e Log error results in Logger middleware (closes #938) (#984)
* Log error results in Logger middleware (closes #938)

* Log internal server errors with an ERROR log level

* Logger middleware: don't log 500 internal server errors, as Actix now logs them always

* Changelog
2019-07-18 14:31:18 +06:00
Aaron Hill
b36fdc46db Remove several usages of 'unsafe' (#968)
* Replace UnsafeCell in DateServiceInner with Cell

The previous API was extremely dangerous - calling `get_ref()`
followed by `reset()` would trigger instant UB, without requiring
any `unsafe` blocks in the caller.

By making DateInner `Copy`, we can use a normal `Cell` instead
of an `UnsafeCell`. This makes it impossible to cause UB (or even panic)
with the API.

* Split unsafe block HttpServiceHandlerResponse

Also add explanation of the safety of the usage of `unsafe`

* Replace UnsafeCell with RefCell in PayloadRef

This ensures that a mistake in the usage of 'get_mut' will cause
a panic, not undefined behavior.
2019-07-18 04:45:17 +06:00
29 changed files with 631 additions and 155 deletions

View File

@@ -1,5 +1,30 @@
# Changes
## [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
* Unix domain sockets (HttpServer::bind_uds) #92
* Actix now logs errors resulting in "internal server error" responses always, with the `error`
logging level
### Fixed
* Restored logging of errors through the `Logger` middleware
## [1.0.4] - 2019-07-17
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "1.0.4"
version = "1.0.5"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
edition = "2018"
[package.metadata.docs.rs]
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"]
features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
@@ -68,6 +68,9 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
# rustls
rust-tls = ["rustls", "actix-server/rust-tls"]
# unix domain sockets support
uds = ["actix-server/uds"]
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.4.1"
@@ -75,11 +78,11 @@ actix-utils = "0.4.4"
actix-router = "0.1.5"
actix-rt = "0.2.4"
actix-web-codegen = "0.1.2"
actix-http = "0.2.6"
actix-server = "0.5.1"
actix-server-config = "0.1.1"
actix-http = "0.2.7"
actix-server = "0.6.0"
actix-server-config = "0.1.2"
actix-threadpool = "0.1.1"
awc = { version = "0.2.1", optional = true }
awc = { version = "0.2.2", optional = true }
bytes = "0.4"
derive_more = "0.15.0"
@@ -104,7 +107,7 @@ rustls = { version = "0.15", optional = true }
[dev-dependencies]
actix = { version = "0.8.3" }
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"
env_logger = "0.6"
serde_derive = "1.0"

View File

@@ -1,5 +1,10 @@
# Changes
## [0.1.4] - 2019-07-20
* Allow to disable `Content-Disposition` header #686
## [0.1.3] - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.1.3"
version = "0.1.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web."
readme = "README.md"

View File

@@ -314,23 +314,32 @@ impl Files {
}
#[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 {
self.file_flags.set(named::Flags::ETAG, value);
self
}
#[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 {
self.file_flags.set(named::Flags::LAST_MD, value);
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.
pub fn default_handler<F, U>(mut self, f: F) -> Self
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]
fn test_named_file_set_content_type() {
let mut file = NamedFile::open("Cargo.toml")

View File

@@ -23,9 +23,10 @@ use crate::range::HttpRange;
use crate::ChunkedReadFile;
bitflags! {
pub(crate) struct Flags: u32 {
pub(crate) struct Flags: u8 {
const ETAG = 0b0000_0001;
const LAST_MD = 0b0000_0010;
const CONTENT_DISPOSITION = 0b0000_0100;
}
}
@@ -40,13 +41,13 @@ impl Default for Flags {
pub struct NamedFile {
path: PathBuf,
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_disposition: header::ContentDisposition,
pub(crate) md: Metadata,
modified: Option<SystemTime>,
pub(crate) encoding: Option<ContentEncoding>,
pub(crate) status_code: StatusCode,
pub(crate) flags: Flags,
}
impl NamedFile {
@@ -172,11 +173,21 @@ impl NamedFile {
/// sent to the peer. By default the disposition is `inline` for text,
/// image, and video content types, and `attachment` otherwise, and
/// 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).
#[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
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
}
@@ -294,10 +305,12 @@ impl Responder for NamedFile {
if self.status_code != StatusCode::OK {
let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone()))
.header(
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
.if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
res.header(
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
});
if let Some(current_encoding) = self.encoding {
resp.encoding(current_encoding);
}
@@ -368,10 +381,12 @@ impl Responder for NamedFile {
let mut resp = HttpResponse::build(self.status_code);
resp.set(header::ContentType(self.content_type.clone()))
.header(
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
.if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| {
res.header(
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
);
});
// default compressing
if let Some(current_encoding) = self.encoding {
resp.encoding(current_encoding);

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-framed"
version = "0.2.0"
version = "0.2.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix framed app server"
readme = "README.md"
@@ -21,18 +21,18 @@ path = "src/lib.rs"
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.4.0"
actix-utils = "0.4.0"
actix-service = "0.4.1"
actix-router = "0.1.2"
actix-rt = "0.2.2"
actix-http = "0.2.0"
actix-server-config = "0.1.1"
actix-http = "0.2.7"
actix-server-config = "0.1.2"
bytes = "0.4"
futures = "0.1.25"
log = "0.4"
[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-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
## [0.2.1] - 2019-07-20
* Remove unneeded actix-utils dependency
## [0.2.0] - 2019-05-12
* Update dependencies

View File

@@ -6,7 +6,6 @@ use actix_http::{Error, Request, Response};
use actix_router::{Path, Router, Url};
use actix_server_config::ServerConfig;
use actix_service::{IntoNewService, NewService, Service};
use actix_utils::cloneable::CloneableService;
use futures::{Async, Future, Poll};
use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService};
@@ -100,7 +99,7 @@ where
type Response = ();
type Error = Error;
type InitError = ();
type Service = CloneableService<FramedAppService<T, S>>;
type Service = FramedAppService<T, S>;
type Future = CreateService<T, S>;
fn new_service(&self, _: &ServerConfig) -> Self::Future {
@@ -138,7 +137,7 @@ impl<S: 'static, T: 'static> Future for CreateService<T, S>
where
T: AsyncRead + AsyncWrite,
{
type Item = CloneableService<FramedAppService<T, S>>;
type Item = FramedAppService<T, S>;
type Error = ();
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
@@ -177,10 +176,10 @@ where
}
router
});
Ok(Async::Ready(CloneableService::new(FramedAppService {
Ok(Async::Ready(FramedAppService {
router: router.finish(),
state: self.state.clone(),
})))
}))
} else {
Ok(Async::NotReady)
}

View File

@@ -1,9 +1,4 @@
#![allow(
clippy::type_complexity,
clippy::new_without_default,
dead_code,
deprecated
)]
#![allow(clippy::type_complexity, clippy::new_without_default, dead_code)]
mod app;
mod helpers;
mod request;

View File

@@ -1,5 +1,12 @@
# Changes
## [0.2.7] - 2019-07-18
### Added
* Add support for downcasting response errors #986
## [0.2.6] - 2019-07-17
### Changed

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "0.2.6"
version = "0.2.7"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives"
readme = "README.md"
@@ -48,7 +48,7 @@ actix-service = "0.4.1"
actix-codec = "0.1.2"
actix-connect = "0.2.1"
actix-utils = "0.4.4"
actix-server-config = "0.1.1"
actix-server-config = "0.1.2"
actix-threadpool = "0.1.1"
base64 = "0.10"
@@ -97,9 +97,9 @@ chrono = "0.4.6"
[dev-dependencies]
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-http-test = { version = "0.2.0", features=["ssl"] }
actix-http-test = { version = "0.2.4", features=["ssl"] }
env_logger = "0.6"
serde_derive = "1.0"
openssl = { version="0.10" }

View File

@@ -162,16 +162,21 @@ impl ServiceConfig {
pub fn set_date(&self, dst: &mut BytesMut) {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(&self.0.timer.date().bytes);
self.0
.timer
.set_date(|date| buf[6..35].copy_from_slice(&date.bytes));
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
}
pub(crate) fn set_date_header(&self, dst: &mut BytesMut) {
dst.extend_from_slice(&self.0.timer.date().bytes);
self.0
.timer
.set_date(|date| dst.extend_from_slice(&date.bytes));
}
}
#[derive(Copy, Clone)]
struct Date {
bytes: [u8; DATE_VALUE_LENGTH],
pos: usize,
@@ -215,10 +220,6 @@ impl DateServiceInner {
}
}
fn get_ref(&self) -> &Option<(Date, Instant)> {
unsafe { &*self.current.get() }
}
fn reset(&self) {
unsafe { (&mut *self.current.get()).take() };
}
@@ -236,7 +237,7 @@ impl DateService {
}
fn check_date(&self) {
if self.0.get_ref().is_none() {
if unsafe { (&*self.0.current.get()).is_none() } {
self.0.update();
// periodic date update
@@ -252,14 +253,12 @@ impl DateService {
fn now(&self) -> Instant {
self.check_date();
self.0.get_ref().as_ref().unwrap().1
unsafe { (&*self.0.current.get()).as_ref().unwrap().1 }
}
fn date(&self) -> &Date {
fn set_date<F: FnMut(&Date)>(&self, mut f: F) {
self.check_date();
let item = self.0.get_ref().as_ref().unwrap();
&item.0
f(&unsafe { (&*self.0.current.get()).as_ref().unwrap().0 })
}
}

View File

@@ -1,4 +1,5 @@
//! Error and Result module
use std::any::TypeId;
use std::cell::RefCell;
use std::io::Write;
use std::str::Utf8Error;
@@ -51,6 +52,11 @@ impl Error {
pub fn as_response_error(&self) -> &dyn ResponseError {
self.cause.as_ref()
}
/// Similar to `as_response_error` but downcasts.
pub fn as_error<T: ResponseError + 'static>(&self) -> Option<&T> {
ResponseError::downcast_ref(self.cause.as_ref())
}
}
/// Error that can be converted to `Response`
@@ -73,6 +79,25 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
);
resp.set_body(Body::from(buf))
}
#[doc(hidden)]
fn __private_get_type_id__(&self) -> TypeId
where
Self: 'static,
{
TypeId::of::<Self>()
}
}
impl ResponseError + 'static {
/// Downcasts a response error to a specific type.
pub fn downcast_ref<T: ResponseError + 'static>(&self) -> Option<&T> {
if self.__private_get_type_id__() == TypeId::of::<T>() {
unsafe { Some(&*(self as *const ResponseError as *const T)) }
} else {
None
}
}
}
impl fmt::Display for Error {
@@ -1044,6 +1069,16 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_error_casting() {
let err = PayloadError::Overflow;
let resp_err: &ResponseError = &err;
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
assert_eq!(err.to_string(), "A payload reached size limit.");
let not_err = resp_err.downcast_ref::<ContentTypeError>();
assert!(not_err.is_none());
}
#[test]
fn test_error_helpers() {
let r: Response = ErrorBadRequest("err").into();

View File

@@ -52,6 +52,9 @@ impl Response<Body> {
#[inline]
pub fn from_error(error: Error) -> Response {
let mut resp = error.as_response_error().render_response();
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
error!("Internal Server Error: {:?}", error);
}
resp.error = Some(error);
resp
}

View File

@@ -466,16 +466,18 @@ where
State::Unknown(ref mut data) => {
if let Some(ref mut item) = data {
loop {
unsafe {
let b = item.1.bytes_mut();
let n = try_ready!(item.0.poll_read(b));
if n == 0 {
return Ok(Async::Ready(()));
}
item.1.advance_mut(n);
if item.1.len() >= HTTP2_PREFACE.len() {
break;
}
// Safety - we only write to the returned slice.
let b = unsafe { item.1.bytes_mut() };
let n = try_ready!(item.0.poll_read(b));
if n == 0 {
return Ok(Async::Ready(()));
}
// Safety - we know that 'n' bytes have
// been initialized via the contract of
// 'poll_read'
unsafe { item.1.advance_mut(n) };
if item.1.len() >= HTTP2_PREFACE.len() {
break;
}
}
} else {

View File

@@ -1,5 +1,5 @@
//! Multipart payload support
use std::cell::{Cell, RefCell, UnsafeCell};
use std::cell::{Cell, RefCell, RefMut};
use std::marker::PhantomData;
use std::rc::Rc;
use std::{cmp, fmt};
@@ -112,7 +112,7 @@ impl Stream for Multipart {
Err(err)
} else if self.safety.current() {
let mut inner = self.inner.as_mut().unwrap().borrow_mut();
if let Some(payload) = inner.payload.get_mut(&self.safety) {
if let Some(mut payload) = inner.payload.get_mut(&self.safety) {
payload.poll_stream()?;
}
inner.poll(&self.safety)
@@ -265,12 +265,12 @@ impl InnerMultipart {
}
}
let headers = if let Some(payload) = self.payload.get_mut(safety) {
let headers = if let Some(mut payload) = self.payload.get_mut(safety) {
match self.state {
// read until first boundary
InnerState::FirstBoundary => {
match InnerMultipart::skip_until_boundary(
payload,
&mut *payload,
&self.boundary,
)? {
Some(eof) => {
@@ -286,7 +286,10 @@ impl InnerMultipart {
}
// read boundary
InnerState::Boundary => {
match InnerMultipart::read_boundary(payload, &self.boundary)? {
match InnerMultipart::read_boundary(
&mut *payload,
&self.boundary,
)? {
None => return Ok(Async::NotReady),
Some(eof) => {
if eof {
@@ -303,7 +306,7 @@ impl InnerMultipart {
// read field headers for next field
if self.state == InnerState::Headers {
if let Some(headers) = InnerMultipart::read_headers(payload)? {
if let Some(headers) = InnerMultipart::read_headers(&mut *payload)? {
self.state = InnerState::Boundary;
headers
} else {
@@ -411,7 +414,8 @@ impl Stream for Field {
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if self.safety.current() {
let mut inner = self.inner.borrow_mut();
if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety)
if let Some(mut payload) =
inner.payload.as_ref().unwrap().get_mut(&self.safety)
{
payload.poll_stream()?;
}
@@ -582,12 +586,13 @@ impl InnerField {
return Ok(Async::Ready(None));
}
let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s)
{
if !self.eof {
let res = if let Some(ref mut len) = self.length {
InnerField::read_len(payload, len)?
InnerField::read_len(&mut *payload, len)?
} else {
InnerField::read_stream(payload, &self.boundary)?
InnerField::read_stream(&mut *payload, &self.boundary)?
};
match res {
@@ -618,7 +623,7 @@ impl InnerField {
}
struct PayloadRef {
payload: Rc<UnsafeCell<PayloadBuffer>>,
payload: Rc<RefCell<PayloadBuffer>>,
}
impl PayloadRef {
@@ -628,15 +633,12 @@ impl PayloadRef {
}
}
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer>
fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<RefMut<'a, PayloadBuffer>>
where
'a: 'b,
{
// Unsafe: Invariant is inforced by Safety Safety is used as ref counter,
// only top most ref can have mutable access to payload.
if s.current() {
let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() };
Some(payload)
Some(self.payload.borrow_mut())
} else {
None
}

View File

@@ -1,5 +1,12 @@
# 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
* Allow to use custom ws codec with `WebsocketContext` #925

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "1.0.1"
version = "1.0.2"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for actix web framework."
readme = "README.md"
@@ -27,4 +27,4 @@ futures = "0.1.25"
[dev-dependencies]
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)))
}
/// 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.
///
/// This function returns handshake `HttpResponse`, ready to send to peer.
/// It does not perform any IO.
///
// /// `protocols` is a sequence of known protocols. On successful handshake,
// /// the returned response headers contain the first protocol in this list
// /// which the server also knows.
pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeError> {
/// `protocols` is a sequence of known protocols. On successful handshake,
/// the returned response headers contain the first protocol in this list
/// which the server also knows.
pub fn handshake_with_protocols(
req: &HttpRequest,
protocols: &[&str],
) -> Result<HttpResponseBuilder, HandshakeError> {
// WebSocket accepts only GET
if *req.method() != Method::GET {
return Err(HandshakeError::GetMethodRequired);
@@ -92,11 +145,28 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
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")
.header(header::TRANSFER_ENCODING, "chunked")
.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
@@ -168,6 +238,24 @@ where
#[inline]
/// 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>
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
A: StreamHandler<Message, ProtocolError>,
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
@@ -179,7 +267,9 @@ where
};
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]
@@ -564,5 +654,87 @@ mod tests {
StatusCode::SWITCHING_PROTOCOLS,
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)
);
}
}

49
examples/uds.rs Normal file
View File

@@ -0,0 +1,49 @@
use futures::IntoFuture;
use actix_web::{
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
};
#[get("/resource1/{name}/index.html")]
fn index(req: HttpRequest, name: web::Path<String>) -> String {
println!("REQ: {:?}", req);
format!("Hello: {}!\r\n", name)
}
fn index_async(req: HttpRequest) -> impl IntoFuture<Item = &'static str, Error = Error> {
println!("REQ: {:?}", req);
Ok("Hello world!\r\n")
}
#[get("/")]
fn no_params() -> &'static str {
"Hello world!\r\n"
}
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
HttpServer::new(|| {
App::new()
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default())
.service(index)
.service(no_params)
.service(
web::resource("/resource2/index.html")
.wrap(
middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"),
)
.default_service(
web::route().to(|| HttpResponse::MethodNotAllowed()),
)
.route(web::get().to_async(index_async)),
)
.service(web::resource("/test1.html").to(|| "Test\r\n"))
})
.bind_uds("/Users/fafhrd91/uds-test")?
.workers(1)
.run()
}

View File

@@ -26,7 +26,7 @@
//! ```
#![allow(non_snake_case)]
use actix_http::http::{self, header, HttpTryFrom};
use actix_http::http::{self, header, uri::Uri, HttpTryFrom};
use actix_http::RequestHead;
/// 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.
// ///
// /// ```rust,ignore
// /// # extern crate actix_web;
// /// use actix_web::{pred, App, HttpResponse};
// ///
// /// fn main() {
// /// App::new().resource("/index.html", |r| {
// /// r.route()
// /// .guard(pred::Host("www.rust-lang.org"))
// /// .f(|_| HttpResponse::MethodNotAllowed())
// /// });
// /// }
// /// ```
// pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
// HostGuard(host.as_ref().to_string(), None)
// }
/// Return predicate that matches if request contains specified Host name.
///
/// ```rust,ignore
/// # extern crate actix_web;
/// use actix_web::{guard::Host, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .guard(Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Host<H: AsRef<str>>(host: H) -> HostGuard {
HostGuard(host.as_ref().to_string(), None)
}
// #[doc(hidden)]
// pub struct HostGuard(String, Option<String>);
fn get_host_uri(req: &RequestHead) -> Option<Uri> {
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 {
// /// Set reuest scheme to match
// pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
// self.1 = Some(scheme.as_ref().to_string())
// }
// }
#[doc(hidden)]
pub struct HostGuard(String, Option<String>);
// impl Guard for HostGuard {
// fn check(&self, _req: &RequestHead) -> bool {
// // let info = req.connection_info();
// // if let Some(ref scheme) = self.1 {
// // self.0 == info.host() && scheme == info.scheme()
// // } else {
// // self.0 == info.host()
// // }
// false
// }
// }
impl HostGuard {
/// Set request scheme to match
pub fn scheme<H: AsRef<str>>(mut self, scheme: H) -> HostGuard {
self.1 = Some(scheme.as_ref().to_string());
self
}
}
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)]
mod tests {
@@ -318,21 +341,64 @@ mod tests {
assert!(!pred.check(req.head()));
}
// #[test]
// fn test_host() {
// let req = TestServiceRequest::default()
// .header(
// header::HOST,
// header::HeaderValue::from_static("www.rust-lang.org"),
// )
// .request();
#[test]
fn test_host() {
let req = TestRequest::default()
.header(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
)
.to_http_request();
// let pred = Host("www.rust-lang.org");
// assert!(pred.check(&req));
let pred = Host("www.rust-lang.org");
assert!(pred.check(req.head()));
// let pred = Host("localhost");
// assert!(!pred.check(&req));
// }
let pred = Host("www.rust-lang.org").scheme("https");
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]
fn test_methods() {

View File

@@ -78,6 +78,7 @@
//! `c` compiler (default enabled)
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//! * `uds` - Unix domain support, enables `HttpServer::bind_uds()` method.
//!
#![allow(clippy::type_complexity, clippy::new_without_default)]

View File

@@ -9,12 +9,13 @@ use actix_service::{Service, Transform};
use bytes::Bytes;
use futures::future::{ok, FutureResult};
use futures::{Async, Future, Poll};
use log::debug;
use regex::Regex;
use time;
use crate::dev::{BodySize, MessageBody, ResponseBody};
use crate::error::{Error, Result};
use crate::http::{HeaderName, HttpTryFrom};
use crate::http::{HeaderName, HttpTryFrom, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::HttpResponse;
@@ -202,6 +203,12 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let res = futures::try_ready!(self.fut.poll());
if let Some(error) = res.response().error() {
if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR {
debug!("Error in response: {:?}", error);
}
}
if let Some(ref mut format) = self.format {
for unit in &mut format.0 {
unit.render_response(res.response());

View File

@@ -434,6 +434,38 @@ where
}
Ok(self)
}
#[cfg(feature = "uds")]
/// Start listening for incoming unix domain connections.
///
/// This method is available with `uds` feature.
pub fn bind_uds<A>(mut self, addr: A) -> io::Result<Self>
where
A: AsRef<std::path::Path>,
{
let cfg = self.config.clone();
let factory = self.factory.clone();
self.sockets.push(Socket {
scheme: "http",
addr: net::SocketAddr::new(
net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
8080,
),
});
self.builder = self.builder.bind_uds(
format!("actix-web-service-{:?}", addr.as_ref()),
addr,
move || {
let c = cfg.lock();
HttpService::build()
.keep_alive(c.keep_alive)
.client_timeout(c.client_timeout)
.finish(factory())
},
)?;
Ok(self)
}
}
impl<F, I, S, B> HttpServer<F, I, S, B>

View File

@@ -12,9 +12,12 @@ use crate::error::QueryPayloadError;
use crate::extract::FromRequest;
use crate::request::HttpRequest;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// 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
///
/// ```rust
@@ -33,10 +36,10 @@ use crate::request::HttpRequest;
/// response_type: ResponseType,
/// }
///
/// // Use `Query` extractor for query information.
/// // This handler get called only if request's query contains `username` field
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// fn index(info: web::Query<AuthRequest>) -> String {
/// // Use `Query` extractor for query information (and destructure it within the signature).
/// // 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"`.
/// fn index(web::Query(info): web::Query<AuthRequest>) -> String {
/// 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
/// }
/// ```
pub struct Query<T>(T);
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct Query<T>(pub T);
impl<T> Query<T> {
/// Deconstruct to a inner value
@@ -162,6 +166,8 @@ where
/// Query extractor configuration
///
/// ## Example
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, web, App, FromRequest, HttpResponse};

View File

@@ -1,5 +1,9 @@
# Changes
## [0.2.4] - 2019-07-18
* Update actix-server to 0.6
## [0.2.3] - 2019-07-16
* Add `delete`, `options`, `patch` methods to `TestServerRunner`

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "0.2.3"
version = "0.2.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http test server"
readme = "README.md"
@@ -33,7 +33,7 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
actix-codec = "0.1.2"
actix-rt = "0.2.2"
actix-service = "0.4.1"
actix-server = "0.5.1"
actix-server = "0.6.0"
actix-utils = "0.4.1"
awc = "0.2.2"

View File

@@ -12,6 +12,7 @@ use futures::future::lazy;
use futures::{Future, IntoFuture, Stream};
use http::Method;
use net2::TcpBuilder;
use tokio_tcp::TcpStream;
thread_local! {
static RT: RefCell<Inner> = {
@@ -109,7 +110,7 @@ pub struct TestServerRuntime {
impl TestServer {
#[allow(clippy::new_ret_no_self)]
/// Start new test server with application factory
pub fn new<F: StreamServiceFactory>(factory: F) -> TestServerRuntime {
pub fn new<F: StreamServiceFactory<TcpStream>>(factory: F) -> TestServerRuntime {
let (tx, rx) = mpsc::channel();
// run server in separate thread