1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-30 10:32:55 +01: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 * HTTP/2 Support
* Refactor error handling
* Asynchronous middlewares * Asynchronous middlewares
* Content compression/decompression (br, gzip, deflate) * Content compression/decompression (br, gzip, deflate)

View File

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

View File

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

View File

@ -14,6 +14,7 @@ use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCel
use task::{IoContext, DrainFut}; use task::{IoContext, DrainFut};
use body::Binary; use body::Binary;
use error::Error;
use route::{Route, Frame}; use route::{Route, Frame};
use httpresponse::HttpResponse; 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 impl<A> Stream for HttpContext<A> where A: Actor<Context=Self> + Route
{ {
type Item = Frame; 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() { if self.act.is_none() {
return Ok(Async::NotReady) return Ok(Async::NotReady)
} }

View File

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

View File

@ -1,78 +1,113 @@
//! Error and Result module. //! Error and Result module.
use std::error::Error as StdError; use std::{fmt, result};
use std::fmt;
use std::io::Error as IoError;
use std::str::Utf8Error; use std::str::Utf8Error;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use std::io::Error as IoError;
use cookie; use cookie;
use httparse; 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 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. /// A set of errors that can occur during parsing HTTP streams.
#[derive(Debug)] #[derive(Fail, Debug)]
pub enum ParseError { pub enum ParseError {
/// An invalid `Method`, such as `GE,T`. /// An invalid `Method`, such as `GE,T`.
#[fail(display="Invalid Method specified")]
Method, Method,
/// An invalid `Uri`, such as `exam ple.domain`. /// An invalid `Uri`, such as `exam ple.domain`.
#[fail(display="Uri error")]
Uri, Uri,
/// An invalid `HttpVersion`, such as `HTP/1.1` /// An invalid `HttpVersion`, such as `HTP/1.1`
#[fail(display="Invalid HTTP version specified")]
Version, Version,
/// An invalid `Header`. /// An invalid `Header`.
#[fail(display="Invalid Header provided")]
Header, Header,
/// A message head is too large to be reasonable. /// A message head is too large to be reasonable.
#[fail(display="Message head is too large")]
TooLarge, TooLarge,
/// A message reached EOF, but is not complete. /// A message reached EOF, but is not complete.
#[fail(display="Message is incomplete")]
Incomplete, Incomplete,
/// An invalid `Status`, such as `1337 ELITE`. /// An invalid `Status`, such as `1337 ELITE`.
#[fail(display="Invalid Status provided")]
Status, Status,
/// A timeout occurred waiting for an IO event. /// A timeout occurred waiting for an IO event.
#[allow(dead_code)] #[allow(dead_code)]
#[fail(display="Timeout")]
Timeout, Timeout,
/// An `io::Error` that occurred while trying to read or write to a network stream. /// An `io::Error` that occurred while trying to read or write to a network stream.
#[fail(display="IO error: {}", _0)]
Io(IoError), Io(IoError),
/// Parsing a field as string failed /// Parsing a field as string failed
#[fail(display="UTF8 error: {}", _0)]
Utf8(Utf8Error), Utf8(Utf8Error),
} }
impl fmt::Display for ParseError { /// Return `BadRequest` for `ParseError`
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { impl ErrorResponse for ParseError {
match *self { fn error_response(&self) -> HttpResponse {
ParseError::Io(ref e) => fmt::Display::fmt(e, f), HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
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,
}
} }
} }
@ -108,47 +143,158 @@ impl From<httparse::Error> for ParseError {
} }
} }
/// Return `BadRequest` for `ParseError` #[derive(Fail, Debug)]
impl From<ParseError> for HttpResponse { /// A set of errors that can occur during payload parsing.
fn from(err: ParseError) -> Self { pub enum PayloadError {
HttpResponse::from_error(StatusCode::BAD_REQUEST, err) /// 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`, /// Return `InternalServerError` for `HttpError`,
/// Response generation can return `HttpError`, so it is internal error /// Response generation can return `HttpError`, so it is internal error
impl From<HttpError> for HttpResponse { impl ErrorResponse for HttpError {}
fn from(err: HttpError) -> Self {
HttpResponse::from_error(StatusCode::INTERNAL_SERVER_ERROR, err)
}
}
/// Return `InternalServerError` for `io::Error` /// Return `InternalServerError` for `io::Error`
impl From<IoError> for HttpResponse { impl ErrorResponse for IoError {}
fn from(err: IoError) -> Self {
HttpResponse::from_error(StatusCode::INTERNAL_SERVER_ERROR, err) /// 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` /// Http range header parsing error
impl From<cookie::ParseError> for HttpResponse { #[derive(Fail, Debug)]
fn from(err: cookie::ParseError) -> Self { pub enum HttpRangeError {
HttpResponse::from_error(StatusCode::BAD_REQUEST, err) /// 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` /// Return `BadRequest` for `MultipartError`
impl From<MultipartError> for HttpResponse { impl ErrorResponse for MultipartError {
fn from(err: MultipartError) -> Self {
HttpResponse::from_error(StatusCode::BAD_REQUEST, err) fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
} }
} }
/// Return `BadRequest` for `HttpRangeParseError` /// Websocket handshake errors
impl From<HttpRangeParseError> for HttpResponse { #[derive(Fail, PartialEq, Debug)]
fn from(_: HttpRangeParseError) -> Self { pub enum WsHandshakeError {
HttpResponse::new( /// Only get method is allowed
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided")) #[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 httparse;
use http::{StatusCode, Error as HttpError}; use http::{StatusCode, Error as HttpError};
use cookie::ParseError as CookieParseError; use cookie::ParseError as CookieParseError;
use super::{ParseError, HttpResponse, HttpRangeParseError, MultipartError}; use super::*;
#[test] #[test]
fn test_into_response() { fn test_into_response() {
let resp: HttpResponse = ParseError::Incomplete.into(); let resp: HttpResponse = ParseError::Incomplete.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); 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); 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); 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); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); 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); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
} }
@ -185,14 +331,14 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other"); let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned(); let desc = orig.description().to_owned();
let e = ParseError::Io(orig); let e = ParseError::Io(orig);
assert_eq!(e.cause().unwrap().description(), desc); assert_eq!(format!("{}", e.cause().unwrap()), desc);
} }
macro_rules! from { macro_rules! from {
($from:expr => $error:pat) => { ($from:expr => $error:pat) => {
match ParseError::from($from) { match ParseError::from($from) {
e @ $error => { e @ $error => {
assert!(e.description().len() >= 5); assert!(format!("{}", e).len() >= 5);
} , } ,
e => panic!("{:?}", e) e => panic!("{:?}", e)
} }
@ -203,9 +349,8 @@ mod tests {
($from:expr => $error:pat) => { ($from:expr => $error:pat) => {
match ParseError::from($from) { match ParseError::from($from) {
e @ $error => { e @ $error => {
let desc = e.cause().unwrap().description(); let desc = format!("{}", e.cause().unwrap());
assert_eq!(desc, $from.description().to_owned()); assert_eq!(desc, $from.description().to_owned());
assert_eq!(desc, e.description());
}, },
_ => panic!("{:?}", $from) _ => panic!("{:?}", $from)
} }

View File

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

View File

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

View File

@ -7,12 +7,11 @@ use futures::{Async, Future, Stream, Poll};
use url::form_urlencoded; use url::form_urlencoded;
use http::{header, Method, Version, HeaderMap, Extensions}; use http::{header, Method, Version, HeaderMap, Extensions};
use {Cookie, CookieParseError}; use {Cookie, HttpRange};
use {HttpRange, HttpRangeParseError};
use error::ParseError;
use recognizer::Params; use recognizer::Params;
use payload::{Payload, PayloadError}; use payload::Payload;
use multipart::{Multipart, MultipartError}; use multipart::Multipart;
use error::{ParseError, PayloadError, MultipartError, CookieParseError, HttpRangeError};
/// An HTTP Request /// An HTTP Request
@ -222,9 +221,10 @@ impl HttpRequest {
/// Parses Range HTTP header string as per RFC 2616. /// Parses Range HTTP header string as per RFC 2616.
/// `size` is full size of response (file). /// `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) { if let Some(range) = self.headers().get(header::RANGE) {
HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size) HttpRange::parse(unsafe{str::from_utf8_unchecked(range.as_bytes())}, size)
.map_err(|e| e.into())
} else { } else {
Ok(Vec::new()) 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::{io, mem, str, fmt};
use std::error::Error as Error;
use std::convert::Into; use std::convert::Into;
use cookie::CookieJar; use cookie::CookieJar;
@ -33,7 +32,6 @@ pub struct HttpResponse {
chunked: bool, chunked: bool,
encoding: ContentEncoding, encoding: ContentEncoding,
connection_type: Option<ConnectionType>, connection_type: Option<ConnectionType>,
error: Option<Box<Error>>,
response_size: u64, response_size: u64,
} }
@ -59,35 +57,10 @@ impl HttpResponse {
chunked: false, chunked: false,
encoding: ContentEncoding::Auto, encoding: ContentEncoding::Auto,
connection_type: None, connection_type: None,
error: None,
response_size: 0, 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. /// Get the HTTP version of this response.
#[inline] #[inline]
pub fn version(&self) -> Option<Version> { pub fn version(&self) -> Option<Version> {
@ -241,9 +214,6 @@ impl fmt::Debug for HttpResponse {
let _ = write!(f, " {:?}: {:?}\n", key, vals[0]); let _ = write!(f, " {:?}: {:?}\n", key, vals[0]);
} }
} }
if let Some(ref err) = self.error {
let _ = write!(f, " error: {}\n", err);
}
res res
} }
} }
@ -445,7 +415,6 @@ impl HttpResponseBuilder {
chunked: parts.chunked, chunked: parts.chunked,
encoding: parts.encoding, encoding: parts.encoding,
connection_type: parts.connection_type, connection_type: parts.connection_type,
error: None,
response_size: 0, response_size: 0,
}) })
} }

