1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-26 03:21:08 +00:00

refactor error handling

This commit is contained in:
Nikolay Kim 2017-11-15 20:06:28 -10:00
parent c565965865
commit de71ad7de4
18 changed files with 317 additions and 325 deletions

View file

@ -5,6 +5,8 @@
* HTTP/2 Support
* Refactor error handling
* Asynchronous middlewares
* Content compression/decompression (br, gzip, deflate)

View file

@ -34,6 +34,8 @@ alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
[dependencies]
log = "0.3"
failure = { git = "https://github.com/withoutboats/failure" }
failure_derive = { git = "https://github.com/withoutboats/failure_derive" }
time = "0.1"
http = "0.1"
httparse = "0.1"
@ -44,11 +46,15 @@ cookie = { version="0.10", features=["percent-encode", "secure"] }
regex = "0.2"
sha1 = "0.2"
url = "1.5"
libc = "^0.2"
libc = "0.2"
serde = "1.0"
serde_json = "1.0"
flate2 = "0.2"
brotli2 = "^0.3.2"
percent-encoding = "1.0"
# redis-async = { git="https://github.com/benashford/redis-async-rs" }
# tokio
bytes = "0.4"
futures = "0.1"

View file

@ -19,7 +19,8 @@ fn index(req: &mut HttpRequest, mut _payload: Payload, state: &()) -> HttpRespon
}
/// somple handle
fn index_async(req: &mut HttpRequest, _payload: Payload, state: &()) -> Once<actix_web::Frame, ()>
fn index_async(req: &mut HttpRequest, _payload: Payload, state: &())
-> Once<actix_web::Frame, actix_web::error::Error>
{
println!("{:?}", req);
@ -49,7 +50,7 @@ fn main() {
HttpServer::new(
Application::default("/")
// enable logger
.middleware(middlewares::Logger::default())
//.middleware(middlewares::Logger::default())
// register simple handle r, handle all methods
.handler("/index.html", index)
// with path parameters

View file

@ -14,6 +14,7 @@ use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCel
use task::{IoContext, DrainFut};
use body::Binary;
use error::Error;
use route::{Route, Frame};
use httpresponse::HttpResponse;
@ -184,9 +185,9 @@ impl<A> HttpContext<A> where A: Actor<Context=Self> + Route {
impl<A> Stream for HttpContext<A> where A: Actor<Context=Self> + Route
{
type Item = Frame;
type Error = std::io::Error;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Frame>, std::io::Error> {
fn poll(&mut self) -> Poll<Option<Frame>, Error> {
if self.act.is_none() {
return Ok(Async::NotReady)
}

View file

@ -14,9 +14,10 @@ use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::{Bytes, BytesMut, BufMut, Writer};
use body::Body;
use error::PayloadError;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use payload::{PayloadSender, PayloadWriter, PayloadError};
use payload::{PayloadSender, PayloadWriter};
/// Represents supported types of content encodings
#[derive(Copy, Clone, PartialEq, Debug)]

View file

@ -1,78 +1,113 @@
//! Error and Result module.
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
use std::{fmt, result};
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::io::Error as IoError;
use cookie;
use httparse;
use http::{StatusCode, Error as HttpError};
use failure::Fail;
use http2::Error as Http2Error;
use http::{header, StatusCode, Error as HttpError};
use http_range::HttpRangeParseError;
// re-exports
pub use cookie::{ParseError as CookieParseError};
use HttpRangeParseError;
use multipart::MultipartError;
use body::Body;
use httpresponse::{HttpResponse};
use httpresponse::HttpResponse;
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed};
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
/// for actix web operations.
///
/// This typedef is generally used to avoid writing out `actix_web::error::Error` directly and
/// is otherwise a direct mapping to `Result`.
pub type Result<T> = result::Result<T, Error>;
/// Actix web error.
#[derive(Debug)]
pub struct Error {
cause: Box<ErrorResponse>,
}
/// Error that can be converted to HttpResponse
pub trait ErrorResponse: Fail {
/// Create response for error
///
/// Internal server error is generated by default.
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.cause, f)
}
}
/// `HttpResponse` for `Error`.
impl From<Error> for HttpResponse {
fn from(err: Error) -> Self {
err.cause.error_response()
}
}
impl<T: ErrorResponse> From<T> for Error {
fn from(err: T) -> Error {
Error { cause: Box::new(err) }
}
}
// /// Default error is `InternalServerError`
// impl<T: StdError + Sync + Send + 'static> ErrorResponse for T {
// fn error_response(&self) -> HttpResponse {
// HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
// }
// }
/// A set of errors that can occur during parsing HTTP streams.
#[derive(Debug)]
#[derive(Fail, Debug)]
pub enum ParseError {
/// An invalid `Method`, such as `GE,T`.
#[fail(display="Invalid Method specified")]
Method,
/// An invalid `Uri`, such as `exam ple.domain`.
#[fail(display="Uri error")]
Uri,
/// An invalid `HttpVersion`, such as `HTP/1.1`
#[fail(display="Invalid HTTP version specified")]
Version,
/// An invalid `Header`.
#[fail(display="Invalid Header provided")]
Header,
/// A message head is too large to be reasonable.
#[fail(display="Message head is too large")]
TooLarge,
/// A message reached EOF, but is not complete.
#[fail(display="Message is incomplete")]
Incomplete,
/// An invalid `Status`, such as `1337 ELITE`.
#[fail(display="Invalid Status provided")]
Status,
/// A timeout occurred waiting for an IO event.
#[allow(dead_code)]
#[fail(display="Timeout")]
Timeout,
/// An `io::Error` that occurred while trying to read or write to a network stream.
#[fail(display="IO error: {}", _0)]
Io(IoError),
/// Parsing a field as string failed
#[fail(display="UTF8 error: {}", _0)]
Utf8(Utf8Error),
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseError::Io(ref e) => fmt::Display::fmt(e, f),
ParseError::Utf8(ref e) => fmt::Display::fmt(e, f),
ref e => f.write_str(e.description()),
}
}
}
impl StdError for ParseError {
fn description(&self) -> &str {
match *self {
ParseError::Method => "Invalid Method specified",
ParseError::Version => "Invalid HTTP version specified",
ParseError::Header => "Invalid Header provided",
ParseError::TooLarge => "Message head is too large",
ParseError::Status => "Invalid Status provided",
ParseError::Incomplete => "Message is incomplete",
ParseError::Timeout => "Timeout",
ParseError::Uri => "Uri error",
ParseError::Io(ref e) => e.description(),
ParseError::Utf8(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&StdError> {
match *self {
ParseError::Io(ref error) => Some(error),
ParseError::Utf8(ref error) => Some(error),
_ => None,
}
/// Return `BadRequest` for `ParseError`
impl ErrorResponse for ParseError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
}
@ -108,47 +143,158 @@ impl From<httparse::Error> for ParseError {
}
}
/// Return `BadRequest` for `ParseError`
impl From<ParseError> for HttpResponse {
fn from(err: ParseError) -> Self {
HttpResponse::from_error(StatusCode::BAD_REQUEST, err)
#[derive(Fail, Debug)]
/// A set of errors that can occur during payload parsing.
pub enum PayloadError {
/// A payload reached EOF, but is not complete.
#[fail(display="A payload reached EOF, but is not complete.")]
Incomplete,
/// Content encoding stream corruption
#[fail(display="Can not decode content-encoding.")]
EncodingCorrupted,
/// Parse error
#[fail(display="{}", _0)]
ParseError(#[cause] IoError),
/// Http2 error
#[fail(display="{}", _0)]
Http2(#[cause] Http2Error),
}
impl From<IoError> for PayloadError {
fn from(err: IoError) -> PayloadError {
PayloadError::ParseError(err)
}
}
/// Return `InternalServerError` for `HttpError`,
/// Response generation can return `HttpError`, so it is internal error
impl From<HttpError> for HttpResponse {
fn from(err: HttpError) -> Self {
HttpResponse::from_error(StatusCode::INTERNAL_SERVER_ERROR, err)
}
}
impl ErrorResponse for HttpError {}
/// Return `InternalServerError` for `io::Error`
impl From<IoError> for HttpResponse {
fn from(err: IoError) -> Self {
HttpResponse::from_error(StatusCode::INTERNAL_SERVER_ERROR, err)
impl ErrorResponse for IoError {}
/// Return `BadRequest` for `cookie::ParseError`
impl ErrorResponse for cookie::ParseError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
}
/// Return `BadRequest` for `cookie::ParseError`
impl From<cookie::ParseError> for HttpResponse {
fn from(err: cookie::ParseError) -> Self {
HttpResponse::from_error(StatusCode::BAD_REQUEST, err)
/// Http range header parsing error
#[derive(Fail, Debug)]
pub enum HttpRangeError {
/// Returned if range is invalid.
#[fail(display="Range header is invalid")]
InvalidRange,
/// Returned if first-byte-pos of all of the byte-range-spec
/// values is greater than the content size.
/// See https://github.com/golang/go/commit/aa9b3d7
#[fail(display="First-byte-pos of all of the byte-range-spec values is greater than the content size")]
NoOverlap,
}
/// Return `BadRequest` for `HttpRangeError`
impl ErrorResponse for HttpRangeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided"))
}
}
impl From<HttpRangeParseError> for HttpRangeError {
fn from(err: HttpRangeParseError) -> HttpRangeError {
match err {
HttpRangeParseError::InvalidRange => HttpRangeError::InvalidRange,
HttpRangeParseError::NoOverlap => HttpRangeError::NoOverlap,
}
}
}
/// A set of errors that can occur during parsing multipart streams.
#[derive(Fail, Debug)]
pub enum MultipartError {
/// Content-Type header is not found
#[fail(display="No Content-type header found")]
NoContentType,
/// Can not parse Content-Type header
#[fail(display="Can not parse Content-Type header")]
ParseContentType,
/// Multipart boundary is not found
#[fail(display="Multipart boundary is not found")]
Boundary,
/// Error during field parsing
#[fail(display="{}", _0)]
Parse(#[cause] ParseError),
/// Payload error
#[fail(display="{}", _0)]
Payload(#[cause] PayloadError),
}
impl From<ParseError> for MultipartError {
fn from(err: ParseError) -> MultipartError {
MultipartError::Parse(err)
}
}
impl From<PayloadError> for MultipartError {
fn from(err: PayloadError) -> MultipartError {
MultipartError::Payload(err)
}
}
/// Return `BadRequest` for `MultipartError`
impl From<MultipartError> for HttpResponse {
fn from(err: MultipartError) -> Self {
HttpResponse::from_error(StatusCode::BAD_REQUEST, err)
impl ErrorResponse for MultipartError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
}
/// Return `BadRequest` for `HttpRangeParseError`
impl From<HttpRangeParseError> for HttpResponse {
fn from(_: HttpRangeParseError) -> Self {
HttpResponse::new(
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided"))
/// Websocket handshake errors
#[derive(Fail, PartialEq, Debug)]
pub enum WsHandshakeError {
/// Only get method is allowed
#[fail(display="Method not allowed")]
GetMethodRequired,
/// Ugrade header if not set to websocket
#[fail(display="Websocket upgrade is expected")]
NoWebsocketUpgrade,
/// Connection header is not set to upgrade
#[fail(display="Connection upgrade is expected")]
NoConnectionUpgrade,
/// Websocket version header is not set
#[fail(display="Websocket version header is required")]
NoVersionHeader,
/// Unsupported websockt version
#[fail(display="Unsupported version")]
UnsupportedVersion,
/// Websocket key is not set or wrong
#[fail(display="Unknown websocket key")]
BadWebsocketKey,
}
impl ErrorResponse for WsHandshakeError {
fn error_response(&self) -> HttpResponse {
match *self {
WsHandshakeError::GetMethodRequired => {
HTTPMethodNotAllowed
.builder()
.header(header::ALLOW, "GET")
.finish()
.unwrap()
}
WsHandshakeError::NoWebsocketUpgrade =>
HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"),
WsHandshakeError::NoConnectionUpgrade =>
HTTPBadRequest.with_reason("No CONNECTION upgrade"),
WsHandshakeError::NoVersionHeader =>
HTTPBadRequest.with_reason("Websocket version header is required"),
WsHandshakeError::UnsupportedVersion =>
HTTPBadRequest.with_reason("Unsupported version"),
WsHandshakeError::BadWebsocketKey =>
HTTPBadRequest.with_reason("Handshake error")
}
}
}
@ -159,24 +305,24 @@ mod tests {
use httparse;
use http::{StatusCode, Error as HttpError};
use cookie::ParseError as CookieParseError;
use super::{ParseError, HttpResponse, HttpRangeParseError, MultipartError};
use super::*;
#[test]
fn test_into_response() {
let resp: HttpResponse = ParseError::Incomplete.into();
let resp: HttpResponse = ParseError::Incomplete.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = HttpRangeParseError::InvalidRange.into();
let resp: HttpResponse = HttpRangeError::InvalidRange.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = CookieParseError::EmptyName.into();
let resp: HttpResponse = CookieParseError::EmptyName.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: HttpResponse = MultipartError::Boundary.into();
let resp: HttpResponse = MultipartError::Boundary.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
let resp: HttpResponse = err.into();
let resp: HttpResponse = err.error_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
@ -185,14 +331,14 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = ParseError::Io(orig);
assert_eq!(e.cause().unwrap().description(), desc);
assert_eq!(format!("{}", e.cause().unwrap()), desc);
}
macro_rules! from {
($from:expr => $error:pat) => {
match ParseError::from($from) {
e @ $error => {
assert!(e.description().len() >= 5);
assert!(format!("{}", e).len() >= 5);
} ,
e => panic!("{:?}", e)
}
@ -203,9 +349,8 @@ mod tests {
($from:expr => $error:pat) => {
match ParseError::from($from) {
e @ $error => {
let desc = e.cause().unwrap().description();
let desc = format!("{}", e.cause().unwrap());
assert_eq!(desc, $from.description().to_owned());
assert_eq!(desc, e.description());
},
_ => panic!("{:?}", $from)
}

View file

@ -17,12 +17,12 @@ use percent_encoding;
use task::Task;
use channel::HttpHandler;
use error::ParseError;
use error::{ParseError, PayloadError, ErrorResponse};
use h1writer::H1Writer;
use httpcodes::HTTPNotFound;
use httprequest::HttpRequest;
use encoding::PayloadType;
use payload::{Payload, PayloadError, PayloadWriter, DEFAULT_BUFFER_SIZE};
use payload::{Payload, PayloadWriter, DEFAULT_BUFFER_SIZE};
const KEEPALIVE_PERIOD: u64 = 15; // seconds
const INIT_BUFFER_SIZE: usize = 8192;
@ -167,7 +167,7 @@ impl<T, H> Http1<T, H>
}
// read incoming data
if !self.error && !self.h2 && self.tasks.len() < MAX_PIPELINED_MESSAGES {
while !self.error && !self.h2 && self.tasks.len() < MAX_PIPELINED_MESSAGES {
match self.reader.parse(self.stream.get_mut(), &mut self.read_buf) {
Ok(Async::Ready(Item::Http1(mut req, payload))) => {
not_ready = false;
@ -224,7 +224,7 @@ impl<T, H> Http1<T, H>
if self.tasks.is_empty() {
if let ReaderError::Error(err) = err {
self.tasks.push_back(
Entry {task: Task::reply(err),
Entry {task: Task::reply(err.error_response()),
req: UnsafeCell::new(HttpRequest::for_error()),
eof: false,
error: false,
@ -250,7 +250,7 @@ impl<T, H> Http1<T, H>
return Ok(Async::Ready(Http1Result::Done))
}
}
return Ok(Async::NotReady)
break
}
}
}

View file

@ -20,8 +20,9 @@ use h2writer::H2Writer;
use channel::HttpHandler;
use httpcodes::HTTPNotFound;
use httprequest::HttpRequest;
use error::PayloadError;
use encoding::PayloadType;
use payload::{Payload, PayloadError, PayloadWriter};
use payload::{Payload, PayloadWriter};
const KEEPALIVE_PERIOD: u64 = 15; // seconds

View file

@ -7,12 +7,11 @@ use futures::{Async, Future, Stream, Poll};
use url::form_urlencoded;
use http::{header, Method, Version, HeaderMap, Extensions};
use {Cookie, CookieParseError};
use {HttpRange, HttpRangeParseError};
use error::ParseError;
use {Cookie, HttpRange};
use recognizer::Params;
use payload::{Payload, PayloadError};
use multipart::{Multipart, MultipartError};
use payload::Payload;
use multipart::Multipart;
use error::{ParseError, PayloadError, MultipartError, CookieParseError, HttpRangeError};
/// An HTTP Request
@ -222,9 +221,10 @@ impl HttpRequest {
/// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file).
pub fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeParseError> {
pub fn range(&self, size: u64) -> Result<Vec<HttpRange>, HttpRangeError> {
if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else {
Ok(Vec::new())
}

View file

@ -1,6 +1,5 @@
//! Pieces pertaining to the HTTP message protocol.
//! Pieces pertaining to the HTTP response.
use std::{io, mem, str, fmt};
use std::error::Error as Error;
use std::convert::Into;
use cookie::CookieJar;
@ -33,7 +32,6 @@ pub struct HttpResponse {
chunked: bool,
encoding: ContentEncoding,
connection_type: Option<ConnectionType>,
error: Option<Box<Error>>,
response_size: u64,
}
@ -59,35 +57,10 @@ impl HttpResponse {
chunked: false,
encoding: ContentEncoding::Auto,
connection_type: None,
error: None,
response_size: 0,
}
}
/// Constructs a response from error
#[inline]
pub fn from_error<E: Error + 'static>(status: StatusCode, error: E) -> HttpResponse {
HttpResponse {
version: None,
headers: Default::default(),
status: status,
reason: None,
body: Body::from_slice(error.description().as_ref()),
chunked: false,
encoding: ContentEncoding::Auto,
connection_type: None,
error: Some(Box::new(error)),
response_size: 0,
}
}
/// The `error` which is responsible for this response
#[inline]
#[cfg_attr(feature="cargo-clippy", allow(borrowed_box))]
pub fn error(&self) -> Option<&Box<Error>> {
self.error.as_ref()
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Option<Version> {
@ -241,9 +214,6 @@ impl fmt::Debug for HttpResponse {
let _ = write!(f, " {:?}: {:?}\n", key, vals[0]);
}
}
if let Some(ref err) = self.error {
let _ = write!(f, " error: {}\n", err);
}
res
}
}
@ -445,7 +415,6 @@ impl HttpResponseBuilder {
chunked: parts.chunked,
encoding: parts.encoding,
connection_type: parts.connection_type,
error: None,
response_size: 0,
})
}

View file

@ -11,6 +11,9 @@ extern crate futures;
extern crate tokio_io;
extern crate tokio_core;
extern crate failure;
#[macro_use] extern crate failure_derive;
extern crate cookie;
extern crate http;
extern crate httparse;
@ -19,12 +22,16 @@ extern crate mime;
extern crate mime_guess;
extern crate url;
extern crate libc;
extern crate serde;
extern crate serde_json;
extern crate flate2;
extern crate brotli2;
extern crate percent_encoding;
extern crate actix;
extern crate h2 as http2;
extern crate redis_async;
#[cfg(feature="tls")]
extern crate native_tls;
#[cfg(feature="tls")]
@ -38,7 +45,6 @@ extern crate tokio_openssl;
mod application;
mod body;
mod context;
mod error;
mod date;
mod encoding;
mod httprequest;
@ -60,16 +66,16 @@ mod h2writer;
pub mod ws;
pub mod dev;
pub mod error;
pub mod httpcodes;
pub mod multipart;
pub mod middlewares;
pub use encoding::ContentEncoding;
pub use error::ParseError;
pub use body::{Body, Binary};
pub use application::{Application, ApplicationBuilder};
pub use httprequest::{HttpRequest, UrlEncoded};
pub use httpresponse::{HttpResponse, HttpResponseBuilder};
pub use payload::{Payload, PayloadItem, PayloadError};
pub use payload::{Payload, PayloadItem};
pub use route::{Frame, Route, RouteFactory, RouteHandler, RouteResult};
pub use resource::{Reply, Resource, HandlerResult};
pub use recognizer::{Params, RouteRecognizer};
@ -81,8 +87,7 @@ pub use staticfiles::StaticFiles;
// re-exports
pub use http::{Method, StatusCode, Version};
pub use cookie::{Cookie, CookieBuilder};
pub use cookie::{ParseError as CookieParseError};
pub use http_range::{HttpRange, HttpRangeParseError};
pub use http_range::HttpRange;
#[cfg(feature="tls")]
pub use native_tls::Pkcs12;

View file

@ -2,7 +2,6 @@
use std::{cmp, fmt};
use std::rc::Rc;
use std::cell::RefCell;
use std::error::Error;
use std::marker::PhantomData;
use mime;
@ -13,69 +12,11 @@ use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use futures::{Async, Stream, Poll};
use futures::task::{Task, current as current_task};
use error::ParseError;
use payload::{Payload, PayloadError};
use error::{ParseError, PayloadError, MultipartError};
use payload::Payload;
const MAX_HEADERS: usize = 32;
/// A set of errors that can occur during parsing multipart streams.
#[derive(Debug)]
pub enum MultipartError {
/// Content-Type header is not found
NoContentType,
/// Can not parse Content-Type header
ParseContentType,
/// Multipart boundary is not found
Boundary,
/// Error during field parsing
Parse(ParseError),
/// Payload error
Payload(PayloadError),
}
impl fmt::Display for MultipartError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MultipartError::Parse(ref e) => fmt::Display::fmt(e, f),
MultipartError::Payload(ref e) => fmt::Display::fmt(e, f),
ref e => f.write_str(e.description()),
}
}
}
impl Error for MultipartError {
fn description(&self) -> &str {
match *self {
MultipartError::NoContentType => "No Content-type header found",
MultipartError::ParseContentType => "Can not parse Content-Type header",
MultipartError::Boundary => "Multipart boundary is not found",
MultipartError::Parse(ref e) => e.description(),
MultipartError::Payload(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&Error> {
match *self {
MultipartError::Parse(ref error) => Some(error),
MultipartError::Payload(ref error) => Some(error),
_ => None,
}
}
}
impl From<ParseError> for MultipartError {
fn from(err: ParseError) -> MultipartError {
MultipartError::Parse(err)
}
}
impl From<PayloadError> for MultipartError {
fn from(err: PayloadError) -> MultipartError {
MultipartError::Payload(err)
}
}
/// The server-side implementation of `multipart/form-data` requests.
///
/// This will parse the incoming stream into `MultipartItem` instances via its

View file

@ -2,14 +2,12 @@ use std::{fmt, cmp};
use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::collections::VecDeque;
use std::error::Error;
use std::io::{Error as IoError};
use bytes::{Bytes, BytesMut};
use http2::Error as Http2Error;
use futures::{Async, Poll, Stream};
use futures::task::{Task, current as current_task};
use actix::ResponseType;
use error::PayloadError;
pub(crate) const DEFAULT_BUFFER_SIZE: usize = 65_536; // max buffer size 64k
@ -27,52 +25,6 @@ impl fmt::Debug for PayloadItem {
}
}
#[derive(Debug)]
/// A set of error that can occur during payload parsing.
pub enum PayloadError {
/// A payload reached EOF, but is not complete.
Incomplete,
/// Content encoding stream corruption
EncodingCorrupted,
/// Parse error
ParseError(IoError),
/// Http2 error
Http2(Http2Error),
}
impl fmt::Display for PayloadError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
PayloadError::ParseError(ref e) => fmt::Display::fmt(e, f),
ref e => f.write_str(e.description()),
}
}
}
impl Error for PayloadError {
fn description(&self) -> &str {
match *self {
PayloadError::Incomplete => "A payload reached EOF, but is not complete.",
PayloadError::EncodingCorrupted => "Can not decode content-encoding.",
PayloadError::ParseError(ref e) => e.description(),
PayloadError::Http2(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&Error> {
match *self {
PayloadError::ParseError(ref error) => Some(error),
_ => None,
}
}
}
impl From<IoError> for PayloadError {
fn from(err: IoError) -> PayloadError {
PayloadError::ParseError(err)
}
}
/// Stream of byte chunks
///
/// Payload stores chunks in vector. First chunk can be received with `.readany()` method.
@ -392,18 +344,17 @@ impl Inner {
mod tests {
use super::*;
use std::io;
use failure::Fail;
use futures::future::{lazy, result};
use tokio_core::reactor::Core;
#[test]
fn test_error() {
let err: PayloadError = IoError::new(io::ErrorKind::Other, "ParseError").into();
assert_eq!(err.description(), "ParseError");
assert_eq!(err.cause().unwrap().description(), "ParseError");
let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into();
assert_eq!(format!("{}", err), "ParseError");
assert_eq!(format!("{}", err.cause().unwrap()), "ParseError");
let err = PayloadError::Incomplete;
assert_eq!(err.description(), "A payload reached EOF, but is not complete.");
assert_eq!(format!("{}", err), "A payload reached EOF, but is not complete.");
}

View file

@ -8,6 +8,7 @@ use http::Method;
use futures::Stream;
use task::Task;
use error::Error;
use route::{Route, RouteHandler, RouteResult, Frame, FnHandler, StreamHandler};
use payload::Payload;
use context::HttpContext;
@ -16,7 +17,7 @@ use httpresponse::HttpResponse;
use httpcodes::{HTTPNotFound, HTTPMethodNotAllowed};
/// Result of a resource handler function
pub type HandlerResult<T> = Result<T, HttpResponse>;
pub type HandlerResult<T> = Result<T, Error>;
/// Http resource
///
@ -77,7 +78,7 @@ impl<S> Resource<S> where S: 'static {
/// Register async handler for specified method.
pub fn async<F, R>(&mut self, method: Method, handler: F)
where F: Fn(&mut HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
R: Stream<Item=Frame, Error=Error> + 'static,
{
self.routes.insert(method, Box::new(StreamHandler::new(handler)));
}

View file

@ -1,4 +1,3 @@
use std::io;
use std::rc::Rc;
use std::cell::RefCell;
use std::marker::PhantomData;
@ -9,6 +8,7 @@ use futures::Stream;
use task::{Task, DrainFut};
use body::Binary;
use error::Error;
use context::HttpContext;
use resource::Reply;
use payload::Payload;
@ -42,7 +42,7 @@ pub trait RouteHandler<S>: 'static {
}
/// Request handling result.
pub type RouteResult<T> = Result<Reply<T>, HttpResponse>;
pub type RouteResult<T> = Result<Reply<T>, Error>;
/// Actors with ability to handle http requests.
#[allow(unused_variables)]
@ -151,7 +151,7 @@ impl<S, R, F> RouteHandler<S> for FnHandler<S, R, F>
pub(crate)
struct StreamHandler<S, R, F>
where F: Fn(&mut HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
R: Stream<Item=Frame, Error=Error> + 'static,
S: 'static,
{
f: Box<F>,
@ -160,7 +160,7 @@ struct StreamHandler<S, R, F>
impl<S, R, F> StreamHandler<S, R, F>
where F: Fn(&mut HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
R: Stream<Item=Frame, Error=Error> + 'static,
S: 'static,
{
pub fn new(f: F) -> Self {
@ -170,14 +170,11 @@ impl<S, R, F> StreamHandler<S, R, F>
impl<S, R, F> RouteHandler<S> for StreamHandler<S, R, F>
where F: Fn(&mut HttpRequest, Payload, &S) -> R + 'static,
R: Stream<Item=Frame, Error=()> + 'static,
R: Stream<Item=Frame, Error=Error> + 'static,
S: 'static,
{
fn handle(&self, req: &mut HttpRequest, payload: Payload, state: Rc<S>) -> Task
{
Task::with_stream(
(self.f)(req, payload, &state).map_err(
|_| io::Error::new(io::ErrorKind::Other, ""))
)
Task::with_stream((self.f)(req, payload, &state))
}
}

View file

@ -1,4 +1,4 @@
use std::{mem, io};
use std::mem;
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque;
@ -7,12 +7,13 @@ use futures::{Async, Future, Poll, Stream};
use futures::task::{Task as FutureTask, current as current_task};
use h1writer::{Writer, WriterState};
use error::Error;
use route::Frame;
use middlewares::{Middleware, MiddlewaresExecutor};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
type FrameStream = Stream<Item=Frame, Error=io::Error>;
type FrameStream = Stream<Item=Frame, Error=Error>;
#[derive(PartialEq, Debug)]
enum TaskRunningState {
@ -53,10 +54,10 @@ impl TaskIOState {
enum TaskStream {
None,
Stream(Box<FrameStream>),
Context(Box<IoContext<Item=Frame, Error=io::Error>>),
Context(Box<IoContext<Item=Frame, Error=Error>>),
}
pub(crate) trait IoContext: Stream<Item=Frame, Error=io::Error> + 'static {
pub(crate) trait IoContext: Stream<Item=Frame, Error=Error> + 'static {
fn disconnected(&mut self);
}
@ -141,7 +142,7 @@ impl Task {
}
pub(crate) fn with_stream<S>(stream: S) -> Self
where S: Stream<Item=Frame, Error=io::Error> + 'static
where S: Stream<Item=Frame, Error=Error> + 'static
{
Task { state: TaskRunningState::Running,
iostate: TaskIOState::ReadingMessage,
@ -290,7 +291,7 @@ impl Task {
}
fn poll_stream<S>(&mut self, stream: &mut S) -> Poll<(), ()>
where S: Stream<Item=Frame, Error=io::Error> {
where S: Stream<Item=Frame, Error=Error> {
loop {
match stream.poll() {
Ok(Async::Ready(Some(frame))) => {

View file

@ -71,7 +71,7 @@ use body::Body;
use context::HttpContext;
use route::Route;
use payload::Payload;
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed};
use error::WsHandshakeError;
use httprequest::HttpRequest;
use httpresponse::{ConnectionType, HttpResponse};
@ -114,14 +114,10 @@ impl ResponseType for Message {
// /// `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<HttpResponse, HttpResponse> {
pub fn handshake(req: &HttpRequest) -> Result<HttpResponse, WsHandshakeError> {
// WebSocket accepts only GET
if *req.method() != Method::GET {
return Err(
HTTPMethodNotAllowed
.builder()
.header(header::ALLOW, "GET")
.finish()?)
return Err(WsHandshakeError::GetMethodRequired)
}
// Check for "UPGRADE" to websocket header
@ -135,17 +131,17 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponse, HttpResponse> {
false
};
if !has_hdr {
return Err(HTTPBadRequest.with_reason("No WebSocket UPGRADE header found"))
return Err(WsHandshakeError::NoWebsocketUpgrade)
}
// Upgrade connection
if !req.upgrade() {
return Err(HTTPBadRequest.with_reason("No CONNECTION upgrade"))
return Err(WsHandshakeError::NoConnectionUpgrade)
}
// check supported version
if !req.headers().contains_key(SEC_WEBSOCKET_VERSION) {
return Err(HTTPBadRequest.with_reason("No websocket version header is required"))
return Err(WsHandshakeError::NoVersionHeader)
}
let supported_ver = {
if let Some(hdr) = req.headers().get(SEC_WEBSOCKET_VERSION) {
@ -155,12 +151,12 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponse, HttpResponse> {
}
};
if !supported_ver {
return Err(HTTPBadRequest.with_reason("Unsupported version"))
return Err(WsHandshakeError::UnsupportedVersion)
}
// check client handshake for validity
if !req.headers().contains_key(SEC_WEBSOCKET_KEY) {
return Err(HTTPBadRequest.with_reason("Handshake error"));
return Err(WsHandshakeError::BadWebsocketKey)
}
let key = {
let key = req.headers().get(SEC_WEBSOCKET_KEY).unwrap();
@ -172,7 +168,7 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponse, HttpResponse> {
.header(header::UPGRADE, "websocket")
.header(header::TRANSFER_ENCODING, "chunked")
.header(SEC_WEBSOCKET_ACCEPT, key.as_str())
.body(Body::Upgrade)?
.body(Body::Upgrade).unwrap()
)
}
@ -338,44 +334,32 @@ impl WsWriter {
#[cfg(test)]
mod tests {
use http::{Method, HeaderMap, StatusCode, Version, header};
use super::{HttpRequest, SEC_WEBSOCKET_VERSION, SEC_WEBSOCKET_KEY, handshake};
use super::*;
use http::{Method, HeaderMap, Version, header};
#[test]
fn test_handshake() {
let req = HttpRequest::new(Method::POST, "/".to_owned(),
Version::HTTP_11, HeaderMap::new(), String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::METHOD_NOT_ALLOWED),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, HeaderMap::new(), String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::BAD_REQUEST),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE,
header::HeaderValue::from_static("test"));
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, headers, String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::BAD_REQUEST),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE,
header::HeaderValue::from_static("websocket"));
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, headers, String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::BAD_REQUEST),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE,
@ -384,10 +368,7 @@ mod tests {
header::HeaderValue::from_static("upgrade"));
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, headers, String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::BAD_REQUEST),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE,
@ -398,10 +379,7 @@ mod tests {
header::HeaderValue::from_static("5"));
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, headers, String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::BAD_REQUEST),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE,
@ -412,10 +390,7 @@ mod tests {
header::HeaderValue::from_static("13"));
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, headers, String::new());
match handshake(&req) {
Err(err) => assert_eq!(err.status(), StatusCode::BAD_REQUEST),
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
let mut headers = HeaderMap::new();
headers.insert(header::UPGRADE,
@ -428,11 +403,6 @@ mod tests {
header::HeaderValue::from_static("13"));
let req = HttpRequest::new(Method::GET, "/".to_owned(),
Version::HTTP_11, headers, String::new());
match handshake(&req) {
Ok(resp) => {
assert_eq!(resp.status(), StatusCode::SWITCHING_PROTOCOLS)
},
_ => panic!("should not happen"),
}
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
}
}

View file

@ -329,21 +329,21 @@ mod test {
#[test]
fn closecode_into_u16() {
assert_eq!(1000u16, CloseCode::Normal.into());
assert_eq!(1001u16, CloseCode::Away.into());
assert_eq!(1002u16, CloseCode::Protocol.into());
assert_eq!(1003u16, CloseCode::Unsupported.into());
assert_eq!(1005u16, CloseCode::Status.into());
assert_eq!(1006u16, CloseCode::Abnormal.into());
assert_eq!(1007u16, CloseCode::Invalid.into());
assert_eq!(1008u16, CloseCode::Policy.into());
assert_eq!(1009u16, CloseCode::Size.into());
assert_eq!(1010u16, CloseCode::Extension.into());
assert_eq!(1011u16, CloseCode::Error.into());
assert_eq!(1012u16, CloseCode::Restart.into());
assert_eq!(1013u16, CloseCode::Again.into());
assert_eq!(1015u16, CloseCode::Tls.into());
assert_eq!(0u16, CloseCode::Empty.into());
assert_eq!(2000u16, CloseCode::Other(2000).into());
assert_eq!(1000u16, Into::<u16>::into(CloseCode::Normal));
assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));
assert_eq!(1003u16, Into::<u16>::into(CloseCode::Unsupported));
assert_eq!(1005u16, Into::<u16>::into(CloseCode::Status));
assert_eq!(1006u16, Into::<u16>::into(CloseCode::Abnormal));
assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid));
assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy));
assert_eq!(1009u16, Into::<u16>::into(CloseCode::Size));
assert_eq!(1010u16, Into::<u16>::into(CloseCode::Extension));
assert_eq!(1011u16, Into::<u16>::into(CloseCode::Error));
assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart));
assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again));
assert_eq!(1015u16, Into::<u16>::into(CloseCode::Tls));
assert_eq!(0u16, Into::<u16>::into(CloseCode::Empty));
assert_eq!(2000u16, Into::<u16>::into(CloseCode::Other(2000)));
}
}