mirror of
https://github.com/fafhrd91/actix-web
synced 2025-01-18 05:41:50 +01:00
drop hyper
This commit is contained in:
parent
994a9e907e
commit
676347d7f6
@ -30,9 +30,6 @@ sha1 = "0.2"
|
||||
url = "1.5"
|
||||
route-recognizer = "0.1"
|
||||
|
||||
hyper = "0.11"
|
||||
unicase = "2.0"
|
||||
|
||||
# tokio
|
||||
bytes = "0.4"
|
||||
futures = "0.1"
|
||||
|
@ -16,6 +16,7 @@ use httpmessage::HttpRequest;
|
||||
pub struct Application<S=()> {
|
||||
state: S,
|
||||
default: Resource<S>,
|
||||
handlers: HashMap<String, Box<RouteHandler<S>>>,
|
||||
resources: HashMap<String, Resource<S>>,
|
||||
}
|
||||
|
||||
@ -23,6 +24,7 @@ impl<S> Application<S> where S: 'static
|
||||
{
|
||||
pub(crate) fn prepare(self, prefix: String) -> Box<Handler> {
|
||||
let mut router = Router::new();
|
||||
let mut handlers = HashMap::new();
|
||||
let prefix = if prefix.ends_with('/') {prefix } else { prefix + "/" };
|
||||
|
||||
for (path, handler) in self.resources {
|
||||
@ -30,10 +32,16 @@ impl<S> Application<S> where S: 'static
|
||||
router.add(path.as_str(), handler);
|
||||
}
|
||||
|
||||
for (path, mut handler) in self.handlers {
|
||||
let path = prefix.clone() + path.trim_left_matches('/');
|
||||
handler.set_prefix(path.clone());
|
||||
handlers.insert(path, handler);
|
||||
}
|
||||
Box::new(
|
||||
InnerApplication {
|
||||
state: Rc::new(self.state),
|
||||
default: self.default,
|
||||
handlers: handlers,
|
||||
router: router }
|
||||
)
|
||||
}
|
||||
@ -46,6 +54,7 @@ impl Default for Application<()> {
|
||||
Application {
|
||||
state: (),
|
||||
default: Resource::default(),
|
||||
handlers: HashMap::new(),
|
||||
resources: HashMap::new(),
|
||||
}
|
||||
}
|
||||
@ -60,6 +69,7 @@ impl<S> Application<S> where S: 'static {
|
||||
Application {
|
||||
state: state,
|
||||
default: Resource::default(),
|
||||
handlers: HashMap::new(),
|
||||
resources: HashMap::new(),
|
||||
}
|
||||
}
|
||||
@ -77,6 +87,20 @@ impl<S> Application<S> where S: 'static {
|
||||
self.resources.get_mut(&path).unwrap()
|
||||
}
|
||||
|
||||
/// Add path handler
|
||||
pub fn add_handler<H, P>(&mut self, path: P, h: H)
|
||||
where H: RouteHandler<S> + 'static, P: ToString
|
||||
{
|
||||
let path = path.to_string();
|
||||
|
||||
// add resource
|
||||
if self.handlers.contains_key(&path) {
|
||||
panic!("Handler already registered: {:?}", path);
|
||||
}
|
||||
|
||||
self.handlers.insert(path, Box::new(h));
|
||||
}
|
||||
|
||||
/// Default resource is used if no matches route could be found.
|
||||
pub fn default_resource(&mut self) -> &mut Resource<S> {
|
||||
&mut self.default
|
||||
@ -88,6 +112,7 @@ pub(crate)
|
||||
struct InnerApplication<S> {
|
||||
state: Rc<S>,
|
||||
default: Resource<S>,
|
||||
handlers: HashMap<String, Box<RouteHandler<S>>>,
|
||||
router: Router<Resource<S>>,
|
||||
}
|
||||
|
||||
@ -98,6 +123,11 @@ impl<S: 'static> Handler for InnerApplication<S> {
|
||||
if let Ok(h) = self.router.recognize(req.path()) {
|
||||
h.handler.handle(req.with_params(h.params), payload, Rc::clone(&self.state))
|
||||
} else {
|
||||
for (prefix, handler) in &self.handlers {
|
||||
if req.path().starts_with(prefix) {
|
||||
return handler.handle(req, payload, Rc::clone(&self.state))
|
||||
}
|
||||
}
|
||||
self.default.handle(req, payload, Rc::clone(&self.state))
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ impl<A> AsyncContextApi<A> for HttpContext<A> where A: Actor<Context=Self> + Rou
|
||||
|
||||
impl<A> HttpContext<A> where A: Actor<Context=Self> + Route {
|
||||
|
||||
pub(crate) fn new(state: Rc<<A as Route>::State>) -> HttpContext<A>
|
||||
pub fn new(state: Rc<<A as Route>::State>) -> HttpContext<A>
|
||||
{
|
||||
HttpContext {
|
||||
act: None,
|
||||
|
21
src/dev.rs
Normal file
21
src/dev.rs
Normal file
@ -0,0 +1,21 @@
|
||||
//! The `actix-http` prelude for library developers
|
||||
//!
|
||||
//! The purpose of this module is to alleviate imports of many common actix traits
|
||||
//! by adding a glob import to the top of actix heavy modules:
|
||||
//!
|
||||
//! ```
|
||||
//! # #![allow(unused_imports)]
|
||||
//! use actix_http::dev::*;
|
||||
//! ```
|
||||
pub use ws;
|
||||
pub use httpcodes;
|
||||
pub use application::Application;
|
||||
pub use httpmessage::{HttpRequest, HttpResponse, IntoHttpResponse};
|
||||
pub use payload::{Payload, PayloadItem};
|
||||
pub use router::RoutingMap;
|
||||
pub use resource::{Reply, Resource};
|
||||
pub use route::{Route, RouteFactory, RouteHandler};
|
||||
pub use server::HttpServer;
|
||||
pub use context::HttpContext;
|
||||
pub use task::Task;
|
||||
pub use route_recognizer::Params;
|
@ -1,13 +1,10 @@
|
||||
//! Pieces pertaining to the HTTP message protocol.
|
||||
use std::{io, mem};
|
||||
use std::str::FromStr;
|
||||
use std::convert::Into;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::{Method, StatusCode, Version, Uri};
|
||||
use hyper::header::{Header, Headers};
|
||||
use hyper::header::{Connection, ConnectionOption,
|
||||
Expect, Encoding, ContentLength, TransferEncoding};
|
||||
use http::{Method, StatusCode, Version, Uri, HeaderMap};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
|
||||
use Params;
|
||||
use error::Error;
|
||||
@ -23,43 +20,44 @@ pub trait Message {
|
||||
|
||||
fn version(&self) -> Version;
|
||||
|
||||
fn headers(&self) -> &Headers;
|
||||
fn headers(&self) -> &HeaderMap;
|
||||
|
||||
/// Checks if a connection should be kept alive.
|
||||
fn should_keep_alive(&self) -> bool {
|
||||
let ret = match (self.version(), self.headers().get::<Connection>()) {
|
||||
(Version::HTTP_10, None) => false,
|
||||
(Version::HTTP_10, Some(conn))
|
||||
if !conn.contains(&ConnectionOption::KeepAlive) => false,
|
||||
(Version::HTTP_11, Some(conn))
|
||||
if conn.contains(&ConnectionOption::Close) => false,
|
||||
_ => true
|
||||
};
|
||||
trace!("should_keep_alive(version={:?}, header={:?}) = {:?}",
|
||||
self.version(), self.headers().get::<Connection>(), ret);
|
||||
ret
|
||||
fn keep_alive(&self) -> bool {
|
||||
if let Some(conn) = self.headers().get(header::CONNECTION) {
|
||||
if let Ok(conn) = conn.to_str() {
|
||||
if self.version() == Version::HTTP_10 && !conn.contains("keep-alive") {
|
||||
false
|
||||
} else if self.version() == Version::HTTP_11 && conn.contains("close") {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.version() != Version::HTTP_10
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a connection is expecting a `100 Continue` before sending its body.
|
||||
#[inline]
|
||||
fn expecting_continue(&self) -> bool {
|
||||
let ret = match (self.version(), self.headers().get::<Expect>()) {
|
||||
(Version::HTTP_11, Some(&Expect::Continue)) => true,
|
||||
_ => false
|
||||
};
|
||||
trace!("expecting_continue(version={:?}, header={:?}) = {:?}",
|
||||
self.version(), self.headers().get::<Expect>(), ret);
|
||||
ret
|
||||
if self.version() == Version::HTTP_11 {
|
||||
if let Some(hdr) = self.headers().get(header::EXPECT) {
|
||||
if let Ok(hdr) = hdr.to_str() {
|
||||
return hdr.to_lowercase().contains("continue")
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_chunked(&self) -> Result<bool, Error> {
|
||||
if let Some(&TransferEncoding(ref encodings)) = self.headers().get() {
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
// If Transfer-Encoding header is present, and 'chunked' is
|
||||
// not the final encoding, and this is a Request, then it is
|
||||
// mal-formed. A server should responsed with 400 Bad Request.
|
||||
if encodings.last() == Some(&Encoding::Chunked) {
|
||||
Ok(true)
|
||||
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
|
||||
if let Ok(s) = encodings.to_str() {
|
||||
return Ok(s.to_lowercase().contains("chunked"))
|
||||
} else {
|
||||
debug!("request with transfer-encoding header, but not chunked, bad request");
|
||||
Err(Error::Header)
|
||||
@ -77,7 +75,7 @@ pub struct HttpRequest {
|
||||
version: Version,
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
headers: Headers,
|
||||
headers: HeaderMap,
|
||||
params: Params,
|
||||
}
|
||||
|
||||
@ -85,7 +83,7 @@ impl Message for HttpRequest {
|
||||
fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
fn headers(&self) -> &Headers {
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
}
|
||||
@ -93,7 +91,7 @@ impl Message for HttpRequest {
|
||||
impl HttpRequest {
|
||||
/// Construct a new Request.
|
||||
#[inline]
|
||||
pub fn new(method: Method, uri: Uri, version: Version, headers: Headers) -> Self {
|
||||
pub fn new(method: Method, uri: Uri, version: Version, headers: HeaderMap) -> Self {
|
||||
HttpRequest {
|
||||
method: method,
|
||||
uri: uri,
|
||||
@ -113,7 +111,7 @@ impl HttpRequest {
|
||||
|
||||
/// Read the Request headers.
|
||||
#[inline]
|
||||
pub fn headers(&self) -> &Headers { &self.headers }
|
||||
pub fn headers(&self) -> &HeaderMap { &self.headers }
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
@ -142,7 +140,7 @@ impl HttpRequest {
|
||||
|
||||
/// Get a mutable reference to the Request headers.
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut Headers {
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
@ -164,28 +162,14 @@ impl HttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is keepalive enabled by client?
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
let ret = match (self.version(), self.headers().get::<Connection>()) {
|
||||
(Version::HTTP_10, None) => false,
|
||||
(Version::HTTP_10, Some(conn))
|
||||
if !conn.contains(&ConnectionOption::KeepAlive) => false,
|
||||
(Version::HTTP_11, Some(conn))
|
||||
if conn.contains(&ConnectionOption::Close) => false,
|
||||
_ => true
|
||||
};
|
||||
trace!("should_keep_alive(version={:?}, header={:?}) = {:?}",
|
||||
self.version(), self.headers().get::<Connection>(), ret);
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn is_upgrade(&self) -> bool {
|
||||
if let Some(&Connection(ref conn)) = self.headers().get() {
|
||||
conn.contains(&ConnectionOption::from_str("upgrade").unwrap())
|
||||
} else {
|
||||
false
|
||||
if let Some(ref conn) = self.headers().get(header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
return s.to_lowercase().contains("upgrade")
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents various types of http message body.
|
||||
@ -225,12 +209,12 @@ pub trait IntoHttpResponse {
|
||||
pub struct HttpResponse {
|
||||
request: HttpRequest,
|
||||
pub version: Version,
|
||||
pub headers: Headers,
|
||||
pub headers: HeaderMap,
|
||||
pub status: StatusCode,
|
||||
reason: Option<&'static str>,
|
||||
body: Body,
|
||||
chunked: bool,
|
||||
compression: Option<Encoding>,
|
||||
// compression: Option<Encoding>,
|
||||
connection_type: Option<ConnectionType>,
|
||||
}
|
||||
|
||||
@ -238,7 +222,7 @@ impl Message for HttpResponse {
|
||||
fn version(&self) -> Version {
|
||||
self.version
|
||||
}
|
||||
fn headers(&self) -> &Headers {
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
}
|
||||
@ -256,7 +240,7 @@ impl HttpResponse {
|
||||
reason: None,
|
||||
body: body,
|
||||
chunked: false,
|
||||
compression: None,
|
||||
// compression: None,
|
||||
connection_type: None,
|
||||
}
|
||||
}
|
||||
@ -275,13 +259,13 @@ impl HttpResponse {
|
||||
|
||||
/// Get the headers from the response.
|
||||
#[inline]
|
||||
pub fn headers(&self) -> &Headers {
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the headers.
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut Headers {
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
|
||||
@ -300,14 +284,14 @@ impl HttpResponse {
|
||||
|
||||
/// Set a header and move the Response.
|
||||
#[inline]
|
||||
pub fn set_header<H: Header>(mut self, header: H) -> Self {
|
||||
self.headers.set(header);
|
||||
pub fn set_header(mut self, name: HeaderName, value: HeaderValue) -> Self {
|
||||
self.headers.insert(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the headers.
|
||||
#[inline]
|
||||
pub fn with_headers(mut self, headers: Headers) -> Self {
|
||||
pub fn with_headers(mut self, headers: HeaderMap) -> Self {
|
||||
self.headers = headers;
|
||||
self
|
||||
}
|
||||
@ -335,7 +319,7 @@ impl HttpResponse {
|
||||
if let Some(ConnectionType::KeepAlive) = self.connection_type {
|
||||
true
|
||||
} else {
|
||||
self.request.should_keep_alive()
|
||||
self.request.keep_alive()
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,7 +335,7 @@ impl HttpResponse {
|
||||
|
||||
/// Enables automatic chunked transfer encoding
|
||||
pub fn enable_chunked_encoding(&mut self) -> Result<(), io::Error> {
|
||||
if self.headers.has::<ContentLength>() {
|
||||
if self.headers.contains_key(header::CONTENT_LENGTH) {
|
||||
Err(io::Error::new(io::ErrorKind::Other,
|
||||
"You can't enable chunked encoding when a content length is set"))
|
||||
} else {
|
||||
|
@ -11,9 +11,6 @@ extern crate futures;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_proto;
|
||||
#[macro_use]
|
||||
extern crate hyper;
|
||||
extern crate unicase;
|
||||
|
||||
extern crate http;
|
||||
extern crate httparse;
|
||||
@ -33,11 +30,11 @@ mod router;
|
||||
mod task;
|
||||
mod reader;
|
||||
mod server;
|
||||
|
||||
pub mod ws;
|
||||
mod wsframe;
|
||||
mod wsproto;
|
||||
|
||||
pub mod ws;
|
||||
pub mod dev;
|
||||
pub mod httpcodes;
|
||||
pub use application::Application;
|
||||
pub use httpmessage::{HttpRequest, HttpResponse, IntoHttpResponse};
|
||||
|
@ -1,13 +1,12 @@
|
||||
use std::{self, fmt, io, ptr};
|
||||
|
||||
use httparse;
|
||||
use http::{Method, Version, Uri, HttpTryFrom};
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
use http::{Method, Version, Uri, HttpTryFrom, HeaderMap};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use bytes::{BytesMut, BufMut};
|
||||
use futures::{Async, Poll};
|
||||
use tokio_io::AsyncRead;
|
||||
|
||||
use hyper::header::{Headers, ContentLength};
|
||||
|
||||
use error::{Error, Result};
|
||||
use decode::Decoder;
|
||||
use payload::{Payload, PayloadSender};
|
||||
@ -50,8 +49,7 @@ impl Reader {
|
||||
b'\r' | b'\n' => i += 1,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
self.read_buf.split_to(i);
|
||||
} self.read_buf.split_to(i);
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,13 +80,10 @@ impl Reader {
|
||||
pub fn parse<T>(&mut self, io: &mut T) -> Poll<(HttpRequest, Payload), Error>
|
||||
where T: AsyncRead
|
||||
{
|
||||
|
||||
|
||||
loop {
|
||||
match self.decode()? {
|
||||
Decoding::Paused => return Ok(Async::NotReady),
|
||||
Decoding::Ready => {
|
||||
println!("decode ready");
|
||||
self.payload = None;
|
||||
break
|
||||
},
|
||||
@ -117,7 +112,6 @@ impl Reader {
|
||||
Decoding::Paused =>
|
||||
break,
|
||||
Decoding::Ready => {
|
||||
println!("decoded 3");
|
||||
self.payload = None;
|
||||
break
|
||||
},
|
||||
@ -238,38 +232,56 @@ pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)
|
||||
}
|
||||
};
|
||||
|
||||
let mut headers = Headers::with_capacity(headers_len);
|
||||
let slice = buf.split_to(len).freeze();
|
||||
let path = slice.slice(path.0, path.1);
|
||||
// path was found to be utf8 by httparse
|
||||
let uri = Uri::from_shared(path).map_err(|_| Error::Uri)?;
|
||||
|
||||
headers.extend(HeadersAsBytesIter {
|
||||
headers: headers_indices[..headers_len].iter(),
|
||||
slice: slice,
|
||||
});
|
||||
// convert headers
|
||||
let mut headers = HeaderMap::with_capacity(headers_len);
|
||||
for header in headers_indices[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::try_from(slice.slice(header.name.0, header.name.1)) {
|
||||
if let Ok(value) = HeaderValue::try_from(
|
||||
slice.slice(header.value.0, header.value.1))
|
||||
{
|
||||
headers.insert(name, value);
|
||||
} else {
|
||||
return Err(Error::Header)
|
||||
}
|
||||
} else {
|
||||
return Err(Error::Header)
|
||||
}
|
||||
}
|
||||
|
||||
let msg = HttpRequest::new(method, uri, version, headers);
|
||||
let upgrade = msg.is_upgrade() || *msg.method() == Method::CONNECT;
|
||||
let chunked = msg.is_chunked()?;
|
||||
|
||||
if upgrade {
|
||||
Ok(Some((msg, Some(Decoder::eof()))))
|
||||
let decoder = if upgrade {
|
||||
Some(Decoder::eof())
|
||||
}
|
||||
// Content-Length
|
||||
else if let Some(&ContentLength(len)) = msg.headers().get() {
|
||||
else if let Some(ref len) = msg.headers().get(header::CONTENT_LENGTH) {
|
||||
if chunked {
|
||||
return Err(Error::Header)
|
||||
}
|
||||
Ok(Some((msg, Some(Decoder::length(len)))))
|
||||
} else if msg.headers().has::<ContentLength>() {
|
||||
debug!("illegal Content-Length: {:?}", msg.headers().get_raw("Content-Length"));
|
||||
Err(Error::Header)
|
||||
} else if chunked {
|
||||
Ok(Some((msg, Some(Decoder::chunked()))))
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
Some(Decoder::length(len))
|
||||
} else {
|
||||
Ok(Some((msg, None)))
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(Error::Header)
|
||||
}
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(Error::Header)
|
||||
}
|
||||
} else if chunked {
|
||||
Some(Decoder::chunked())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Some((msg, decoder)))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@ -292,24 +304,3 @@ fn record_header_indices(bytes: &[u8],
|
||||
indices.value = (value_start, value_end);
|
||||
}
|
||||
}
|
||||
|
||||
struct HeadersAsBytesIter<'a> {
|
||||
headers: ::std::slice::Iter<'a, HeaderIndices>,
|
||||
slice: Bytes,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for HeadersAsBytesIter<'a> {
|
||||
type Item = (&'a str, Bytes);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.headers.next().map(|header| {
|
||||
let name = unsafe {
|
||||
let bytes = ::std::slice::from_raw_parts(
|
||||
self.slice.as_ref().as_ptr().offset(header.name.0 as isize),
|
||||
header.name.1 - header.name.0
|
||||
);
|
||||
::std::str::from_utf8_unchecked(bytes)
|
||||
};
|
||||
(name, self.slice.slice(header.value.0, header.value.1))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -66,25 +66,29 @@ impl<S> Resource<S> where S: 'static {
|
||||
}
|
||||
|
||||
/// Handler for `GET` method.
|
||||
pub fn get<A>(&mut self) -> &mut Self where A: Route<State=S>
|
||||
pub fn get<A>(&mut self) -> &mut Self
|
||||
where A: Actor<Context=HttpContext<A>> + Route<State=S>
|
||||
{
|
||||
self.handler(Method::GET, A::factory())
|
||||
}
|
||||
|
||||
/// Handler for `POST` method.
|
||||
pub fn post<A>(&mut self) -> &mut Self where A: Route<State=S>
|
||||
pub fn post<A>(&mut self) -> &mut Self
|
||||
where A: Actor<Context=HttpContext<A>> + Route<State=S>
|
||||
{
|
||||
self.handler(Method::POST, A::factory())
|
||||
}
|
||||
|
||||
/// Handler for `PUR` method.
|
||||
pub fn put<A>(&mut self) -> &mut Self where A: Route<State=S>
|
||||
pub fn put<A>(&mut self) -> &mut Self
|
||||
where A: Actor<Context=HttpContext<A>> + Route<State=S>
|
||||
{
|
||||
self.handler(Method::PUT, A::factory())
|
||||
}
|
||||
|
||||
/// Handler for `METHOD` method.
|
||||
pub fn delete<A>(&mut self) -> &mut Self where A: Route<State=S>
|
||||
pub fn delete<A>(&mut self) -> &mut Self
|
||||
where A: Actor<Context=HttpContext<A>> + Route<State=S>
|
||||
{
|
||||
self.handler(Method::DELETE, A::factory())
|
||||
}
|
||||
@ -104,15 +108,15 @@ impl<S: 'static> RouteHandler<S> for Resource<S> {
|
||||
|
||||
|
||||
#[cfg_attr(feature="cargo-clippy", allow(large_enum_variant))]
|
||||
enum ReplyItem<A> where A: Actor<Context=HttpContext<A>> + Route {
|
||||
enum ReplyItem<A> where A: Actor + Route {
|
||||
Message(HttpResponse),
|
||||
Actor(A),
|
||||
}
|
||||
|
||||
/// Represents response process.
|
||||
pub struct Reply<A: Actor<Context=HttpContext<A>> + Route> (ReplyItem<A>);
|
||||
pub struct Reply<A: Actor + Route> (ReplyItem<A>);
|
||||
|
||||
impl<A> Reply<A> where A: Actor<Context=HttpContext<A>> + Route
|
||||
impl<A> Reply<A> where A: Actor + Route
|
||||
{
|
||||
/// Create async response
|
||||
pub fn stream(act: A) -> Self {
|
||||
@ -129,7 +133,8 @@ impl<A> Reply<A> where A: Actor<Context=HttpContext<A>> + Route
|
||||
Reply(ReplyItem::Message(msg.response(req)))
|
||||
}
|
||||
|
||||
pub(crate) fn into(self, mut ctx: HttpContext<A>) -> Task {
|
||||
pub fn into(self, mut ctx: HttpContext<A>) -> Task where A: Actor<Context=HttpContext<A>>
|
||||
{
|
||||
match self.0 {
|
||||
ReplyItem::Message(msg) => {
|
||||
Task::reply(msg)
|
||||
|
10
src/route.rs
10
src/route.rs
@ -20,11 +20,15 @@ pub enum Frame {
|
||||
|
||||
/// Trait defines object that could be regestered as resource route
|
||||
pub trait RouteHandler<S>: 'static {
|
||||
/// Handle request
|
||||
fn handle(&self, req: HttpRequest, payload: Payload, state: Rc<S>) -> Task;
|
||||
|
||||
/// Set route prefix
|
||||
fn set_prefix(&mut self, _prefix: String) {}
|
||||
}
|
||||
|
||||
/// Actors with ability to handle http requests
|
||||
pub trait Route: Actor<Context=HttpContext<Self>> {
|
||||
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;
|
||||
@ -33,7 +37,7 @@ pub trait Route: Actor<Context=HttpContext<Self>> {
|
||||
/// result immediately with `Reply::reply` or `Reply::with`.
|
||||
/// Actor itself could be returned for handling streaming request/response.
|
||||
/// In that case `HttpContext::start` and `HttpContext::write` has to be used.
|
||||
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self>;
|
||||
fn request(req: HttpRequest, payload: Payload, ctx: &mut Self::Context) -> Reply<Self>;
|
||||
|
||||
/// This method creates `RouteFactory` for this actor.
|
||||
fn factory() -> RouteFactory<Self, Self::State> {
|
||||
@ -45,7 +49,7 @@ pub trait Route: Actor<Context=HttpContext<Self>> {
|
||||
pub struct RouteFactory<A: Route<State=S>, S>(PhantomData<A>);
|
||||
|
||||
impl<A, S> RouteHandler<S> for RouteFactory<A, S>
|
||||
where A: Route<State=S>,
|
||||
where A: Actor<Context=HttpContext<A>> + Route<State=S>,
|
||||
S: 'static
|
||||
{
|
||||
fn handle(&self, req: HttpRequest, payload: Payload, state: Rc<A::State>) -> Task
|
||||
|
47
src/task.rs
47
src/task.rs
@ -4,14 +4,12 @@ use std::fmt::Write;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use http::{StatusCode, Version};
|
||||
use http::header::{HeaderValue,
|
||||
CONNECTION, CONTENT_TYPE, CONTENT_LENGTH, TRANSFER_ENCODING, DATE};
|
||||
use bytes::BytesMut;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use tokio_core::net::TcpStream;
|
||||
|
||||
use unicase::Ascii;
|
||||
use hyper::header::{Date, Connection, ConnectionOption,
|
||||
ContentType, ContentLength, Encoding, TransferEncoding};
|
||||
|
||||
use date;
|
||||
use route::Frame;
|
||||
use httpmessage::{Body, HttpResponse};
|
||||
@ -100,22 +98,26 @@ impl Task {
|
||||
if msg.chunked() {
|
||||
error!("Chunked transfer is enabled but body is set to Empty");
|
||||
}
|
||||
msg.headers.set(ContentLength(0));
|
||||
msg.headers.remove::<TransferEncoding>();
|
||||
msg.headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
||||
msg.headers.remove(TRANSFER_ENCODING);
|
||||
self.encoder = Encoder::length(0);
|
||||
},
|
||||
Body::Length(n) => {
|
||||
if msg.chunked() {
|
||||
error!("Chunked transfer is enabled but body with specific length is specified");
|
||||
}
|
||||
msg.headers.set(ContentLength(n));
|
||||
msg.headers.remove::<TransferEncoding>();
|
||||
msg.headers.insert(
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::from_str(format!("{}", n).as_str()).unwrap());
|
||||
msg.headers.remove(TRANSFER_ENCODING);
|
||||
self.encoder = Encoder::length(n);
|
||||
},
|
||||
Body::Binary(ref bytes) => {
|
||||
extra = bytes.len();
|
||||
msg.headers.set(ContentLength(bytes.len() as u64));
|
||||
msg.headers.remove::<TransferEncoding>();
|
||||
msg.headers.insert(
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::from_str(format!("{}", bytes.len()).as_str()).unwrap());
|
||||
msg.headers.remove(TRANSFER_ENCODING);
|
||||
self.encoder = Encoder::length(0);
|
||||
}
|
||||
Body::Streaming => {
|
||||
@ -123,16 +125,15 @@ impl Task {
|
||||
if msg.version < Version::HTTP_11 {
|
||||
error!("Chunked transfer encoding is forbidden for {:?}", msg.version);
|
||||
}
|
||||
msg.headers.remove::<ContentLength>();
|
||||
msg.headers.set(TransferEncoding(vec![Encoding::Chunked]));
|
||||
msg.headers.remove(CONTENT_LENGTH);
|
||||
msg.headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
||||
self.encoder = Encoder::chunked();
|
||||
} else {
|
||||
self.encoder = Encoder::eof();
|
||||
}
|
||||
}
|
||||
Body::Upgrade => {
|
||||
msg.headers.set(Connection(vec![
|
||||
ConnectionOption::ConnectionHeader(Ascii::new("upgrade".to_owned()))]));
|
||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
||||
self.encoder = Encoder::eof();
|
||||
}
|
||||
}
|
||||
@ -140,10 +141,10 @@ impl Task {
|
||||
// keep-alive
|
||||
if msg.keep_alive() {
|
||||
if msg.version < Version::HTTP_11 {
|
||||
msg.headers.set(Connection::keep_alive());
|
||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
|
||||
}
|
||||
} else if msg.version >= Version::HTTP_11 {
|
||||
msg.headers.set(Connection::close());
|
||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("close"));
|
||||
}
|
||||
|
||||
// render message
|
||||
@ -152,14 +153,20 @@ impl Task {
|
||||
|
||||
if msg.version == Version::HTTP_11 && msg.status == StatusCode::OK {
|
||||
self.buffer.extend(b"HTTP/1.1 200 OK\r\n");
|
||||
let _ = write!(self.buffer, "{}", msg.headers);
|
||||
} else {
|
||||
let _ = write!(self.buffer, "{:?} {}\r\n{}", msg.version, msg.status, msg.headers);
|
||||
let _ = write!(self.buffer, "{:?} {}\r\n", msg.version, msg.status);
|
||||
}
|
||||
for (key, value) in &msg.headers {
|
||||
let t: &[u8] = key.as_ref();
|
||||
self.buffer.extend(t);
|
||||
self.buffer.extend(b": ");
|
||||
self.buffer.extend(value.as_ref());
|
||||
self.buffer.extend(b"\r\n");
|
||||
}
|
||||
|
||||
// using http::h1::date is quite a lot faster than generating
|
||||
// a unique Date header each time like req/s goes up about 10%
|
||||
if !msg.headers.has::<Date>() {
|
||||
if !msg.headers.contains_key(DATE) {
|
||||
self.buffer.reserve(date::DATE_VALUE_LENGTH + 8);
|
||||
self.buffer.extend(b"Date: ");
|
||||
date::extend(&mut self.buffer);
|
||||
@ -167,7 +174,7 @@ impl Task {
|
||||
}
|
||||
|
||||
// default content-type
|
||||
if !msg.headers.has::<ContentType>() {
|
||||
if !msg.headers.contains_key(CONTENT_TYPE) {
|
||||
self.buffer.extend(b"ContentType: application/octet-stream\r\n".as_ref());
|
||||
}
|
||||
|
||||
|
68
src/ws.rs
68
src/ws.rs
@ -64,10 +64,10 @@
|
||||
//! fn main() {}
|
||||
//! ```
|
||||
use std::vec::Vec;
|
||||
use http::{Method, StatusCode};
|
||||
use std::str::FromStr;
|
||||
use http::{Method, StatusCode, header};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use hyper::header;
|
||||
|
||||
use actix::Actor;
|
||||
|
||||
@ -81,22 +81,13 @@ use wsframe;
|
||||
use wsproto::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
header! {
|
||||
/// SEC-WEBSOCKET-ACCEPT header
|
||||
(WebSocketAccept, "SEC-WEBSOCKET-ACCEPT") => [String]
|
||||
}
|
||||
header! {
|
||||
/// SEC-WEBSOCKET-KEY header
|
||||
(WebSocketKey, "SEC-WEBSOCKET-KEY") => [String]
|
||||
}
|
||||
header! {
|
||||
/// SEC-WEBSOCKET-VERSION header
|
||||
(WebSocketVersion, "SEC-WEBSOCKET-VERSION") => [String]
|
||||
}
|
||||
header! {
|
||||
/// SEC-WEBSOCKET-PROTOCOL header
|
||||
(WebSocketProtocol, "SEC-WEBSOCKET-PROTOCOL") => [String]
|
||||
}
|
||||
const SEC_WEBSOCKET_ACCEPT: &'static str = "SEC-WEBSOCKET-ACCEPT";
|
||||
#[doc(hidden)]
|
||||
const SEC_WEBSOCKET_KEY: &'static str = "SEC-WEBSOCKET-KEY";
|
||||
#[doc(hidden)]
|
||||
const SEC_WEBSOCKET_VERSION: &'static str = "SEC-WEBSOCKET-VERSION";
|
||||
// #[doc(hidden)]
|
||||
// const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL";
|
||||
|
||||
|
||||
/// `WebSocket` Message
|
||||
@ -126,8 +117,12 @@ pub fn handshake(req: HttpRequest) -> Result<HttpResponse, HttpResponse> {
|
||||
}
|
||||
|
||||
// Check for "UPGRADE" to websocket header
|
||||
let has_hdr = if let Some::<&header::Upgrade>(hdr) = req.headers().get() {
|
||||
hdr.0.contains(&header::Protocol::new(header::ProtocolName::WebSocket, None))
|
||||
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
|
||||
if let Ok(s) = hdr.to_str() {
|
||||
s.to_lowercase().contains("websocket")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@ -141,14 +136,14 @@ pub fn handshake(req: HttpRequest) -> Result<HttpResponse, HttpResponse> {
|
||||
}
|
||||
|
||||
// check supported version
|
||||
if !req.headers().has::<WebSocketVersion>() {
|
||||
if !req.headers().contains_key(SEC_WEBSOCKET_VERSION) {
|
||||
return Err(HTTPBadRequest.with_reason(req, "No websocket version header is required"))
|
||||
}
|
||||
let supported_ver = {
|
||||
let hdr = req.headers().get::<WebSocketVersion>().unwrap();
|
||||
match hdr.0.as_str() {
|
||||
"13" | "8" | "7" => true,
|
||||
_ => false,
|
||||
if let Some(hdr) = req.headers().get(SEC_WEBSOCKET_VERSION) {
|
||||
hdr == "13" || hdr == "8" || hdr == "7"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if !supported_ver {
|
||||
@ -156,25 +151,20 @@ pub fn handshake(req: HttpRequest) -> Result<HttpResponse, HttpResponse> {
|
||||
}
|
||||
|
||||
// check client handshake for validity
|
||||
let key = if let Some::<&WebSocketKey>(hdr) = req.headers().get() {
|
||||
Some(hash_key(hdr.0.as_bytes()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let key = if let Some(key) = key {
|
||||
key
|
||||
} else {
|
||||
if !req.headers().contains_key(SEC_WEBSOCKET_KEY) {
|
||||
return Err(HTTPBadRequest.with_reason(req, "Handshake error"));
|
||||
}
|
||||
let key = {
|
||||
let key = req.headers().get(SEC_WEBSOCKET_KEY).unwrap();
|
||||
hash_key(key.as_ref())
|
||||
};
|
||||
|
||||
Ok(HttpResponse::new(req, StatusCode::SWITCHING_PROTOCOLS, Body::Empty)
|
||||
.set_connection_type(ConnectionType::Upgrade)
|
||||
.set_header(
|
||||
header::Upgrade(vec![header::Protocol::new(header::ProtocolName::WebSocket, None)]))
|
||||
.set_header(
|
||||
header::TransferEncoding(vec![header::Encoding::Chunked]))
|
||||
.set_header(
|
||||
WebSocketAccept(key))
|
||||
.set_header(header::UPGRADE, header::HeaderValue::from_static("websocket"))
|
||||
.set_header(header::TRANSFER_ENCODING, header::HeaderValue::from_static("chunked"))
|
||||
.set_header(header::HeaderName::from_str(SEC_WEBSOCKET_ACCEPT).unwrap(),
|
||||
header::HeaderValue::from_str(key.as_str()).unwrap())
|
||||
.set_body(Body::Upgrade)
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user