diff --git a/src/context.rs b/src/context.rs index 5e5985ed9..bcf59122b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -108,8 +108,8 @@ impl HttpContext where A: Actor + Route { } /// Write payload - pub fn write(&mut self, data: Bytes) { - self.stream.push_back(Frame::Payload(Some(data))) + pub fn write>(&mut self, data: B) { + self.stream.push_back(Frame::Payload(Some(data.into()))) } /// Indicate end of streamimng payload diff --git a/src/httpcodes.rs b/src/httpcodes.rs index 7ffa1a661..54d70ea5c 100644 --- a/src/httpcodes.rs +++ b/src/httpcodes.rs @@ -12,10 +12,29 @@ use httpresponse::{Body, HttpResponse, HttpResponseBuilder}; pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK); pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED); pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT); + pub const HTTPBadRequest: StaticResponse = StaticResponse(StatusCode::BAD_REQUEST); pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND); +pub const HTTPUnauthorized: StaticResponse = StaticResponse(StatusCode::UNAUTHORIZED); +pub const HTTPPaymentRequired: StaticResponse = StaticResponse(StatusCode::PAYMENT_REQUIRED); pub const HTTPForbidden: StaticResponse = StaticResponse(StatusCode::FORBIDDEN); -pub const HTTPMethodNotAllowed: StaticResponse = StaticResponse(StatusCode::METHOD_NOT_ALLOWED); + +pub const HTTPMethodNotAllowed: StaticResponse = + StaticResponse(StatusCode::METHOD_NOT_ALLOWED); +pub const HTTPNotAcceptable: StaticResponse = StaticResponse(StatusCode::NOT_ACCEPTABLE); +pub const HTTPProxyAuthenticationRequired: StaticResponse = + StaticResponse(StatusCode::PROXY_AUTHENTICATION_REQUIRED); +pub const HTTPRequestTimeout: StaticResponse = StaticResponse(StatusCode::REQUEST_TIMEOUT); +pub const HTTPConflict: StaticResponse = StaticResponse(StatusCode::CONFLICT); +pub const HTTPGone: StaticResponse = StaticResponse(StatusCode::GONE); +pub const HTTPLengthRequired: StaticResponse = StaticResponse(StatusCode::LENGTH_REQUIRED); +pub const HTTPPreconditionFailed: StaticResponse = + StaticResponse(StatusCode::PRECONDITION_FAILED); +pub const HTTPPayloadTooLarge: StaticResponse = StaticResponse(StatusCode::PAYLOAD_TOO_LARGE); +pub const HTTPUriTooLong: StaticResponse = StaticResponse(StatusCode::URI_TOO_LONG); +pub const HTTPExpectationFailed: StaticResponse = + StaticResponse(StatusCode::EXPECTATION_FAILED); + pub const HTTPInternalServerError: StaticResponse = StaticResponse(StatusCode::INTERNAL_SERVER_ERROR); @@ -34,6 +53,9 @@ impl StaticResponse { resp.set_reason(reason); resp } + pub fn with_body(self, body: Body) -> HttpResponse { + HttpResponse::new(self.0, body) + } } impl RouteHandler for StaticResponse { diff --git a/src/route.rs b/src/route.rs index f549379b6..e2cfcaf7f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -4,6 +4,7 @@ use std::marker::PhantomData; use actix::Actor; use bytes::Bytes; +use http::{header, Version}; use futures::Stream; use task::Task; @@ -11,7 +12,8 @@ use context::HttpContext; use resource::Reply; use payload::Payload; use httprequest::HttpRequest; -use httpresponse::HttpResponse; +use httpresponse::{Body, HttpResponse}; +use httpcodes::HTTPExpectationFailed; #[doc(hidden)] #[derive(Debug)] @@ -31,11 +33,39 @@ pub trait RouteHandler: 'static { } /// Actors with ability to handle http requests +#[allow(unused_variables)] pub trait Route: Actor { /// Route shared state. State is shared with all routes within same application and could be /// accessed with `HttpContext::state()` method. type State; + /// Handle `EXPECT` header. By default respond with `HTTP/1.1 100 Continue` + fn expect(req: &HttpRequest, ctx: &mut Self::Context) -> Result<(), HttpResponse> + where Self: Actor> + { + // handle expect header only for HTTP/1.1 + if req.version() == Version::HTTP_11 { + if let Some(expect) = req.headers().get(header::EXPECT) { + if let Ok(expect) = expect.to_str() { + if expect.to_lowercase() == "100-continue" { + ctx.write("HTTP/1.1 100 Continue\r\n\r\n"); + Ok(()) + } else { + Err(HTTPExpectationFailed.with_body( + Body::Binary("Unknown Expect".into()))) + } + } else { + Err(HTTPExpectationFailed.with_body( + Body::Binary("Unknown Expect".into()))) + } + } else { + Ok(()) + } + } else { + Ok(()) + } + } + /// Handle incoming request. Route actor can return /// result immediately with `Reply::reply` or `Reply::with`. /// Actor itself could be returned for handling streaming request/response. @@ -58,6 +88,13 @@ impl RouteHandler for RouteFactory fn handle(&self, req: HttpRequest, payload: Payload, state: Rc) -> Task { let mut ctx = HttpContext::new(state); + + // handle EXPECT header + if req.headers().contains_key(header::EXPECT) { + if let Err(resp) = A::expect(&req, &mut ctx) { + return Task::reply(resp) + } + } A::request(req, payload, &mut ctx).into(ctx) } } diff --git a/src/task.rs b/src/task.rs index 2fdd569cd..fd1523bf7 100644 --- a/src/task.rs +++ b/src/task.rs @@ -74,6 +74,7 @@ pub struct Task { buffer: BytesMut, upgrade: bool, keepalive: bool, + prepared: bool, } impl Task { @@ -92,6 +93,7 @@ impl Task { buffer: BytesMut::new(), upgrade: false, keepalive: false, + prepared: false, } } @@ -107,6 +109,7 @@ impl Task { buffer: BytesMut::new(), upgrade: false, keepalive: false, + prepared: false, } } @@ -122,6 +125,7 @@ impl Task { let body = msg.replace_body(Body::Empty); let version = msg.version().unwrap_or_else(|| req.version); self.keepalive = msg.keep_alive().unwrap_or_else(|| req.keep_alive); + self.prepared = true; match body { Body::Empty => { @@ -248,8 +252,13 @@ impl Task { Frame::Payload(chunk) => { match chunk { Some(chunk) => { - // TODO: add warning, write after EOF - self.encoder.encode(&mut self.buffer, chunk.as_ref()); + if self.prepared { + // TODO: add warning, write after EOF + self.encoder.encode(&mut self.buffer, chunk.as_ref()); + } else { + // might be response for EXCEPT + self.buffer.extend(chunk) + } } None => { // TODO: add error "not eof""