mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-23 15:51:06 +01:00
websocket support
This commit is contained in:
parent
4234f133d2
commit
f2d20514fa
@ -24,8 +24,12 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
time = "0.1"
|
||||
http = "0.1"
|
||||
httparse = "*"
|
||||
httparse = "0.1"
|
||||
hyper = "0.11"
|
||||
unicase = "2.0"
|
||||
slab = "0.4"
|
||||
sha1 = "0.2"
|
||||
rand = "0.3"
|
||||
route-recognizer = "0.1"
|
||||
|
||||
# tokio
|
||||
|
@ -14,6 +14,7 @@ Actix Http is licensed under the [Apache-2.0 license](http://opensource.org/lice
|
||||
|
||||
* HTTP 1.1 and 1.0 support
|
||||
* Streaming and pipelining support
|
||||
* WebSockets support
|
||||
* Configurable request routing
|
||||
|
||||
## Usage
|
||||
@ -47,8 +48,7 @@ impl Actor for MyRoute {
|
||||
impl Route for MyRoute {
|
||||
type State = ();
|
||||
|
||||
fn request(req: HttpRequest,
|
||||
payload: Option<Payload>,
|
||||
fn request(req: HttpRequest, payload: Option<Payload>,
|
||||
ctx: &mut HttpContext<Self>) -> HttpMessage<Self>
|
||||
{
|
||||
Self::http_reply(req, httpcodes::HTTPOk)
|
||||
@ -60,7 +60,8 @@ fn main() {
|
||||
|
||||
// create routing map with `MyRoute` route
|
||||
let mut routes = RoutingMap::default();
|
||||
routes.add_resource("/")
|
||||
routes
|
||||
.add_resource("/")
|
||||
.post::<MyRoute>();
|
||||
|
||||
// start http server
|
||||
|
@ -7,8 +7,6 @@ use task::Task;
|
||||
use route::{Payload, RouteHandler};
|
||||
use httpmessage::{Body, HttpRequest, HttpResponse, IntoHttpResponse};
|
||||
|
||||
pub struct StaticResponse(StatusCode);
|
||||
|
||||
pub const HTTPOk: StaticResponse = StaticResponse(StatusCode::OK);
|
||||
pub const HTTPCreated: StaticResponse = StaticResponse(StatusCode::CREATED);
|
||||
pub const HTTPNoContent: StaticResponse = StaticResponse(StatusCode::NO_CONTENT);
|
||||
@ -17,6 +15,15 @@ pub const HTTPNotFound: StaticResponse = StaticResponse(StatusCode::NOT_FOUND);
|
||||
pub const HTTPMethodNotAllowed: StaticResponse = StaticResponse(StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
|
||||
pub struct StaticResponse(StatusCode);
|
||||
|
||||
impl StaticResponse {
|
||||
pub fn with_reason(self, req: HttpRequest, reason: &'static str) -> HttpResponse {
|
||||
HttpResponse::new(req, self.0, Body::Empty)
|
||||
.set_reason(reason)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> RouteHandler<S> for StaticResponse {
|
||||
fn handle(&self, req: HttpRequest, _: Option<Payload>, _: Rc<S>) -> Task
|
||||
{
|
||||
@ -25,7 +32,7 @@ impl<S> RouteHandler<S> for StaticResponse {
|
||||
}
|
||||
|
||||
impl IntoHttpResponse for StaticResponse {
|
||||
fn into_response(self, req: HttpRequest) -> HttpResponse {
|
||||
fn response(self, req: HttpRequest) -> HttpResponse {
|
||||
HttpResponse::new(req, self.0, Body::Empty)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,13 @@ use hyper::header::{Connection, ConnectionOption,
|
||||
use Params;
|
||||
use error::Error;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum ConnectionType {
|
||||
Close,
|
||||
KeepAlive,
|
||||
Upgrade,
|
||||
}
|
||||
|
||||
pub trait Message {
|
||||
|
||||
fn version(&self) -> Version;
|
||||
@ -61,14 +68,6 @@ pub trait Message {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_upgrade(&self) -> bool {
|
||||
if let Some(&Connection(ref conn)) = self.headers().get() {
|
||||
conn.contains(&ConnectionOption::from_str("upgrade").unwrap())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -159,6 +158,14 @@ impl HttpRequest {
|
||||
params: params
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_upgrade(&self) -> bool {
|
||||
if let Some(&Connection(ref conn)) = self.headers().get() {
|
||||
conn.contains(&ConnectionOption::from_str("upgrade").unwrap())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents various types of http message body.
|
||||
@ -173,6 +180,8 @@ pub enum Body {
|
||||
/// Unspecified streaming response. Developer is responsible for setting
|
||||
/// right `Content-Length` or `Transfer-Encoding` headers.
|
||||
Streaming,
|
||||
/// Upgrade connection.
|
||||
Upgrade,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
@ -188,7 +197,7 @@ impl Body {
|
||||
/// Implements by something that can be converted to `HttpMessage`
|
||||
pub trait IntoHttpResponse {
|
||||
/// Convert into response.
|
||||
fn into_response(self, req: HttpRequest) -> HttpResponse;
|
||||
fn response(self, req: HttpRequest) -> HttpResponse;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -198,10 +207,11 @@ pub struct HttpResponse {
|
||||
pub version: Version,
|
||||
pub headers: Headers,
|
||||
pub status: StatusCode,
|
||||
reason: Option<&'static str>,
|
||||
body: Body,
|
||||
chunked: bool,
|
||||
keep_alive: Option<bool>,
|
||||
compression: Option<Encoding>,
|
||||
connection_type: Option<ConnectionType>,
|
||||
}
|
||||
|
||||
impl Message for HttpResponse {
|
||||
@ -223,13 +233,20 @@ impl HttpResponse {
|
||||
version: version,
|
||||
headers: Default::default(),
|
||||
status: status,
|
||||
reason: None,
|
||||
body: body,
|
||||
chunked: false,
|
||||
keep_alive: None,
|
||||
compression: None,
|
||||
connection_type: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Original prequest
|
||||
#[inline]
|
||||
pub fn request(&self) -> &HttpRequest {
|
||||
&self.request
|
||||
}
|
||||
|
||||
/// Get the HTTP version of this response.
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
@ -256,37 +273,55 @@ impl HttpResponse {
|
||||
|
||||
/// Set the `StatusCode` for this response.
|
||||
#[inline]
|
||||
pub fn set_status(&mut self, status: StatusCode) -> &mut Self {
|
||||
pub fn set_status(mut self, status: StatusCode) -> Self {
|
||||
self.status = status;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header and move the Response.
|
||||
#[inline]
|
||||
pub fn set_header<H: Header>(&mut self, header: H) -> &mut Self {
|
||||
pub fn set_header<H: Header>(mut self, header: H) -> Self {
|
||||
self.headers.set(header);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the headers and move the Response.
|
||||
/// Set the headers.
|
||||
#[inline]
|
||||
pub fn with_headers(&mut self, headers: Headers) -> &mut Self {
|
||||
pub fn with_headers(mut self, headers: Headers) -> Self {
|
||||
self.headers = headers;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the custom reason for the response.
|
||||
#[inline]
|
||||
pub fn set_reason(mut self, reason: &'static str) -> Self {
|
||||
self.reason = Some(reason);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set connection type
|
||||
pub fn set_connection_type(mut self, conn: ConnectionType) -> Self {
|
||||
self.connection_type = Some(conn);
|
||||
self
|
||||
}
|
||||
|
||||
/// Connection upgrade status
|
||||
pub fn upgrade(&self) -> bool {
|
||||
self.connection_type == Some(ConnectionType::Upgrade)
|
||||
}
|
||||
|
||||
/// Keep-alive status for this connection
|
||||
pub fn keep_alive(&self) -> bool {
|
||||
if let Some(ka) = self.keep_alive {
|
||||
ka
|
||||
if let Some(ConnectionType::KeepAlive) = self.connection_type {
|
||||
true
|
||||
} else {
|
||||
self.request.should_keep_alive()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Force close connection, even if it is marked as keep-alive
|
||||
pub fn force_close(&mut self) {
|
||||
self.keep_alive = Some(false);
|
||||
self.connection_type = Some(ConnectionType::Close);
|
||||
}
|
||||
|
||||
/// is chunked encoding enabled
|
||||
@ -310,8 +345,14 @@ impl HttpResponse {
|
||||
&self.body
|
||||
}
|
||||
|
||||
/// Set a body
|
||||
pub fn set_body<B: Into<Body>>(mut self, body: B) -> Self {
|
||||
self.body = body.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a body and return previous body value
|
||||
pub fn set_body<B: Into<Body>>(&mut self, body: B) -> Body {
|
||||
pub fn replace_body<B: Into<Body>>(&mut self, body: B) -> Body {
|
||||
mem::replace(&mut self.body, body.into())
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,16 @@
|
||||
extern crate log;
|
||||
extern crate time;
|
||||
extern crate bytes;
|
||||
extern crate rand;
|
||||
extern crate sha1;
|
||||
#[macro_use]
|
||||
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;
|
||||
extern crate route_recognizer;
|
||||
@ -28,6 +32,10 @@ mod task;
|
||||
mod reader;
|
||||
mod server;
|
||||
|
||||
pub mod ws;
|
||||
mod wsframe;
|
||||
mod wsproto;
|
||||
|
||||
pub mod httpcodes;
|
||||
pub use application::HttpApplication;
|
||||
pub use route::{Route, RouteFactory, RouteHandler, Payload, PayloadItem};
|
||||
|
62
src/main.rs
62
src/main.rs
@ -1,4 +1,4 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(dead_code, unused_variables)]
|
||||
extern crate actix;
|
||||
extern crate actix_http;
|
||||
extern crate tokio_core;
|
||||
@ -25,9 +25,9 @@ impl Route for MyRoute {
|
||||
{
|
||||
if let Some(pl) = payload {
|
||||
ctx.add_stream(pl);
|
||||
Self::http_stream(MyRoute{req: Some(req)})
|
||||
HttpMessage::stream(MyRoute{req: Some(req)})
|
||||
} else {
|
||||
Self::http_reply(req, httpcodes::HTTPOk)
|
||||
HttpMessage::reply_with(req, httpcodes::HTTPOk)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,7 +45,7 @@ impl Handler<PayloadItem> for MyRoute {
|
||||
{
|
||||
println!("CHUNK: {:?}", msg);
|
||||
if let Some(req) = self.req.take() {
|
||||
ctx.start(httpcodes::HTTPOk.into_response(req));
|
||||
ctx.start(httpcodes::HTTPOk.response(req));
|
||||
ctx.write_eof();
|
||||
}
|
||||
|
||||
@ -53,6 +53,57 @@ impl Handler<PayloadItem> for MyRoute {
|
||||
}
|
||||
}
|
||||
|
||||
struct MyWS {}
|
||||
|
||||
impl Actor for MyWS {
|
||||
type Context = HttpContext<Self>;
|
||||
}
|
||||
|
||||
impl Route for MyWS {
|
||||
type State = ();
|
||||
|
||||
fn request(req: HttpRequest,
|
||||
payload: Option<Payload>,
|
||||
ctx: &mut HttpContext<Self>) -> HttpMessage<Self>
|
||||
{
|
||||
if let Some(payload) = payload {
|
||||
match ws::do_handshake(req) {
|
||||
Ok(resp) => {
|
||||
ctx.start(resp);
|
||||
ctx.add_stream(ws::WsStream::new(payload));
|
||||
HttpMessage::stream(MyWS{})
|
||||
},
|
||||
Err(err) =>
|
||||
HttpMessage::reply(err)
|
||||
}
|
||||
} else {
|
||||
HttpMessage::reply_with(req, httpcodes::HTTPBadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseType<ws::Message> for MyWS {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
impl StreamHandler<ws::Message> for MyWS {}
|
||||
|
||||
impl Handler<ws::Message> for MyWS {
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
||||
-> Response<Self, ws::Message>
|
||||
{
|
||||
println!("WS: {:?}", msg);
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, msg),
|
||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, text),
|
||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
||||
_ => (),
|
||||
}
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::init();
|
||||
@ -71,6 +122,9 @@ fn main() {
|
||||
routes.add_resource("/test")
|
||||
.post::<MyRoute>();
|
||||
|
||||
routes.add_resource("/ws/")
|
||||
.get::<MyWS>();
|
||||
|
||||
let http = HttpServer::new(routes);
|
||||
http.serve::<()>(
|
||||
&net::SocketAddr::from_str("127.0.0.1:9080").unwrap()).unwrap();
|
||||
|
@ -120,6 +120,7 @@ impl Reader {
|
||||
match self.decode()? {
|
||||
Decoding::Paused => return Ok(Async::NotReady),
|
||||
Decoding::Ready => {
|
||||
println!("decode ready");
|
||||
self.payload = None;
|
||||
break
|
||||
},
|
||||
@ -149,6 +150,7 @@ impl Reader {
|
||||
Decoding::Paused =>
|
||||
break,
|
||||
Decoding::Ready => {
|
||||
println!("decoded 3");
|
||||
self.payload = None;
|
||||
break
|
||||
},
|
||||
@ -280,12 +282,14 @@ pub fn parse(buf: &mut BytesMut) -> Result<Option<(HttpRequest, Option<Decoder>)
|
||||
});
|
||||
|
||||
let msg = HttpRequest::new(method, uri, version, headers);
|
||||
|
||||
let _upgrade = msg.is_upgrade();
|
||||
let upgrade = msg.is_upgrade() || *msg.method() == Method::CONNECT;
|
||||
let chunked = msg.is_chunked()?;
|
||||
|
||||
if upgrade {
|
||||
Ok(Some((msg, Some(Decoder::eof()))))
|
||||
}
|
||||
// Content-Length
|
||||
if let Some(&ContentLength(len)) = msg.headers().get() {
|
||||
else if let Some(&ContentLength(len)) = msg.headers().get() {
|
||||
if chunked {
|
||||
return Err(Error::Header)
|
||||
}
|
||||
|
@ -101,8 +101,13 @@ impl<A> HttpMessage<A> where A: Actor<Context=HttpContext<A>> + Route
|
||||
}
|
||||
|
||||
/// Send response
|
||||
pub fn reply<I: IntoHttpResponse>(req: HttpRequest, msg: I) -> Self {
|
||||
HttpMessage(HttpMessageItem::Message(msg.into_response(req)))
|
||||
pub fn reply(msg: HttpResponse) -> Self {
|
||||
HttpMessage(HttpMessageItem::Message(msg))
|
||||
}
|
||||
|
||||
/// Send response
|
||||
pub fn reply_with<I: IntoHttpResponse>(req: HttpRequest, msg: I) -> Self {
|
||||
HttpMessage(HttpMessageItem::Message(msg.response(req)))
|
||||
}
|
||||
|
||||
pub(crate) fn into(self, mut ctx: HttpContext<A>) -> Task {
|
||||
|
12
src/route.rs
12
src/route.rs
@ -8,7 +8,7 @@ use futures::unsync::mpsc::Receiver;
|
||||
use task::Task;
|
||||
use context::HttpContext;
|
||||
use resource::HttpMessage;
|
||||
use httpmessage::{HttpRequest, HttpResponse, IntoHttpResponse};
|
||||
use httpmessage::{HttpRequest, HttpResponse};
|
||||
|
||||
/// Stream of `PayloadItem`'s
|
||||
pub type Payload = Receiver<PayloadItem>;
|
||||
@ -60,16 +60,6 @@ pub trait Route: Actor<Context=HttpContext<Self>> {
|
||||
fn factory() -> RouteFactory<Self, Self::State> {
|
||||
RouteFactory(PhantomData)
|
||||
}
|
||||
|
||||
/// Create async response
|
||||
fn http_stream(act: Self) -> HttpMessage<Self> {
|
||||
HttpMessage::stream(act)
|
||||
}
|
||||
|
||||
/// Send response
|
||||
fn http_reply<I: IntoHttpResponse>(req: HttpRequest, msg: I) -> HttpMessage<Self> {
|
||||
HttpMessage::reply(req, msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,7 +91,7 @@ impl Router {
|
||||
}
|
||||
}
|
||||
|
||||
Task::reply(IntoHttpResponse::into_response(HTTPNotFound, req))
|
||||
Task::reply(IntoHttpResponse::response(HTTPNotFound, req))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
src/task.rs
42
src/task.rs
@ -8,8 +8,9 @@ use bytes::BytesMut;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use tokio_core::net::TcpStream;
|
||||
|
||||
use hyper::header::{Date, Connection, ContentType,
|
||||
ContentLength, Encoding, TransferEncoding};
|
||||
use unicase::Ascii;
|
||||
use hyper::header::{Date, Connection, ConnectionOption,
|
||||
ContentType, ContentLength, Encoding, TransferEncoding};
|
||||
|
||||
use date;
|
||||
use route::Frame;
|
||||
@ -53,6 +54,7 @@ pub struct Task {
|
||||
stream: Option<Box<FrameStream>>,
|
||||
encoder: Encoder,
|
||||
buffer: BytesMut,
|
||||
upgraded: bool,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
@ -69,6 +71,7 @@ impl Task {
|
||||
stream: None,
|
||||
encoder: Encoder::length(0),
|
||||
buffer: BytesMut::new(),
|
||||
upgraded: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +85,7 @@ impl Task {
|
||||
stream: Some(Box::new(stream)),
|
||||
encoder: Encoder::length(0),
|
||||
buffer: BytesMut::new(),
|
||||
upgraded: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +94,8 @@ impl Task {
|
||||
trace!("Prepare message status={:?}", msg.status);
|
||||
|
||||
let mut extra = 0;
|
||||
let body = msg.set_body(Body::Empty);
|
||||
let body = msg.replace_body(Body::Empty);
|
||||
|
||||
match body {
|
||||
Body::Empty => {
|
||||
if msg.chunked() {
|
||||
@ -126,21 +131,24 @@ impl Task {
|
||||
self.encoder = Encoder::eof();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keep-alive
|
||||
if !msg.headers.has::<Connection>() {
|
||||
if msg.keep_alive() {
|
||||
if msg.version < Version::HTTP_11 {
|
||||
msg.headers.set(Connection::keep_alive());
|
||||
}
|
||||
} else if msg.version >= Version::HTTP_11 {
|
||||
msg.headers.set(Connection::close());
|
||||
Body::Upgrade => {
|
||||
msg.headers.set(Connection(vec![
|
||||
ConnectionOption::ConnectionHeader(Ascii::new("upgrade".to_owned()))]));
|
||||
self.encoder = Encoder::eof();
|
||||
}
|
||||
}
|
||||
|
||||
// keep-alive
|
||||
if msg.keep_alive() {
|
||||
if msg.version < Version::HTTP_11 {
|
||||
msg.headers.set(Connection::keep_alive());
|
||||
}
|
||||
} else if msg.version >= Version::HTTP_11 {
|
||||
msg.headers.set(Connection::close());
|
||||
}
|
||||
|
||||
// render message
|
||||
let init_cap = 30 + msg.headers.len() * AVERAGE_HEADER_SIZE + extra;
|
||||
let init_cap = 100 + msg.headers.len() * AVERAGE_HEADER_SIZE + extra;
|
||||
self.buffer.reserve(init_cap);
|
||||
|
||||
if msg.version == Version::HTTP_11 && msg.status == StatusCode::OK {
|
||||
@ -149,6 +157,7 @@ impl Task {
|
||||
} else {
|
||||
let _ = write!(self.buffer, "{:?} {}\r\n{}", msg.version, msg.status, msg.headers);
|
||||
}
|
||||
|
||||
// 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>() {
|
||||
@ -169,7 +178,7 @@ impl Task {
|
||||
self.buffer.extend(bytes);
|
||||
return
|
||||
}
|
||||
msg.set_body(body);
|
||||
msg.replace_body(body);
|
||||
}
|
||||
|
||||
pub(crate) fn poll_io(&mut self, io: &mut TcpStream) -> Poll<bool, ()> {
|
||||
@ -261,7 +270,8 @@ impl Future for Task {
|
||||
error!("Non expected frame {:?}", frame);
|
||||
return Err(())
|
||||
}
|
||||
if msg.body().has_body() {
|
||||
self.upgraded = msg.upgrade();
|
||||
if self.upgraded || msg.body().has_body() {
|
||||
self.iostate = TaskIOState::ReadingPayload;
|
||||
} else {
|
||||
self.iostate = TaskIOState::Done;
|
||||
|
248
src/ws.rs
Normal file
248
src/ws.rs
Normal file
@ -0,0 +1,248 @@
|
||||
//! `WebSocket` context implementation
|
||||
|
||||
#![allow(dead_code, unused_variables, unused_imports)]
|
||||
|
||||
use std::io;
|
||||
use std::vec::Vec;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use http::{Method, StatusCode};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use hyper::header;
|
||||
|
||||
use actix::Actor;
|
||||
|
||||
use context::HttpContext;
|
||||
use route::{Route, Payload, PayloadItem};
|
||||
use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed};
|
||||
use httpmessage::{Body, ConnectionType, HttpRequest, HttpResponse, IntoHttpResponse};
|
||||
|
||||
use wsframe;
|
||||
pub use wsproto::*;
|
||||
|
||||
header! { (WebSocketAccept, "SEC-WEBSOCKET-ACCEPT") => [String] }
|
||||
header! { (WebSocketKey, "SEC-WEBSOCKET-KEY") => [String] }
|
||||
header! { (WebSocketVersion, "SEC-WEBSOCKET-VERSION") => [String] }
|
||||
header! { (WebSocketProtocol, "SEC-WEBSOCKET-PROTOCOL") => [String] }
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
Ping(String),
|
||||
Pong(String),
|
||||
Close,
|
||||
Closed,
|
||||
Error
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SendMessage {
|
||||
Text(String),
|
||||
Binary(Vec<u8>),
|
||||
Close(CloseCode),
|
||||
Ping,
|
||||
Pong,
|
||||
}
|
||||
|
||||
/// Prepare `WebSocket` handshake.
|
||||
///
|
||||
/// It return HTTP response code, response headers, websocket parser,
|
||||
/// websocket writer. It does not perform any IO.
|
||||
///
|
||||
/// `protocols` is a sequence of known protocols. On successful handshake,
|
||||
/// the returned response headers contain the first protocol in this list
|
||||
/// which the server also knows.
|
||||
pub fn do_handshake(req: HttpRequest) -> Result<HttpResponse, HttpResponse> {
|
||||
// WebSocket accepts only GET
|
||||
if *req.method() != Method::GET {
|
||||
return Err(HTTPMethodNotAllowed.response(req))
|
||||
}
|
||||
|
||||
// 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))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !has_hdr {
|
||||
return Err(HTTPMethodNotAllowed.with_reason(req, "No WebSocket UPGRADE header found"))
|
||||
}
|
||||
|
||||
// Upgrade connection
|
||||
if !req.is_upgrade() {
|
||||
return Err(HTTPBadRequest.with_reason(req, "No CONNECTION upgrade"))
|
||||
}
|
||||
|
||||
// check supported version
|
||||
if !req.headers().has::<WebSocketVersion>() {
|
||||
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 !supported_ver {
|
||||
return Err(HTTPBadRequest.with_reason(req, "Unsupported version"))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return Err(HTTPBadRequest.with_reason(req, "Handshake error"));
|
||||
};
|
||||
|
||||
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_body(Body::Upgrade)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/// Struct represent stream of `ws::Message` items
|
||||
pub struct WsStream {
|
||||
rx: Payload,
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
impl WsStream {
|
||||
pub fn new(rx: Payload) -> WsStream {
|
||||
WsStream { rx: rx, buf: BytesMut::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WsStream {
|
||||
type Item = Message;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let mut done = false;
|
||||
|
||||
loop {
|
||||
match self.rx.poll() {
|
||||
Ok(Async::Ready(Some(item))) => {
|
||||
match item {
|
||||
PayloadItem::Eof =>
|
||||
return Ok(Async::Ready(None)),
|
||||
PayloadItem::Chunk(chunk) => {
|
||||
self.buf.extend(chunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(None)) => done = true,
|
||||
Ok(Async::NotReady) => {},
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
match wsframe::Frame::parse(&mut self.buf) {
|
||||
Ok(Some(frame)) => {
|
||||
trace!("Frame {}", frame);
|
||||
let (finished, opcode, payload) = frame.unpack();
|
||||
|
||||
match opcode {
|
||||
OpCode::Continue => continue,
|
||||
OpCode::Bad =>
|
||||
return Ok(Async::Ready(Some(Message::Error))),
|
||||
OpCode::Close =>
|
||||
return Ok(Async::Ready(Some(Message::Closed))),
|
||||
OpCode::Ping =>
|
||||
return Ok(Async::Ready(Some(
|
||||
Message::Ping(String::from_utf8_lossy(&payload).into())))),
|
||||
OpCode::Pong =>
|
||||
return Ok(Async::Ready(Some(
|
||||
Message::Pong(String::from_utf8_lossy(&payload).into())))),
|
||||
OpCode::Binary =>
|
||||
return Ok(Async::Ready(Some(Message::Binary(payload)))),
|
||||
OpCode::Text => {
|
||||
match String::from_utf8(payload) {
|
||||
Ok(s) =>
|
||||
return Ok(Async::Ready(Some(Message::Text(s)))),
|
||||
Err(err) =>
|
||||
return Ok(Async::Ready(Some(Message::Error))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => if done {
|
||||
return Ok(Async::Ready(None))
|
||||
} else {
|
||||
return Ok(Async::NotReady)
|
||||
},
|
||||
Err(err) =>
|
||||
return Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// `WebSocket` writer
|
||||
pub struct WsWriter;
|
||||
|
||||
impl WsWriter {
|
||||
|
||||
pub fn text<A>(ctx: &mut HttpContext<A>, text: String)
|
||||
where A: Actor<Context=HttpContext<A>> + Route
|
||||
{
|
||||
let mut frame = wsframe::Frame::message(Vec::from(text.as_str()), OpCode::Text, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
ctx.write(
|
||||
Bytes::from(buf.as_slice())
|
||||
);
|
||||
}
|
||||
|
||||
pub fn binary<A>(ctx: &mut HttpContext<A>, data: Vec<u8>)
|
||||
where A: Actor<Context=HttpContext<A>> + Route
|
||||
{
|
||||
let mut frame = wsframe::Frame::message(data, OpCode::Binary, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
ctx.write(
|
||||
Bytes::from(buf.as_slice())
|
||||
);
|
||||
}
|
||||
|
||||
pub fn ping<A>(ctx: &mut HttpContext<A>, message: String)
|
||||
where A: Actor<Context=HttpContext<A>> + Route
|
||||
{
|
||||
let mut frame = wsframe::Frame::ping(Vec::from(message.as_str()));
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
ctx.write(
|
||||
Bytes::from(buf.as_slice())
|
||||
)
|
||||
}
|
||||
|
||||
pub fn pong<A>(ctx: &mut HttpContext<A>, message: String)
|
||||
where A: Actor<Context=HttpContext<A>> + Route
|
||||
{
|
||||
let mut frame = wsframe::Frame::pong(Vec::from(message.as_str()));
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
ctx.write(
|
||||
Bytes::from(buf.as_slice())
|
||||
)
|
||||
}
|
||||
}
|
496
src/wsframe.rs
Normal file
496
src/wsframe.rs
Normal file
@ -0,0 +1,496 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
use std::fmt;
|
||||
use std::mem::transmute;
|
||||
use std::io::{Write, Error, ErrorKind};
|
||||
use std::default::Default;
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use rand;
|
||||
use bytes::BytesMut;
|
||||
|
||||
use wsproto::{OpCode, CloseCode};
|
||||
|
||||
|
||||
fn apply_mask(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
let iter = buf.iter_mut().zip(mask.iter().cycle());
|
||||
for (byte, &key) in iter {
|
||||
*byte ^= key
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn generate_mask() -> [u8; 4] {
|
||||
unsafe { transmute(rand::random::<u32>()) }
|
||||
}
|
||||
|
||||
/// A struct representing a `WebSocket` frame.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Frame {
|
||||
finished: bool,
|
||||
rsv1: bool,
|
||||
rsv2: bool,
|
||||
rsv3: bool,
|
||||
opcode: OpCode,
|
||||
mask: Option<[u8; 4]>,
|
||||
payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
|
||||
/// Desctructe frame
|
||||
pub fn unpack(self) -> (bool, OpCode, Vec<u8>) {
|
||||
(self.finished, self.opcode, self.payload)
|
||||
}
|
||||
|
||||
/// Get the length of the frame.
|
||||
/// This is the length of the header + the length of the payload.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
let mut header_length = 2;
|
||||
let payload_len = self.payload().len();
|
||||
if payload_len > 125 {
|
||||
if payload_len <= u16::max_value() as usize {
|
||||
header_length += 2;
|
||||
} else {
|
||||
header_length += 8;
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_masked() {
|
||||
header_length += 4;
|
||||
}
|
||||
|
||||
header_length + payload_len
|
||||
}
|
||||
|
||||
/// Test whether the frame is a final frame.
|
||||
#[inline]
|
||||
pub fn is_final(&self) -> bool {
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// Test whether the first reserved bit is set.
|
||||
#[inline]
|
||||
pub fn has_rsv1(&self) -> bool {
|
||||
self.rsv1
|
||||
}
|
||||
|
||||
/// Test whether the second reserved bit is set.
|
||||
#[inline]
|
||||
pub fn has_rsv2(&self) -> bool {
|
||||
self.rsv2
|
||||
}
|
||||
|
||||
/// Test whether the third reserved bit is set.
|
||||
#[inline]
|
||||
pub fn has_rsv3(&self) -> bool {
|
||||
self.rsv3
|
||||
}
|
||||
|
||||
/// Get the OpCode of the frame.
|
||||
#[inline]
|
||||
pub fn opcode(&self) -> OpCode {
|
||||
self.opcode
|
||||
}
|
||||
|
||||
/// Test whether this is a control frame.
|
||||
#[inline]
|
||||
pub fn is_control(&self) -> bool {
|
||||
self.opcode.is_control()
|
||||
}
|
||||
|
||||
/// Get a reference to the frame's payload.
|
||||
#[inline]
|
||||
pub fn payload(&self) -> &Vec<u8> {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
// Test whether the frame is masked.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn is_masked(&self) -> bool {
|
||||
self.mask.is_some()
|
||||
}
|
||||
|
||||
// Get an optional reference to the frame's mask.
|
||||
#[doc(hidden)]
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn mask(&self) -> Option<&[u8; 4]> {
|
||||
self.mask.as_ref()
|
||||
}
|
||||
|
||||
/// Make this frame a final frame.
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn set_final(&mut self, is_final: bool) -> &mut Frame {
|
||||
self.finished = is_final;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the first reserved bit.
|
||||
#[inline]
|
||||
pub fn set_rsv1(&mut self, has_rsv1: bool) -> &mut Frame {
|
||||
self.rsv1 = has_rsv1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the second reserved bit.
|
||||
#[inline]
|
||||
pub fn set_rsv2(&mut self, has_rsv2: bool) -> &mut Frame {
|
||||
self.rsv2 = has_rsv2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the third reserved bit.
|
||||
#[inline]
|
||||
pub fn set_rsv3(&mut self, has_rsv3: bool) -> &mut Frame {
|
||||
self.rsv3 = has_rsv3;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the OpCode.
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn set_opcode(&mut self, opcode: OpCode) -> &mut Frame {
|
||||
self.opcode = opcode;
|
||||
self
|
||||
}
|
||||
|
||||
/// Edit the frame's payload.
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn payload_mut(&mut self) -> &mut Vec<u8> {
|
||||
&mut self.payload
|
||||
}
|
||||
|
||||
// Generate a new mask for this frame.
|
||||
//
|
||||
// This method simply generates and stores the mask. It does not change the payload data.
|
||||
// Instead, the payload data will be masked with the generated mask when the frame is sent
|
||||
// to the other endpoint.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn set_mask(&mut self) -> &mut Frame {
|
||||
self.mask = Some(generate_mask());
|
||||
self
|
||||
}
|
||||
|
||||
// This method unmasks the payload and should only be called on frames that are actually
|
||||
// masked. In other words, those frames that have just been received from a client endpoint.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn remove_mask(&mut self) -> &mut Frame {
|
||||
self.mask.and_then(|mask| {
|
||||
Some(apply_mask(&mut self.payload, &mask))
|
||||
});
|
||||
self.mask = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Consume the frame into its payload.
|
||||
pub fn into_data(self) -> Vec<u8> {
|
||||
self.payload
|
||||
}
|
||||
|
||||
/// Create a new data frame.
|
||||
#[inline]
|
||||
pub fn message(data: Vec<u8>, code: OpCode, finished: bool) -> Frame {
|
||||
debug_assert!(match code {
|
||||
OpCode::Text | OpCode::Binary | OpCode::Continue => true,
|
||||
_ => false,
|
||||
}, "Invalid opcode for data frame.");
|
||||
|
||||
Frame {
|
||||
finished: finished,
|
||||
opcode: code,
|
||||
payload: data,
|
||||
.. Frame::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Pong control frame.
|
||||
#[inline]
|
||||
pub fn pong(data: Vec<u8>) -> Frame {
|
||||
Frame {
|
||||
opcode: OpCode::Pong,
|
||||
payload: data,
|
||||
.. Frame::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Ping control frame.
|
||||
#[inline]
|
||||
pub fn ping(data: Vec<u8>) -> Frame {
|
||||
Frame {
|
||||
opcode: OpCode::Ping,
|
||||
payload: data,
|
||||
.. Frame::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Close control frame.
|
||||
#[inline]
|
||||
pub fn close(code: CloseCode, reason: &str) -> Frame {
|
||||
let raw: [u8; 2] = unsafe {
|
||||
let u: u16 = code.into();
|
||||
transmute(u.to_be())
|
||||
};
|
||||
|
||||
let payload = if let CloseCode::Empty = code {
|
||||
Vec::new()
|
||||
} else {
|
||||
Vec::from_iter(
|
||||
raw[..].iter()
|
||||
.chain(reason.as_bytes().iter())
|
||||
.cloned())
|
||||
};
|
||||
|
||||
Frame {
|
||||
payload: payload,
|
||||
.. Frame::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the input stream into a frame.
|
||||
pub fn parse(buf: &mut BytesMut) -> Result<Option<Frame>, Error> {
|
||||
let mut idx = 2;
|
||||
|
||||
let (frame, length) = {
|
||||
let mut size = buf.len();
|
||||
|
||||
if size < 2 {
|
||||
return Ok(None)
|
||||
}
|
||||
let mut head = [0u8; 2];
|
||||
size -= 2;
|
||||
head.copy_from_slice(&buf[..2]);
|
||||
|
||||
trace!("Parsed headers {:?}", head);
|
||||
|
||||
let first = head[0];
|
||||
let second = head[1];
|
||||
trace!("First: {:b}", first);
|
||||
trace!("Second: {:b}", second);
|
||||
|
||||
let finished = first & 0x80 != 0;
|
||||
|
||||
let rsv1 = first & 0x40 != 0;
|
||||
let rsv2 = first & 0x20 != 0;
|
||||
let rsv3 = first & 0x10 != 0;
|
||||
|
||||
let opcode = OpCode::from(first & 0x0F);
|
||||
trace!("Opcode: {:?}", opcode);
|
||||
|
||||
let masked = second & 0x80 != 0;
|
||||
trace!("Masked: {:?}", masked);
|
||||
|
||||
let mut header_length = 2;
|
||||
let mut length = u64::from(second & 0x7F);
|
||||
|
||||
if length == 126 {
|
||||
let mut length_bytes = [0u8; 2];
|
||||
if size < 2 {
|
||||
return Ok(None)
|
||||
}
|
||||
length_bytes.copy_from_slice(&buf[idx..idx+2]);
|
||||
size -= 2;
|
||||
idx += 2;
|
||||
|
||||
length = u64::from(unsafe{
|
||||
let mut wide: u16 = transmute(length_bytes);
|
||||
wide = u16::from_be(wide);
|
||||
wide});
|
||||
header_length += 2;
|
||||
} else if length == 127 {
|
||||
let mut length_bytes = [0u8; 8];
|
||||
if size < 8 {
|
||||
return Ok(None)
|
||||
}
|
||||
length_bytes.copy_from_slice(&buf[idx..idx+8]);
|
||||
size -= 8;
|
||||
idx += 2;
|
||||
|
||||
unsafe { length = transmute(length_bytes); }
|
||||
length = u64::from_be(length);
|
||||
header_length += 8;
|
||||
}
|
||||
trace!("Payload length: {}", length);
|
||||
|
||||
let mask = if masked {
|
||||
let mut mask_bytes = [0u8; 4];
|
||||
if size < 4 {
|
||||
return Ok(None)
|
||||
} else {
|
||||
header_length += 4;
|
||||
size -= 4;
|
||||
mask_bytes.copy_from_slice(&buf[idx..idx+4]);
|
||||
idx += 4;
|
||||
Some(mask_bytes)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let length = length as usize;
|
||||
if size < length {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
let mut data = Vec::with_capacity(length);
|
||||
if length > 0 {
|
||||
data.extend_from_slice(&buf[idx..idx+length]);
|
||||
}
|
||||
|
||||
// Disallow bad opcode
|
||||
if let OpCode::Bad = opcode {
|
||||
return Err(
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Encountered invalid opcode: {}", first & 0x0F)))
|
||||
}
|
||||
|
||||
// control frames must have length <= 125
|
||||
match opcode {
|
||||
OpCode::Ping | OpCode::Pong if length > 125 => {
|
||||
return Err(
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Rejected WebSocket handshake.Received control frame with length: {}.", length)))
|
||||
}
|
||||
OpCode::Close if length > 125 => {
|
||||
debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame.");
|
||||
return Ok(Some(Frame::close(CloseCode::Protocol, "Received close frame with payload length exceeding 125.")))
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
|
||||
// unmask
|
||||
if let Some(ref mask) = mask {
|
||||
apply_mask(&mut data, mask);
|
||||
}
|
||||
|
||||
let frame = Frame {
|
||||
finished: finished,
|
||||
rsv1: rsv1,
|
||||
rsv2: rsv2,
|
||||
rsv3: rsv3,
|
||||
opcode: opcode,
|
||||
mask: mask,
|
||||
payload: data,
|
||||
};
|
||||
|
||||
(frame, header_length + length)
|
||||
};
|
||||
|
||||
buf.split_to(length);
|
||||
Ok(Some(frame))
|
||||
}
|
||||
|
||||
/// Write a frame out to a buffer
|
||||
pub fn format<W>(&mut self, w: &mut W) -> Result<(), Error>
|
||||
where W: Write
|
||||
{
|
||||
let mut one = 0u8;
|
||||
let code: u8 = self.opcode.into();
|
||||
if self.is_final() {
|
||||
one |= 0x80;
|
||||
}
|
||||
if self.has_rsv1() {
|
||||
one |= 0x40;
|
||||
}
|
||||
if self.has_rsv2() {
|
||||
one |= 0x20;
|
||||
}
|
||||
if self.has_rsv3() {
|
||||
one |= 0x10;
|
||||
}
|
||||
one |= code;
|
||||
|
||||
let mut two = 0u8;
|
||||
|
||||
if self.is_masked() {
|
||||
two |= 0x80;
|
||||
}
|
||||
|
||||
if self.payload.len() < 126 {
|
||||
two |= self.payload.len() as u8;
|
||||
let headers = [one, two];
|
||||
try!(w.write_all(&headers));
|
||||
} else if self.payload.len() <= 65_535 {
|
||||
two |= 126;
|
||||
let length_bytes: [u8; 2] = unsafe {
|
||||
let short = self.payload.len() as u16;
|
||||
transmute(short.to_be())
|
||||
};
|
||||
let headers = [one, two, length_bytes[0], length_bytes[1]];
|
||||
try!(w.write_all(&headers));
|
||||
} else {
|
||||
two |= 127;
|
||||
let length_bytes: [u8; 8] = unsafe {
|
||||
let long = self.payload.len() as u64;
|
||||
transmute(long.to_be())
|
||||
};
|
||||
let headers = [
|
||||
one,
|
||||
two,
|
||||
length_bytes[0],
|
||||
length_bytes[1],
|
||||
length_bytes[2],
|
||||
length_bytes[3],
|
||||
length_bytes[4],
|
||||
length_bytes[5],
|
||||
length_bytes[6],
|
||||
length_bytes[7],
|
||||
];
|
||||
try!(w.write_all(&headers));
|
||||
}
|
||||
|
||||
if self.is_masked() {
|
||||
let mask = self.mask.take().unwrap();
|
||||
apply_mask(&mut self.payload, &mask);
|
||||
try!(w.write_all(&mask));
|
||||
}
|
||||
|
||||
try!(w.write_all(&self.payload));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Frame {
|
||||
fn default() -> Frame {
|
||||
Frame {
|
||||
finished: true,
|
||||
rsv1: false,
|
||||
rsv2: false,
|
||||
rsv3: false,
|
||||
opcode: OpCode::Close,
|
||||
mask: None,
|
||||
payload: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Frame {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f,
|
||||
"
|
||||
<FRAME>
|
||||
final: {}
|
||||
reserved: {} {} {}
|
||||
opcode: {}
|
||||
length: {}
|
||||
payload length: {}
|
||||
payload: 0x{}
|
||||
</FRAME>",
|
||||
self.finished,
|
||||
self.rsv1,
|
||||
self.rsv2,
|
||||
self.rsv3,
|
||||
self.opcode,
|
||||
// self.mask.map(|mask| format!("{:?}", mask)).unwrap_or("NONE".into()),
|
||||
self.len(),
|
||||
self.payload.len(),
|
||||
self.payload.iter().map(|byte| format!("{:x}", byte)).collect::<String>())
|
||||
}
|
||||
}
|
300
src/wsproto.rs
Normal file
300
src/wsproto.rs
Normal file
@ -0,0 +1,300 @@
|
||||
#![allow(dead_code, unused_variables)]
|
||||
use std::fmt;
|
||||
use std::convert::{Into, From};
|
||||
use std::mem::transmute;
|
||||
|
||||
use rand;
|
||||
use sha1;
|
||||
|
||||
|
||||
use self::OpCode::*;
|
||||
/// Operation codes as part of rfc6455.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum OpCode {
|
||||
/// Indicates a continuation frame of a fragmented message.
|
||||
Continue,
|
||||
/// Indicates a text data frame.
|
||||
Text,
|
||||
/// Indicates a binary data frame.
|
||||
Binary,
|
||||
/// Indicates a close control frame.
|
||||
Close,
|
||||
/// Indicates a ping control frame.
|
||||
Ping,
|
||||
/// Indicates a pong control frame.
|
||||
Pong,
|
||||
/// Indicates an invalid opcode was received.
|
||||
Bad,
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
|
||||
/// Test whether the opcode indicates a control frame.
|
||||
pub fn is_control(&self) -> bool {
|
||||
match *self {
|
||||
Text | Binary | Continue => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl fmt::Display for OpCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Continue => write!(f, "CONTINUE"),
|
||||
Text => write!(f, "TEXT"),
|
||||
Binary => write!(f, "BINARY"),
|
||||
Close => write!(f, "CLOSE"),
|
||||
Ping => write!(f, "PING"),
|
||||
Pong => write!(f, "PONG"),
|
||||
Bad => write!(f, "BAD"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u8> for OpCode {
|
||||
|
||||
fn into(self) -> u8 {
|
||||
match self {
|
||||
Continue => 0,
|
||||
Text => 1,
|
||||
Binary => 2,
|
||||
Close => 8,
|
||||
Ping => 9,
|
||||
Pong => 10,
|
||||
Bad => {
|
||||
debug_assert!(false, "Attempted to convert invalid opcode to u8. This is a bug.");
|
||||
8 // if this somehow happens, a close frame will help us tear down quickly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for OpCode {
|
||||
|
||||
fn from(byte: u8) -> OpCode {
|
||||
match byte {
|
||||
0 => Continue,
|
||||
1 => Text,
|
||||
2 => Binary,
|
||||
8 => Close,
|
||||
9 => Ping,
|
||||
10 => Pong,
|
||||
_ => Bad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use self::CloseCode::*;
|
||||
/// Status code used to indicate why an endpoint is closing the `WebSocket` connection.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum CloseCode {
|
||||
/// Indicates a normal closure, meaning that the purpose for
|
||||
/// which the connection was established has been fulfilled.
|
||||
Normal,
|
||||
/// Indicates that an endpoint is "going away", such as a server
|
||||
/// going down or a browser having navigated away from a page.
|
||||
Away,
|
||||
/// Indicates that an endpoint is terminating the connection due
|
||||
/// to a protocol error.
|
||||
Protocol,
|
||||
/// Indicates that an endpoint is terminating the connection
|
||||
/// because it has received a type of data it cannot accept (e.g., an
|
||||
/// endpoint that understands only text data MAY send this if it
|
||||
/// receives a binary message).
|
||||
Unsupported,
|
||||
/// Indicates that no status code was included in a closing frame. This
|
||||
/// close code makes it possible to use a single method, `on_close` to
|
||||
/// handle even cases where no close code was provided.
|
||||
Status,
|
||||
/// Indicates an abnormal closure. If the abnormal closure was due to an
|
||||
/// error, this close code will not be used. Instead, the `on_error` method
|
||||
/// of the handler will be called with the error. However, if the connection
|
||||
/// is simply dropped, without an error, this close code will be sent to the
|
||||
/// handler.
|
||||
Abnormal,
|
||||
/// Indicates that an endpoint is terminating the connection
|
||||
/// because it has received data within a message that was not
|
||||
/// consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
|
||||
/// data within a text message).
|
||||
Invalid,
|
||||
/// Indicates that an endpoint is terminating the connection
|
||||
/// because it has received a message that violates its policy. This
|
||||
/// is a generic status code that can be returned when there is no
|
||||
/// other more suitable status code (e.g., Unsupported or Size) or if there
|
||||
/// is a need to hide specific details about the policy.
|
||||
Policy,
|
||||
/// Indicates that an endpoint is terminating the connection
|
||||
/// because it has received a message that is too big for it to
|
||||
/// process.
|
||||
Size,
|
||||
/// Indicates that an endpoint (client) is terminating the
|
||||
/// connection because it has expected the server to negotiate one or
|
||||
/// more extension, but the server didn't return them in the response
|
||||
/// message of the WebSocket handshake. The list of extensions that
|
||||
/// are needed should be given as the reason for closing.
|
||||
/// Note that this status code is not used by the server, because it
|
||||
/// can fail the WebSocket handshake instead.
|
||||
Extension,
|
||||
/// Indicates that a server is terminating the connection because
|
||||
/// it encountered an unexpected condition that prevented it from
|
||||
/// fulfilling the request.
|
||||
Error,
|
||||
/// Indicates that the server is restarting. A client may choose to reconnect,
|
||||
/// and if it does, it should use a randomized delay of 5-30 seconds between attempts.
|
||||
Restart,
|
||||
/// Indicates that the server is overloaded and the client should either connect
|
||||
/// to a different IP (when multiple targets exist), or reconnect to the same IP
|
||||
/// when a user has performed an action.
|
||||
Again,
|
||||
#[doc(hidden)]
|
||||
Tls,
|
||||
#[doc(hidden)]
|
||||
Empty,
|
||||
#[doc(hidden)]
|
||||
Other(u16),
|
||||
}
|
||||
|
||||
impl Into<u16> for CloseCode {
|
||||
|
||||
fn into(self) -> u16 {
|
||||
match self {
|
||||
Normal => 1000,
|
||||
Away => 1001,
|
||||
Protocol => 1002,
|
||||
Unsupported => 1003,
|
||||
Status => 1005,
|
||||
Abnormal => 1006,
|
||||
Invalid => 1007,
|
||||
Policy => 1008,
|
||||
Size => 1009,
|
||||
Extension => 1010,
|
||||
Error => 1011,
|
||||
Restart => 1012,
|
||||
Again => 1013,
|
||||
Tls => 1015,
|
||||
Empty => 0,
|
||||
Other(code) => code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for CloseCode {
|
||||
|
||||
fn from(code: u16) -> CloseCode {
|
||||
match code {
|
||||
1000 => Normal,
|
||||
1001 => Away,
|
||||
1002 => Protocol,
|
||||
1003 => Unsupported,
|
||||
1005 => Status,
|
||||
1006 => Abnormal,
|
||||
1007 => Invalid,
|
||||
1008 => Policy,
|
||||
1009 => Size,
|
||||
1010 => Extension,
|
||||
1011 => Error,
|
||||
1012 => Restart,
|
||||
1013 => Again,
|
||||
1015 => Tls,
|
||||
0 => Empty,
|
||||
_ => Other(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
static BASE64: &'static [u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
|
||||
fn generate_key() -> String {
|
||||
let key: [u8; 16] = unsafe {
|
||||
transmute(rand::random::<(u64, u64)>())
|
||||
};
|
||||
encode_base64(&key)
|
||||
}
|
||||
|
||||
// TODO: hash is always same size, we dont need String
|
||||
pub(crate) fn hash_key(key: &[u8]) -> String {
|
||||
let mut hasher = sha1::Sha1::new();
|
||||
|
||||
hasher.update(key);
|
||||
hasher.update(WS_GUID.as_bytes());
|
||||
|
||||
encode_base64(&hasher.digest().bytes())
|
||||
}
|
||||
|
||||
|
||||
// This code is based on rustc_serialize base64 STANDARD
|
||||
fn encode_base64(data: &[u8]) -> String {
|
||||
let len = data.len();
|
||||
let mod_len = len % 3;
|
||||
|
||||
let mut encoded = vec![b'='; (len + 2) / 3 * 4];
|
||||
{
|
||||
let mut in_iter = data[..len - mod_len].iter().map(|&c| u32::from(c));
|
||||
let mut out_iter = encoded.iter_mut();
|
||||
|
||||
let enc = |val| BASE64[val as usize];
|
||||
let mut write = |val| *out_iter.next().unwrap() = val;
|
||||
|
||||
while let (Some(one), Some(two), Some(three)) = (in_iter.next(), in_iter.next(), in_iter.next()) {
|
||||
let g24 = one << 16 | two << 8 | three;
|
||||
write(enc((g24 >> 18) & 63));
|
||||
write(enc((g24 >> 12) & 63));
|
||||
write(enc((g24 >> 6 ) & 63));
|
||||
write(enc(g24 & 63));
|
||||
}
|
||||
|
||||
match mod_len {
|
||||
1 => {
|
||||
let pad = u32::from(data[len-1]) << 16;
|
||||
write(enc((pad >> 18) & 63));
|
||||
write(enc((pad >> 12) & 63));
|
||||
}
|
||||
2 => {
|
||||
let pad = u32::from(data[len-2]) << 16 | u32::from(data[len-1]) << 8;
|
||||
write(enc((pad >> 18) & 63));
|
||||
write(enc((pad >> 12) & 63));
|
||||
write(enc((pad >> 6) & 63));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
String::from_utf8(encoded).unwrap()
|
||||
}
|
||||
|
||||
|
||||
mod test {
|
||||
#![allow(unused_imports, unused_variables, dead_code)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn opcode_from_u8() {
|
||||
let byte = 2u8;
|
||||
assert_eq!(OpCode::from(byte), OpCode::Binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode_into_u8() {
|
||||
let text = OpCode::Text;
|
||||
let byte: u8 = text.into();
|
||||
assert_eq!(byte, 1u8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closecode_from_u16() {
|
||||
let byte = 1008u16;
|
||||
assert_eq!(CloseCode::from(byte), CloseCode::Policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closecode_into_u16() {
|
||||
let text = CloseCode::Away;
|
||||
let byte: u16 = text.into();
|
||||
assert_eq!(byte, 1001u16);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user