1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-12-12 07:13:11 +01:00

simplify Frame::Message; impl Try for Reply

This commit is contained in:
Nikolay Kim 2017-10-13 14:43:17 -07:00
parent c0e73c7275
commit 0447c66de1
13 changed files with 226 additions and 141 deletions

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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))
} }
} }

View File

@ -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 {

View File

@ -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;

View File

@ -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)
}
} }
} }

View File

@ -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())

View File

@ -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)
}
}

View File

@ -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>),
} }

View File

@ -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())
} }
} }
} }

View File

@ -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})
}
} }
} }
} }

View File

@ -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(())