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:
parent
c565965865
commit
de71ad7de4
18 changed files with 317 additions and 325 deletions
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
293
src/error.rs
293
src/error.rs
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
10
src/h1.rs
10
src/h1.rs
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
|
15
src/route.rs
15
src/route.rs
|
@ -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, ""))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
src/task.rs
13
src/task.rs
|
@ -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))) => {
|
||||||
|
|
68
src/ws.rs
68
src/ws.rs
|
@ -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"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue