mirror of
https://github.com/actix/actix-web.git
synced 2024-11-18 15:41:17 +00:00
simplify Frame::Message; impl Try for Reply
This commit is contained in:
parent
c0e73c7275
commit
0447c66de1
13 changed files with 226 additions and 141 deletions
|
@ -21,10 +21,17 @@ path = "src/lib.rs"
|
||||||
name = "test"
|
name = "test"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["nightly"]
|
||||||
|
|
||||||
|
# Enable nightly features
|
||||||
|
nightly = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
http = "0.1"
|
http = "0.1"
|
||||||
httparse = "0.1"
|
httparse = "0.1"
|
||||||
|
cookie = { version="0.10", features=["percent-encode"] }
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
sha1 = "0.2"
|
sha1 = "0.2"
|
||||||
url = "1.5"
|
url = "1.5"
|
||||||
|
|
|
@ -9,7 +9,7 @@ use actix::fut::ActorFuture;
|
||||||
use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, SpawnHandle};
|
use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, SpawnHandle};
|
||||||
|
|
||||||
use route::{Route, Frame};
|
use route::{Route, Frame};
|
||||||
use httpmessage::{HttpRequest, HttpResponse};
|
use httpmessage::HttpResponse;
|
||||||
|
|
||||||
|
|
||||||
/// Actor execution context
|
/// Actor execution context
|
||||||
|
@ -102,8 +102,8 @@ impl<A> HttpContext<A> where A: Actor<Context=Self> + Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start response processing
|
/// Start response processing
|
||||||
pub fn start<R: Into<HttpResponse>>(&mut self, request: HttpRequest, response: R) {
|
pub fn start<R: Into<HttpResponse>>(&mut self, response: R) {
|
||||||
self.stream.push_back(Frame::Message(request, response.into()))
|
self.stream.push_back(Frame::Message(response.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write payload
|
/// Write payload
|
||||||
|
|
110
src/error.rs
110
src/error.rs
|
@ -5,27 +5,15 @@ use std::io::Error as IoError;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
use cookie;
|
||||||
use httparse;
|
use httparse;
|
||||||
|
use http::{StatusCode, Error as HttpError};
|
||||||
|
|
||||||
use self::Error::{
|
use httpmessage::{Body, HttpResponse};
|
||||||
Method,
|
|
||||||
Uri,
|
|
||||||
Version,
|
|
||||||
Header,
|
|
||||||
Status,
|
|
||||||
Timeout,
|
|
||||||
Io,
|
|
||||||
TooLarge,
|
|
||||||
Incomplete,
|
|
||||||
Utf8
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Result type often returned from methods that can have error.
|
|
||||||
pub type Result<T> = ::std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// A set of errors that can occur parsing HTTP streams.
|
/// A set of errors that can occur parsing HTTP streams.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum ParseError {
|
||||||
/// An invalid `Method`, such as `GE,T`.
|
/// An invalid `Method`, such as `GE,T`.
|
||||||
Method,
|
Method,
|
||||||
/// An invalid `Uri`, such as `exam ple.domain`.
|
/// An invalid `Uri`, such as `exam ple.domain`.
|
||||||
|
@ -43,79 +31,107 @@ pub enum Error {
|
||||||
/// A timeout occurred waiting for an IO event.
|
/// A timeout occurred waiting for an IO event.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
Timeout,
|
Timeout,
|
||||||
|
/// Unexpected EOF during parsing
|
||||||
|
Eof,
|
||||||
/// 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.
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
/// Parsing a field as string failed
|
/// Parsing a field as string failed
|
||||||
Utf8(Utf8Error),
|
Utf8(Utf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for ParseError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
Io(ref e) => fmt::Display::fmt(e, f),
|
ParseError::Io(ref e) => fmt::Display::fmt(e, f),
|
||||||
Utf8(ref e) => fmt::Display::fmt(e, f),
|
ParseError::Utf8(ref e) => fmt::Display::fmt(e, f),
|
||||||
ref e => f.write_str(e.description()),
|
ref e => f.write_str(e.description()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdError for Error {
|
impl StdError for ParseError {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
Method => "Invalid Method specified",
|
ParseError::Method => "Invalid Method specified",
|
||||||
Version => "Invalid HTTP version specified",
|
ParseError::Version => "Invalid HTTP version specified",
|
||||||
Header => "Invalid Header provided",
|
ParseError::Header => "Invalid Header provided",
|
||||||
TooLarge => "Message head is too large",
|
ParseError::TooLarge => "Message head is too large",
|
||||||
Status => "Invalid Status provided",
|
ParseError::Status => "Invalid Status provided",
|
||||||
Incomplete => "Message is incomplete",
|
ParseError::Incomplete => "Message is incomplete",
|
||||||
Timeout => "Timeout",
|
ParseError::Timeout => "Timeout",
|
||||||
Uri => "Uri error",
|
ParseError::Uri => "Uri error",
|
||||||
Io(ref e) => e.description(),
|
ParseError::Eof => "Unexpected eof during parse",
|
||||||
Utf8(ref e) => e.description(),
|
ParseError::Io(ref e) => e.description(),
|
||||||
|
ParseError::Utf8(ref e) => e.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&StdError> {
|
fn cause(&self) -> Option<&StdError> {
|
||||||
match *self {
|
match *self {
|
||||||
Io(ref error) => Some(error),
|
ParseError::Io(ref error) => Some(error),
|
||||||
Utf8(ref error) => Some(error),
|
ParseError::Utf8(ref error) => Some(error),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IoError> for Error {
|
impl From<IoError> for ParseError {
|
||||||
fn from(err: IoError) -> Error {
|
fn from(err: IoError) -> ParseError {
|
||||||
Io(err)
|
ParseError::Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Utf8Error> for Error {
|
impl From<Utf8Error> for ParseError {
|
||||||
fn from(err: Utf8Error) -> Error {
|
fn from(err: Utf8Error) -> ParseError {
|
||||||
Utf8(err)
|
ParseError::Utf8(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<FromUtf8Error> for Error {
|
impl From<FromUtf8Error> for ParseError {
|
||||||
fn from(err: FromUtf8Error) -> Error {
|
fn from(err: FromUtf8Error) -> ParseError {
|
||||||
Utf8(err.utf8_error())
|
ParseError::Utf8(err.utf8_error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<httparse::Error> for Error {
|
impl From<httparse::Error> for ParseError {
|
||||||
fn from(err: httparse::Error) -> Error {
|
fn from(err: httparse::Error) -> ParseError {
|
||||||
match err {
|
match err {
|
||||||
httparse::Error::HeaderName |
|
httparse::Error::HeaderName |
|
||||||
httparse::Error::HeaderValue |
|
httparse::Error::HeaderValue |
|
||||||
httparse::Error::NewLine |
|
httparse::Error::NewLine |
|
||||||
httparse::Error::Token => Header,
|
httparse::Error::Token => ParseError::Header,
|
||||||
httparse::Error::Status => Status,
|
httparse::Error::Status => ParseError::Status,
|
||||||
httparse::Error::TooManyHeaders => TooLarge,
|
httparse::Error::TooManyHeaders => ParseError::TooLarge,
|
||||||
httparse::Error::Version => Version,
|
httparse::Error::Version => ParseError::Version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return BadRequest for ParseError
|
||||||
|
impl From<ParseError> for HttpResponse {
|
||||||
|
fn from(err: ParseError) -> Self {
|
||||||
|
HttpResponse::new(StatusCode::BAD_REQUEST,
|
||||||
|
Body::Binary(err.description().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return InternalServerError for HttpError,
|
||||||
|
/// Response generation can return HttpError, so it is internal error
|
||||||
|
impl From<HttpError> for HttpResponse {
|
||||||
|
fn from(err: HttpError) -> Self {
|
||||||
|
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Body::Binary(err.description().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return BadRequest for cookie::ParseError
|
||||||
|
impl From<cookie::ParseError> for HttpResponse {
|
||||||
|
fn from(err: cookie::ParseError) -> Self {
|
||||||
|
HttpResponse::new(StatusCode::BAD_REQUEST,
|
||||||
|
Body::Binary(err.description().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
|
|
@ -35,8 +35,8 @@ impl StaticResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> RouteHandler<S> for StaticResponse {
|
impl<S> RouteHandler<S> for StaticResponse {
|
||||||
fn handle(&self, req: HttpRequest, _: Payload, _: Rc<S>) -> Task {
|
fn handle(&self, _: HttpRequest, _: Payload, _: Rc<S>) -> Task {
|
||||||
Task::reply(req, HttpResponse::new(self.0, Body::Empty))
|
Task::reply(HttpResponse::new(self.0, Body::Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//! Pieces pertaining to the HTTP message protocol.
|
//! Pieces pertaining to the HTTP message protocol.
|
||||||
use std::{io, mem};
|
use std::{io, mem, str};
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
|
|
||||||
|
use cookie;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{Method, StatusCode, Version, Uri, HeaderMap, HttpTryFrom, Error};
|
use http::{Method, StatusCode, Version, Uri, HeaderMap, HttpTryFrom, Error};
|
||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
|
@ -78,6 +78,17 @@ impl HttpRequest {
|
||||||
self.uri.query()
|
self.uri.query()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return request cookie.
|
||||||
|
pub fn cookie(&self) -> Result<Option<cookie::Cookie>, cookie::ParseError> {
|
||||||
|
if let Some(val) = self.headers.get(header::COOKIE) {
|
||||||
|
let s = str::from_utf8(val.as_bytes())
|
||||||
|
.map_err(|e| cookie::ParseError::from(e))?;
|
||||||
|
cookie::Cookie::parse(s).map(|c| Some(c))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the Request headers.
|
/// Get a mutable reference to the Request headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
|
@ -300,13 +311,7 @@ impl HttpResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for HttpResponse {
|
/// Helper conversion implementation
|
||||||
fn from(err: Error) -> Self {
|
|
||||||
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
Body::Binary(err.description().into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Into<HttpResponse>, E: Into<HttpResponse>> From<Result<I, E>> for HttpResponse {
|
impl<I: Into<HttpResponse>, E: Into<HttpResponse>> From<Result<I, E>> for HttpResponse {
|
||||||
fn from(res: Result<I, E>) -> Self {
|
fn from(res: Result<I, E>) -> Self {
|
||||||
match res {
|
match res {
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
//! Http framework for [Actix](https://github.com/fafhrd91/actix)
|
//! Http framework for [Actix](https://github.com/fafhrd91/actix)
|
||||||
|
|
||||||
|
#![cfg_attr(feature="nightly", feature(
|
||||||
|
try_trait, // std::ops::Try #42327
|
||||||
|
))]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
extern crate sha1;
|
extern crate sha1;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
|
extern crate cookie;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
|
|
21
src/main.rs
21
src/main.rs
|
@ -1,3 +1,4 @@
|
||||||
|
#![feature(try_trait)]
|
||||||
#![allow(dead_code, unused_variables)]
|
#![allow(dead_code, unused_variables)]
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_http;
|
extern crate actix_http;
|
||||||
|
@ -24,7 +25,7 @@ impl Route for MyRoute {
|
||||||
ctx.add_stream(payload);
|
ctx.add_stream(payload);
|
||||||
Reply::stream(MyRoute{req: Some(req)})
|
Reply::stream(MyRoute{req: Some(req)})
|
||||||
} else {
|
} else {
|
||||||
Reply::reply(req, httpcodes::HTTPOk)
|
Reply::reply(httpcodes::HTTPOk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +43,7 @@ impl Handler<PayloadItem> for MyRoute {
|
||||||
{
|
{
|
||||||
println!("CHUNK: {:?}", msg);
|
println!("CHUNK: {:?}", msg);
|
||||||
if let Some(req) = self.req.take() {
|
if let Some(req) = self.req.take() {
|
||||||
ctx.start(req, httpcodes::HTTPOk);
|
ctx.start(httpcodes::HTTPOk);
|
||||||
ctx.write_eof();
|
ctx.write_eof();
|
||||||
}
|
}
|
||||||
Self::empty()
|
Self::empty()
|
||||||
|
@ -58,16 +59,12 @@ impl Actor for MyWS {
|
||||||
impl Route for MyWS {
|
impl Route for MyWS {
|
||||||
type State = ();
|
type State = ();
|
||||||
|
|
||||||
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self> {
|
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self>
|
||||||
match ws::handshake(&req) {
|
{
|
||||||
Ok(resp) => {
|
let resp = ws::handshake(&req)?;
|
||||||
ctx.start(req, resp);
|
ctx.start(resp);
|
||||||
ctx.add_stream(ws::WsStream::new(payload));
|
ctx.add_stream(ws::WsStream::new(payload));
|
||||||
Reply::stream(MyWS{})
|
Reply::stream(MyWS{})
|
||||||
},
|
|
||||||
Err(err) =>
|
|
||||||
Reply::reply(req, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{self, fmt, io, ptr};
|
use std::{self, io, ptr};
|
||||||
|
|
||||||
use httparse;
|
use httparse;
|
||||||
use http::{Method, Version, Uri, HttpTryFrom, HeaderMap};
|
use http::{Method, Version, Uri, HttpTryFrom, HeaderMap};
|
||||||
|
@ -7,7 +7,7 @@ use bytes::{BytesMut, BufMut};
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use tokio_io::AsyncRead;
|
use tokio_io::AsyncRead;
|
||||||
|
|
||||||
use error::{Error, Result};
|
use error::ParseError;
|
||||||
use decode::Decoder;
|
use decode::Decoder;
|
||||||
use httpmessage::HttpRequest;
|
use httpmessage::HttpRequest;
|
||||||
use payload::{Payload, PayloadSender};
|
use payload::{Payload, PayloadSender};
|
||||||
|
@ -53,7 +53,7 @@ impl Reader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode(&mut self) -> std::result::Result<Decoding, Error>
|
fn decode(&mut self) -> std::result::Result<Decoding, ParseError>
|
||||||
{
|
{
|
||||||
if let Some(ref mut payload) = self.payload {
|
if let Some(ref mut payload) = self.payload {
|
||||||
if payload.tx.maybe_paused() {
|
if payload.tx.maybe_paused() {
|
||||||
|
@ -69,7 +69,7 @@ impl Reader {
|
||||||
return Ok(Decoding::Ready)
|
return Ok(Decoding::Ready)
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) => return Ok(Decoding::NotReady),
|
Ok(Async::NotReady) => return Ok(Decoding::NotReady),
|
||||||
Err(_) => return Err(Error::Incomplete),
|
Err(_) => return Err(ParseError::Incomplete),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,7 +77,7 @@ impl Reader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse<T>(&mut self, io: &mut T) -> Poll<(HttpRequest, Payload), Error>
|
pub fn parse<T>(&mut self, io: &mut T) -> Poll<(HttpRequest, Payload), ParseError>
|
||||||
where T: AsyncRead
|
where T: AsyncRead
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
|
@ -89,8 +89,7 @@ impl Reader {
|
||||||
},
|
},
|
||||||
Decoding::NotReady => {
|
Decoding::NotReady => {
|
||||||
if 0 == try_ready!(self.read_from_io(io)) {
|
if 0 == try_ready!(self.read_from_io(io)) {
|
||||||
return Err(io::Error::new(
|
return Err(ParseError::Eof)
|
||||||
io::ErrorKind::UnexpectedEof, ParseEof).into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,8 +118,7 @@ impl Reader {
|
||||||
match self.read_from_io(io) {
|
match self.read_from_io(io) {
|
||||||
Ok(Async::Ready(0)) => {
|
Ok(Async::Ready(0)) => {
|
||||||
trace!("parse eof");
|
trace!("parse eof");
|
||||||
return Err(io::Error::new(
|
return Err(ParseError::Eof);
|
||||||
io::ErrorKind::UnexpectedEof, ParseEof).into());
|
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(_)) => {
|
Ok(Async::Ready(_)) => {
|
||||||
continue
|
continue
|
||||||
|
@ -141,13 +139,13 @@ impl Reader {
|
||||||
None => {
|
None => {
|
||||||
if self.read_buf.capacity() >= MAX_BUFFER_SIZE {
|
if self.read_buf.capacity() >= MAX_BUFFER_SIZE {
|
||||||
debug!("MAX_BUFFER_SIZE reached, closing");
|
debug!("MAX_BUFFER_SIZE reached, closing");
|
||||||
return Err(Error::TooLarge);
|
return Err(ParseError::TooLarge);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if 0 == try_ready!(self.read_from_io(io)) {
|
if 0 == try_ready!(self.read_from_io(io)) {
|
||||||
trace!("parse eof");
|
trace!("parse eof");
|
||||||
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, ParseEof).into());
|
return Err(ParseError::Eof);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,23 +175,9 @@ impl Reader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ParseEof;
|
|
||||||
|
|
||||||
impl fmt::Display for ParseEof {
|
pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)>, ParseError>
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
{
|
||||||
f.write_str("parse eof")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::std::error::Error for ParseEof {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
"parse eof"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)>> {
|
|
||||||
if buf.is_empty() {
|
if buf.is_empty() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +195,8 @@ pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)
|
||||||
match try!(req.parse(buf)) {
|
match try!(req.parse(buf)) {
|
||||||
httparse::Status::Complete(len) => {
|
httparse::Status::Complete(len) => {
|
||||||
trace!("Request.parse Complete({})", len);
|
trace!("Request.parse Complete({})", len);
|
||||||
let method = Method::try_from(req.method.unwrap()).map_err(|_| Error::Method)?;
|
let method = Method::try_from(req.method.unwrap())
|
||||||
|
.map_err(|_| ParseError::Method)?;
|
||||||
let path = req.path.unwrap();
|
let path = req.path.unwrap();
|
||||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||||
let path_start = path.as_ptr() as usize - bytes_ptr;
|
let path_start = path.as_ptr() as usize - bytes_ptr;
|
||||||
|
@ -235,7 +220,7 @@ pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)
|
||||||
let slice = buf.split_to(len).freeze();
|
let slice = buf.split_to(len).freeze();
|
||||||
let path = slice.slice(path.0, path.1);
|
let path = slice.slice(path.0, path.1);
|
||||||
// path was found to be utf8 by httparse
|
// path was found to be utf8 by httparse
|
||||||
let uri = Uri::from_shared(path).map_err(|_| Error::Uri)?;
|
let uri = Uri::from_shared(path).map_err(|_| ParseError::Uri)?;
|
||||||
|
|
||||||
// convert headers
|
// convert headers
|
||||||
let mut headers = HeaderMap::with_capacity(headers_len);
|
let mut headers = HeaderMap::with_capacity(headers_len);
|
||||||
|
@ -246,10 +231,10 @@ pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)
|
||||||
{
|
{
|
||||||
headers.insert(name, value);
|
headers.insert(name, value);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Header)
|
return Err(ParseError::Header)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Header)
|
return Err(ParseError::Header)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,18 +248,18 @@ pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)
|
||||||
// Content-Length
|
// Content-Length
|
||||||
else if let Some(len) = msg.headers().get(header::CONTENT_LENGTH) {
|
else if let Some(len) = msg.headers().get(header::CONTENT_LENGTH) {
|
||||||
if chunked {
|
if chunked {
|
||||||
return Err(Error::Header)
|
return Err(ParseError::Header)
|
||||||
}
|
}
|
||||||
if let Ok(s) = len.to_str() {
|
if let Ok(s) = len.to_str() {
|
||||||
if let Ok(len) = s.parse::<u64>() {
|
if let Ok(len) = s.parse::<u64>() {
|
||||||
Some(Decoder::length(len))
|
Some(Decoder::length(len))
|
||||||
} else {
|
} else {
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
debug!("illegal Content-Length: {:?}", len);
|
||||||
return Err(Error::Header)
|
return Err(ParseError::Header)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
debug!("illegal Content-Length: {:?}", len);
|
||||||
return Err(Error::Header)
|
return Err(ParseError::Header)
|
||||||
}
|
}
|
||||||
} else if chunked {
|
} else if chunked {
|
||||||
Some(Decoder::chunked())
|
Some(Decoder::chunked())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::convert::From;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ impl<S: 'static> RouteHandler<S> for Resource<S> {
|
||||||
|
|
||||||
#[cfg_attr(feature="cargo-clippy", allow(large_enum_variant))]
|
#[cfg_attr(feature="cargo-clippy", allow(large_enum_variant))]
|
||||||
enum ReplyItem<A> where A: Actor + Route {
|
enum ReplyItem<A> where A: Actor + Route {
|
||||||
Message(HttpRequest, HttpResponse),
|
Message(HttpResponse),
|
||||||
Actor(A),
|
Actor(A),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,15 +125,15 @@ impl<A> Reply<A> where A: Actor + Route
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send response
|
/// Send response
|
||||||
pub fn reply<R: Into<HttpResponse>>(req: HttpRequest, response: R) -> Self {
|
pub fn reply<R: Into<HttpResponse>>(response: R) -> Self {
|
||||||
Reply(ReplyItem::Message(req, response.into()))
|
Reply(ReplyItem::Message(response.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into(self, mut ctx: HttpContext<A>) -> Task where A: Actor<Context=HttpContext<A>>
|
pub fn into(self, mut ctx: HttpContext<A>) -> Task where A: Actor<Context=HttpContext<A>>
|
||||||
{
|
{
|
||||||
match self.0 {
|
match self.0 {
|
||||||
ReplyItem::Message(req, msg) => {
|
ReplyItem::Message(msg) => {
|
||||||
Task::reply(req, msg)
|
Task::reply(msg)
|
||||||
},
|
},
|
||||||
ReplyItem::Actor(act) => {
|
ReplyItem::Actor(act) => {
|
||||||
ctx.set_actor(act);
|
ctx.set_actor(act);
|
||||||
|
@ -141,3 +142,32 @@ impl<A> Reply<A> where A: Actor + Route
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A, T> From<T> for Reply<A>
|
||||||
|
where T: Into<HttpResponse>, A: Actor + Route
|
||||||
|
{
|
||||||
|
fn from(item: T) -> Self {
|
||||||
|
Reply::reply(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="nightly")]
|
||||||
|
use std::ops::Try;
|
||||||
|
|
||||||
|
#[cfg(feature="nightly")]
|
||||||
|
impl<A> Try for Reply<A> where A: Actor + Route {
|
||||||
|
type Ok = HttpResponse;
|
||||||
|
type Error = HttpResponse;
|
||||||
|
|
||||||
|
fn into_result(self) -> Result<Self::Ok, Self::Error> {
|
||||||
|
panic!("Reply -> Result conversion is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_error(v: Self::Error) -> Self {
|
||||||
|
Reply::reply(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_ok(v: Self::Ok) -> Self {
|
||||||
|
Reply::reply(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use httpmessage::{HttpRequest, HttpResponse};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature="cargo-clippy", allow(large_enum_variant))]
|
#[cfg_attr(feature="cargo-clippy", allow(large_enum_variant))]
|
||||||
pub enum Frame {
|
pub enum Frame {
|
||||||
Message(HttpRequest, HttpResponse),
|
Message(HttpResponse),
|
||||||
Payload(Option<Bytes>),
|
Payload(Option<Bytes>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ impl Router {
|
||||||
return app.handle(req, payload)
|
return app.handle(req, payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Task::reply(req, HTTPNotFound.response())
|
Task::reply(HTTPNotFound.response())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{io, net};
|
use std::{io, net, mem};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ use actix::dev::*;
|
||||||
use futures::{Future, Poll, Async};
|
use futures::{Future, Poll, Async};
|
||||||
use tokio_core::net::{TcpListener, TcpStream};
|
use tokio_core::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
use task::Task;
|
use task::{Task, RequestInfo};
|
||||||
use reader::Reader;
|
use reader::Reader;
|
||||||
use router::{Router, RoutingMap};
|
use router::{Router, RoutingMap};
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ impl Handler<(TcpStream, net::SocketAddr), io::Error> for HttpServer {
|
||||||
addr: msg.1,
|
addr: msg.1,
|
||||||
stream: msg.0,
|
stream: msg.0,
|
||||||
reader: Reader::new(),
|
reader: Reader::new(),
|
||||||
|
error: false,
|
||||||
items: VecDeque::new(),
|
items: VecDeque::new(),
|
||||||
inactive: Vec::new(),
|
inactive: Vec::new(),
|
||||||
});
|
});
|
||||||
|
@ -65,6 +66,7 @@ impl Handler<(TcpStream, net::SocketAddr), io::Error> for HttpServer {
|
||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
task: Task,
|
task: Task,
|
||||||
|
req: RequestInfo,
|
||||||
eof: bool,
|
eof: bool,
|
||||||
error: bool,
|
error: bool,
|
||||||
finished: bool,
|
finished: bool,
|
||||||
|
@ -76,6 +78,7 @@ pub struct HttpChannel {
|
||||||
addr: net::SocketAddr,
|
addr: net::SocketAddr,
|
||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
|
error: bool,
|
||||||
items: VecDeque<Entry>,
|
items: VecDeque<Entry>,
|
||||||
inactive: Vec<Entry>,
|
inactive: Vec<Entry>,
|
||||||
}
|
}
|
||||||
|
@ -97,7 +100,13 @@ impl Future for HttpChannel {
|
||||||
if self.items[idx].error {
|
if self.items[idx].error {
|
||||||
return Err(())
|
return Err(())
|
||||||
}
|
}
|
||||||
match self.items[idx].task.poll_io(&mut self.stream) {
|
|
||||||
|
// this is anoying
|
||||||
|
let req: &RequestInfo = unsafe {
|
||||||
|
mem::transmute(&self.items[idx].req)
|
||||||
|
};
|
||||||
|
match self.items[idx].task.poll_io(&mut self.stream, req)
|
||||||
|
{
|
||||||
Ok(Async::Ready(val)) => {
|
Ok(Async::Ready(val)) => {
|
||||||
let mut item = self.items.pop_front().unwrap();
|
let mut item = self.items.pop_front().unwrap();
|
||||||
if !val {
|
if !val {
|
||||||
|
@ -107,7 +116,11 @@ impl Future for HttpChannel {
|
||||||
continue
|
continue
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Err(_) => return Err(()),
|
Err(_) => {
|
||||||
|
// it is not possible to recover from error
|
||||||
|
// during task handling, so just drop connection
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if !self.items[idx].finished {
|
} else if !self.items[idx].finished {
|
||||||
match self.items[idx].task.poll() {
|
match self.items[idx].task.poll() {
|
||||||
|
@ -121,19 +134,32 @@ impl Future for HttpChannel {
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for parse error
|
||||||
|
if self.items.is_empty() && self.error {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// read incoming data
|
// read incoming data
|
||||||
match self.reader.parse(&mut self.stream) {
|
if !self.error {
|
||||||
Ok(Async::Ready((req, payload))) => {
|
match self.reader.parse(&mut self.stream) {
|
||||||
self.items.push_back(
|
Ok(Async::Ready((req, payload))) => {
|
||||||
Entry {task: self.router.call(req, payload),
|
let info = RequestInfo::new(&req);
|
||||||
eof: false,
|
self.items.push_back(
|
||||||
error: false,
|
Entry {task: self.router.call(req, payload),
|
||||||
finished: false});
|
req: info,
|
||||||
},
|
eof: false,
|
||||||
Ok(Async::NotReady) =>
|
error: false,
|
||||||
return Ok(Async::NotReady),
|
finished: false});
|
||||||
Err(_) =>
|
}
|
||||||
return Err(()),
|
Ok(Async::NotReady) =>
|
||||||
|
return Ok(Async::NotReady),
|
||||||
|
Err(err) => return Err(())
|
||||||
|
//self.items.push_back(
|
||||||
|
// Entry {task: Task::reply(err),
|
||||||
|
// eof: false,
|
||||||
|
// error: false,
|
||||||
|
// finished: false})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
src/task.rs
36
src/task.rs
|
@ -44,6 +44,20 @@ impl TaskIOState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct RequestInfo {
|
||||||
|
version: Version,
|
||||||
|
keep_alive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestInfo {
|
||||||
|
pub fn new(req: &HttpRequest) -> Self {
|
||||||
|
RequestInfo {
|
||||||
|
version: req.version(),
|
||||||
|
keep_alive: req.keep_alive(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
state: TaskRunningState,
|
state: TaskRunningState,
|
||||||
iostate: TaskIOState,
|
iostate: TaskIOState,
|
||||||
|
@ -56,9 +70,9 @@ pub struct Task {
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
|
|
||||||
pub fn reply<R: Into<HttpResponse>>(req: HttpRequest, response: R) -> Self {
|
pub fn reply<R: Into<HttpResponse>>(response: R) -> Self {
|
||||||
let mut frames = VecDeque::new();
|
let mut frames = VecDeque::new();
|
||||||
frames.push_back(Frame::Message(req, response.into()));
|
frames.push_back(Frame::Message(response.into()));
|
||||||
frames.push_back(Frame::Payload(None));
|
frames.push_back(Frame::Payload(None));
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
|
@ -86,13 +100,13 @@ impl Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&mut self, req: HttpRequest, mut msg: HttpResponse)
|
fn prepare(&mut self, req: &RequestInfo, mut msg: HttpResponse)
|
||||||
{
|
{
|
||||||
trace!("Prepare message status={:?}", msg.status);
|
trace!("Prepare message status={:?}", msg.status);
|
||||||
|
|
||||||
let mut extra = 0;
|
let mut extra = 0;
|
||||||
let body = msg.replace_body(Body::Empty);
|
let body = msg.replace_body(Body::Empty);
|
||||||
let version = msg.version().unwrap_or_else(|| req.version());
|
let version = msg.version().unwrap_or_else(|| req.version);
|
||||||
|
|
||||||
match body {
|
match body {
|
||||||
Body::Empty => {
|
Body::Empty => {
|
||||||
|
@ -124,7 +138,7 @@ impl Task {
|
||||||
Body::Streaming => {
|
Body::Streaming => {
|
||||||
if msg.chunked() {
|
if msg.chunked() {
|
||||||
if version < Version::HTTP_11 {
|
if version < Version::HTTP_11 {
|
||||||
error!("Chunked transfer encoding is forbidden for {:?}", msg.version);
|
error!("Chunked transfer encoding is forbidden for {:?}", version);
|
||||||
}
|
}
|
||||||
msg.headers.remove(CONTENT_LENGTH);
|
msg.headers.remove(CONTENT_LENGTH);
|
||||||
msg.headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
msg.headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
||||||
|
@ -144,7 +158,7 @@ impl Task {
|
||||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
msg.headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
||||||
}
|
}
|
||||||
// keep-alive
|
// keep-alive
|
||||||
else if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
|
else if msg.keep_alive().unwrap_or_else(|| req.keep_alive) {
|
||||||
if version < Version::HTTP_11 {
|
if version < Version::HTTP_11 {
|
||||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
|
msg.headers.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
|
||||||
}
|
}
|
||||||
|
@ -159,7 +173,7 @@ impl Task {
|
||||||
if version == Version::HTTP_11 && msg.status == StatusCode::OK {
|
if version == Version::HTTP_11 && msg.status == StatusCode::OK {
|
||||||
self.buffer.extend(b"HTTP/1.1 200 OK\r\n");
|
self.buffer.extend(b"HTTP/1.1 200 OK\r\n");
|
||||||
} else {
|
} else {
|
||||||
let _ = write!(self.buffer, "{:?} {}\r\n", msg.version, msg.status);
|
let _ = write!(self.buffer, "{:?} {}\r\n", version, msg.status);
|
||||||
}
|
}
|
||||||
for (key, value) in &msg.headers {
|
for (key, value) in &msg.headers {
|
||||||
let t: &[u8] = key.as_ref();
|
let t: &[u8] = key.as_ref();
|
||||||
|
@ -192,7 +206,7 @@ impl Task {
|
||||||
msg.replace_body(body);
|
msg.replace_body(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn poll_io(&mut self, io: &mut TcpStream) -> Poll<bool, ()> {
|
pub(crate) fn poll_io(&mut self, io: &mut TcpStream, info: &RequestInfo) -> Poll<bool, ()> {
|
||||||
trace!("POLL-IO frames:{:?}", self.frames.len());
|
trace!("POLL-IO frames:{:?}", self.frames.len());
|
||||||
// response is completed
|
// response is completed
|
||||||
if self.frames.is_empty() && self.iostate.is_done() {
|
if self.frames.is_empty() && self.iostate.is_done() {
|
||||||
|
@ -213,8 +227,8 @@ impl Task {
|
||||||
while let Some(frame) = self.frames.pop_front() {
|
while let Some(frame) = self.frames.pop_front() {
|
||||||
trace!("IO Frame: {:?}", frame);
|
trace!("IO Frame: {:?}", frame);
|
||||||
match frame {
|
match frame {
|
||||||
Frame::Message(request, response) => {
|
Frame::Message(response) => {
|
||||||
self.prepare(request, response);
|
self.prepare(info, response);
|
||||||
}
|
}
|
||||||
Frame::Payload(chunk) => {
|
Frame::Payload(chunk) => {
|
||||||
match chunk {
|
match chunk {
|
||||||
|
@ -277,7 +291,7 @@ impl Future for Task {
|
||||||
match stream.poll() {
|
match stream.poll() {
|
||||||
Ok(Async::Ready(Some(frame))) => {
|
Ok(Async::Ready(Some(frame))) => {
|
||||||
match frame {
|
match frame {
|
||||||
Frame::Message(_, ref msg) => {
|
Frame::Message(ref msg) => {
|
||||||
if self.iostate != TaskIOState::ReadingMessage {
|
if self.iostate != TaskIOState::ReadingMessage {
|
||||||
error!("Non expected frame {:?}", frame);
|
error!("Non expected frame {:?}", frame);
|
||||||
return Err(())
|
return Err(())
|
||||||
|
|
Loading…
Reference in a new issue