1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-01-18 05:41:50 +01:00

add HttpContext::drain()

This commit is contained in:
Nikolay Kim 2017-10-29 06:05:31 -07:00
parent 5cd25cc8b1
commit af1e0bac08
7 changed files with 156 additions and 50 deletions

View File

@ -38,7 +38,6 @@ mime = "0.3"
mime_guess = "1.8" mime_guess = "1.8"
cookie = { version="0.10", features=["percent-encode"] } cookie = { version="0.10", features=["percent-encode"] }
regex = "0.2" regex = "0.2"
slab = "0.4"
sha1 = "0.2" sha1 = "0.2"
url = "1.5" url = "1.5"
percent-encoding = "1.0" percent-encoding = "1.0"
@ -46,9 +45,8 @@ percent-encoding = "1.0"
# tokio # tokio
bytes = "0.4" bytes = "0.4"
futures = "0.1" futures = "0.1"
tokio-core = "0.1"
tokio-io = "0.1" tokio-io = "0.1"
tokio-proto = "0.1" tokio-core = "0.1"
# h2 = { git = 'https://github.com/carllerche/h2', optional = true } # h2 = { git = 'https://github.com/carllerche/h2', optional = true }
[dependencies.actix] [dependencies.actix]

View File

@ -91,7 +91,7 @@ impl Application<()> {
parts: Some(ApplicationBuilderParts { parts: Some(ApplicationBuilderParts {
state: (), state: (),
prefix: prefix.to_string(), prefix: prefix.to_string(),
default: Resource::default(), default: Resource::default_not_found(),
handlers: HashMap::new(), handlers: HashMap::new(),
resources: HashMap::new(), resources: HashMap::new(),
middlewares: Vec::new(), middlewares: Vec::new(),
@ -110,7 +110,7 @@ impl<S> Application<S> where S: 'static {
parts: Some(ApplicationBuilderParts { parts: Some(ApplicationBuilderParts {
state: state, state: state,
prefix: prefix.to_string(), prefix: prefix.to_string(),
default: Resource::default(), default: Resource::default_not_found(),
handlers: HashMap::new(), handlers: HashMap::new(),
resources: HashMap::new(), resources: HashMap::new(),
middlewares: Vec::new(), middlewares: Vec::new(),

View File

@ -1,7 +1,9 @@
use std; use std;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use futures::{Async, Stream, Poll}; use std::marker::PhantomData;
use futures::{Async, Future, Stream, Poll};
use futures::sync::oneshot::Sender; use futures::sync::oneshot::Sender;
use actix::{Actor, ActorState, ActorContext, AsyncContext, use actix::{Actor, ActorState, ActorContext, AsyncContext,
@ -10,7 +12,7 @@ use actix::fut::ActorFuture;
use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, SpawnHandle, use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, SpawnHandle,
Envelope, ToEnvelope, RemoteEnvelope}; Envelope, ToEnvelope, RemoteEnvelope};
use task::IoContext; use task::{IoContext, DrainFut};
use body::BinaryBody; use body::BinaryBody;
use route::{Route, Frame}; use route::{Route, Frame};
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -137,6 +139,14 @@ impl<A> HttpContext<A> where A: Actor<Context=Self> + Route {
self.stream.push_back(Frame::Payload(None)) self.stream.push_back(Frame::Payload(None))
} }
/// Returns drain future
pub fn drain(&mut self) -> Drain<A> {
let fut = Rc::new(RefCell::new(DrainFut::new()));
self.stream.push_back(Frame::Drain(fut.clone()));
self.modified = true;
Drain{ a: PhantomData, inner: fut }
}
/// Check if connection still open /// Check if connection still open
pub fn connected(&self) -> bool { pub fn connected(&self) -> bool {
!self.disconnected !self.disconnected
@ -199,6 +209,10 @@ impl<A> Stream for HttpContext<A> where A: Actor<Context=Self> + Route
// check wait futures // check wait futures
if self.wait.poll(act, ctx) { if self.wait.poll(act, ctx) {
// get frame
if let Some(frame) = self.stream.pop_front() {
return Ok(Async::Ready(Some(frame)))
}
return Ok(Async::NotReady) return Ok(Async::NotReady)
} }
@ -269,3 +283,21 @@ impl<A> ToEnvelope<A> for HttpContext<A>
RemoteEnvelope::new(msg, tx).into() RemoteEnvelope::new(msg, tx).into()
} }
} }
pub struct Drain<A> {
a: PhantomData<A>,
inner: Rc<RefCell<DrainFut>>
}
impl<A> ActorFuture for Drain<A>
where A: Actor
{
type Item = ();
type Error = ();
type Actor = A;
fn poll(&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context) -> Poll<(), ()> {
self.inner.borrow_mut().poll()
}
}

View File

@ -8,9 +8,8 @@ extern crate sha1;
extern crate regex; extern crate regex;
#[macro_use] #[macro_use]
extern crate futures; extern crate futures;
extern crate tokio_core;
extern crate tokio_io; extern crate tokio_io;
extern crate tokio_proto; extern crate tokio_core;
extern crate cookie; extern crate cookie;
extern crate http; extern crate http;

View File

@ -13,7 +13,7 @@ use payload::Payload;
use context::HttpContext; use context::HttpContext;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use httpcodes::HTTPMethodNotAllowed; use httpcodes::{HTTPNotFound, HTTPMethodNotAllowed};
/// Http resource /// Http resource
/// ///
@ -51,6 +51,14 @@ impl<S> Default for Resource<S> {
impl<S> Resource<S> where S: 'static { impl<S> Resource<S> where S: 'static {
pub(crate) fn default_not_found() -> Self {
Resource {
name: String::new(),
state: PhantomData,
routes: HashMap::new(),
default: Box::new(HTTPNotFound)}
}
/// Set resource name /// Set resource name
pub fn set_name<T: ToString>(&mut self, name: T) { pub fn set_name<T: ToString>(&mut self, name: T) {
self.name = name.to_string(); self.name = name.to_string();

View File

@ -1,12 +1,13 @@
use std::io; use std::io;
use std::rc::Rc; use std::rc::Rc;
use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use actix::Actor; use actix::Actor;
use http::{header, Version}; use http::{header, Version};
use futures::Stream; use futures::Stream;
use task::Task; use task::{Task, DrainFut};
use body::BinaryBody; use body::BinaryBody;
use context::HttpContext; use context::HttpContext;
use resource::Reply; use resource::Reply;
@ -21,9 +22,9 @@ use httpcodes::HTTPExpectationFailed;
pub enum Frame { pub enum Frame {
Message(HttpResponse), Message(HttpResponse),
Payload(Option<BinaryBody>), Payload(Option<BinaryBody>),
Drain(Rc<RefCell<DrainFut>>),
} }
/// Trait defines object that could be regestered as resource route /// Trait defines object that could be regestered as resource route
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait RouteHandler<S>: 'static { pub trait RouteHandler<S>: 'static {

View File

@ -1,6 +1,7 @@
use std::{mem, cmp, io}; use std::{mem, cmp, io};
use std::rc::Rc; use std::rc::Rc;
use std::fmt::Write; use std::fmt::Write;
use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use http::{StatusCode, Version}; use http::{StatusCode, Version};
@ -8,6 +9,7 @@ use http::header::{HeaderValue,
CONNECTION, CONTENT_TYPE, CONTENT_LENGTH, TRANSFER_ENCODING, DATE}; CONNECTION, CONTENT_TYPE, CONTENT_LENGTH, TRANSFER_ENCODING, DATE};
use bytes::BytesMut; use bytes::BytesMut;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use futures::task::{Task as FutureTask, current as current_task};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use date; use date;
@ -57,6 +59,45 @@ pub(crate) trait IoContext: Stream<Item=Frame, Error=io::Error> + 'static {
fn disconnected(&mut self); fn disconnected(&mut self);
} }
#[doc(hidden)]
#[derive(Debug)]
pub struct DrainFut {
drained: bool,
task: Option<FutureTask>,
}
impl DrainFut {
pub fn new() -> DrainFut {
DrainFut {
drained: false,
task: None,
}
}
fn set(&mut self) {
self.drained = true;
if let Some(task) = self.task.take() {
task.notify()
}
}
}
impl Future for DrainFut {
type Item = ();
type Error = ();
fn poll(&mut self) -> Poll<(), ()> {
if self.drained {
Ok(Async::Ready(()))
} else {
self.task = Some(current_task());
Ok(Async::NotReady)
}
}
}
pub struct Task { pub struct Task {
state: TaskRunningState, state: TaskRunningState,
iostate: TaskIOState, iostate: TaskIOState,
@ -64,6 +105,7 @@ pub struct Task {
stream: TaskStream, stream: TaskStream,
encoder: Encoder, encoder: Encoder,
buffer: BytesMut, buffer: BytesMut,
drain: Vec<Rc<RefCell<DrainFut>>>,
upgrade: bool, upgrade: bool,
keepalive: bool, keepalive: bool,
prepared: Option<HttpResponse>, prepared: Option<HttpResponse>,
@ -82,6 +124,7 @@ impl Task {
state: TaskRunningState::Running, state: TaskRunningState::Running,
iostate: TaskIOState::Done, iostate: TaskIOState::Done,
frames: frames, frames: frames,
drain: Vec::new(),
stream: TaskStream::None, stream: TaskStream::None,
encoder: Encoder::length(0), encoder: Encoder::length(0),
buffer: BytesMut::new(), buffer: BytesMut::new(),
@ -103,6 +146,7 @@ impl Task {
stream: TaskStream::Stream(Box::new(stream)), stream: TaskStream::Stream(Box::new(stream)),
encoder: Encoder::length(0), encoder: Encoder::length(0),
buffer: BytesMut::new(), buffer: BytesMut::new(),
drain: Vec::new(),
upgrade: false, upgrade: false,
keepalive: false, keepalive: false,
prepared: None, prepared: None,
@ -120,6 +164,7 @@ impl Task {
stream: TaskStream::Context(Box::new(ctx)), stream: TaskStream::Context(Box::new(ctx)),
encoder: Encoder::length(0), encoder: Encoder::length(0),
buffer: BytesMut::new(), buffer: BytesMut::new(),
drain: Vec::new(),
upgrade: false, upgrade: false,
keepalive: false, keepalive: false,
prepared: None, prepared: None,
@ -275,47 +320,53 @@ impl Task {
if self.frames.is_empty() && self.iostate.is_done() { if self.frames.is_empty() && self.iostate.is_done() {
return Ok(Async::Ready(self.state.is_done())); return Ok(Async::Ready(self.state.is_done()));
} else { } else {
// poll stream if self.drain.is_empty() {
if self.state == TaskRunningState::Running { // poll stream
match self.poll() { if self.state == TaskRunningState::Running {
Ok(Async::Ready(_)) => { match self.poll() {
self.state = TaskRunningState::Done; Ok(Async::Ready(_)) => {
} self.state = TaskRunningState::Done;
Ok(Async::NotReady) => (),
Err(_) => return Err(())
}
}
// use exiting frames
while let Some(frame) = self.frames.pop_front() {
trace!("IO Frame: {:?}", frame);
match frame {
Frame::Message(response) => {
if !self.disconnected {
self.prepare(req, response);
} }
Ok(Async::NotReady) => (),
Err(_) => return Err(())
} }
Frame::Payload(Some(chunk)) => { }
if !self.disconnected {
if self.prepared.is_some() { // use exiting frames
// TODO: add warning, write after EOF while let Some(frame) = self.frames.pop_front() {
self.encoder.encode(&mut self.buffer, chunk.as_ref()); trace!("IO Frame: {:?}", frame);
} else { match frame {
// might be response for EXCEPT Frame::Message(response) => {
self.buffer.extend_from_slice(chunk.as_ref()) if !self.disconnected {
self.prepare(req, response);
} }
} }
}, Frame::Payload(Some(chunk)) => {
Frame::Payload(None) => { if !self.disconnected {
if !self.disconnected && if self.prepared.is_some() {
!self.encoder.encode(&mut self.buffer, [].as_ref()) // TODO: add warning, write after EOF
{ self.encoder.encode(&mut self.buffer, chunk.as_ref());
// TODO: add error "not eof"" } else {
debug!("last payload item, but it is not EOF "); // might be response for EXCEPT
return Err(()) self.buffer.extend_from_slice(chunk.as_ref())
}
}
},
Frame::Payload(None) => {
if !self.disconnected &&
!self.encoder.encode(&mut self.buffer, [].as_ref())
{
// TODO: add error "not eof""
debug!("last payload item, but it is not EOF ");
return Err(())
}
break
},
Frame::Drain(fut) => {
self.drain.push(fut);
break
} }
break }
},
} }
} }
} }
@ -347,6 +398,23 @@ impl Task {
self.iostate = TaskIOState::Done; self.iostate = TaskIOState::Done;
} }
// drain
if self.buffer.is_empty() && !self.drain.is_empty() {
match io.flush() {
Ok(_) => (),
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
return Ok(Async::NotReady)
}
Err(_) => return Err(()),
}
for fut in &mut self.drain {
fut.borrow_mut().set()
}
self.drain.clear();
// return self.poll_io(io, req);
}
// response is completed // response is completed
if (self.buffer.is_empty() || self.disconnected) && self.iostate.is_done() { if (self.buffer.is_empty() || self.disconnected) && self.iostate.is_done() {
// run middlewares // run middlewares
@ -357,7 +425,6 @@ impl Task {
} }
} }
} }
Ok(Async::Ready(self.state.is_done())) Ok(Async::Ready(self.state.is_done()))
} else { } else {
Ok(Async::NotReady) Ok(Async::NotReady)
@ -391,6 +458,7 @@ impl Task {
return Err(()) return Err(())
} }
}, },
_ => (),
} }
self.frames.push_back(frame) self.frames.push_back(frame)
}, },
@ -399,7 +467,7 @@ impl Task {
Ok(Async::NotReady) => Ok(Async::NotReady) =>
return Ok(Async::NotReady), return Ok(Async::NotReady),
Err(_) => Err(_) =>
return Err(()) return Err(()),
} }
} }
} }