View File

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

View File

@ -2,7 +2,6 @@
use std::{cmp, fmt}; use std::{cmp, fmt};
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use std::error::Error;
use std::marker::PhantomData; use std::marker::PhantomData;
use mime; use mime;
@ -13,69 +12,11 @@ use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use futures::{Async, Stream, Poll}; use futures::{Async, Stream, Poll};
use futures::task::{Task, current as current_task}; use futures::task::{Task, current as current_task};
use error::ParseError; use error::{ParseError, PayloadError, MultipartError};
use payload::{Payload, PayloadError}; use payload::Payload;
const MAX_HEADERS: usize = 32; 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. /// The server-side implementation of `multipart/form-data` requests.
/// ///
/// This will parse the incoming stream into `MultipartItem` instances via its /// 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::rc::{Rc, Weak};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::error::Error;
use std::io::{Error as IoError};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http2::Error as Http2Error;
use futures::{Async, Poll, Stream}; use futures::{Async, Poll, Stream};
use futures::task::{Task, current as current_task}; use futures::task::{Task, current as current_task};
use actix::ResponseType; use actix::ResponseType;
use error::PayloadError;
pub(crate) const DEFAULT_BUFFER_SIZE: usize = 65_536; // max buffer size 64k 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 /// Stream of byte chunks
/// ///
/// Payload stores chunks in vector. First chunk can be received with `.readany()` method. /// Payload stores chunks in vector. First chunk can be received with `.readany()` method.
@ -392,18 +344,17 @@ impl Inner {
mod tests { mod tests {
use super::*; use super::*;
use std::io; use std::io;
use failure::Fail;
use futures::future::{lazy, result}; use futures::future::{lazy, result};
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
#[test] #[test]
fn test_error() { fn test_error() {
let err: PayloadError = IoError::new(io::ErrorKind::Other, "ParseError").into(); let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into();
assert_eq!(err.description(), "ParseError");
assert_eq!(err.cause().unwrap().description(), "ParseError");
assert_eq!(format!("{}", err), "ParseError"); assert_eq!(format!("{}", err), "ParseError");
assert_eq!(format!("{}", err.cause().unwrap()), "ParseError");
let err = PayloadError::Incomplete; 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."); 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 futures::Stream;
use task::Task; use task::Task;
use error::Error;
use route::{Route, RouteHandler, RouteResult, Frame, FnHandler, StreamHandler}; use route::{Route, RouteHandler, RouteResult, Frame, FnHandler, StreamHandler};
use payload::Payload; use payload::Payload;
use context::HttpContext; use context::HttpContext;
@ -16,7 +17,7 @@ use httpresponse::HttpResponse;
use httpcodes::{HTTPNotFound, HTTPMethodNotAllowed}; use httpcodes::{HTTPNotFound, HTTPMethodNotAllowed};
/// Result of a resource handler function /// Result of a resource handler function
pub type HandlerResult<T> = Result<T, HttpResponse>; pub type HandlerResult<T> = Result<T, Error>;
/// Http resource /// Http resource
/// ///
@ -77,7 +78,7 @@ impl<S> Resource<S> where S: 'static {
/// Register async handler for specified method. /// Register async handler for specified method.
pub fn async<F, R>(&mut self, method: Method, handler: F) pub fn async<F, R>(&mut self, method: Method, handler: F)
where F: Fn(&mut HttpRequest, Payload, &S) -> R + 'static, 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))); self.routes.insert(method, Box::new(StreamHandler::new(handler)));
} }

View File

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

View File

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

View File

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

View File

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