mirror of
https://github.com/actix/actix-extras.git
synced 2025-06-26 18:37:41 +02:00
move websocket code to submodule
This commit is contained in:
256
src/ws/context.rs
Normal file
256
src/ws/context.rs
Normal file
@ -0,0 +1,256 @@
|
||||
use std::mem;
|
||||
use std::collections::VecDeque;
|
||||
use futures::{Async, Poll};
|
||||
use futures::sync::oneshot::Sender;
|
||||
use futures::unsync::oneshot;
|
||||
|
||||
use actix::{Actor, ActorState, ActorContext, AsyncContext,
|
||||
Address, SyncAddress, Handler, Subscriber, ResponseType, SpawnHandle};
|
||||
use actix::fut::ActorFuture;
|
||||
use actix::dev::{queue, AsyncContextApi,
|
||||
ContextImpl, ContextProtocol, Envelope, ToEnvelope, RemoteEnvelope};
|
||||
|
||||
use body::{Body, Binary};
|
||||
use error::{Error, Result, ErrorInternalServerError};
|
||||
use httprequest::HttpRequest;
|
||||
use context::{Frame as ContextFrame, ActorHttpContext, Drain};
|
||||
|
||||
use ws::frame::Frame;
|
||||
use ws::proto::{OpCode, CloseCode};
|
||||
|
||||
|
||||
/// Http actor execution context
|
||||
pub struct WebsocketContext<A, S=()> where A: Actor<Context=WebsocketContext<A, S>>,
|
||||
{
|
||||
inner: ContextImpl<A>,
|
||||
stream: VecDeque<ContextFrame>,
|
||||
request: HttpRequest<S>,
|
||||
disconnected: bool,
|
||||
}
|
||||
|
||||
impl<A, S> ActorContext for WebsocketContext<A, S> where A: Actor<Context=Self>
|
||||
{
|
||||
fn stop(&mut self) {
|
||||
self.inner.stop();
|
||||
}
|
||||
fn terminate(&mut self) {
|
||||
self.inner.terminate()
|
||||
}
|
||||
fn state(&self) -> ActorState {
|
||||
self.inner.state()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> AsyncContext<A> for WebsocketContext<A, S> where A: Actor<Context=Self>
|
||||
{
|
||||
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
|
||||
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||
{
|
||||
self.inner.spawn(fut)
|
||||
}
|
||||
|
||||
fn wait<F>(&mut self, fut: F)
|
||||
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||
{
|
||||
self.inner.wait(fut)
|
||||
}
|
||||
|
||||
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
||||
self.inner.cancel_future(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<A, S> AsyncContextApi<A> for WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||
#[inline]
|
||||
fn unsync_sender(&mut self) -> queue::unsync::UnboundedSender<ContextProtocol<A>> {
|
||||
self.inner.unsync_sender()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn unsync_address(&mut self) -> Address<A> {
|
||||
self.inner.unsync_address()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sync_address(&mut self) -> SyncAddress<A> {
|
||||
self.inner.sync_address()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S: 'static> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||
|
||||
#[inline]
|
||||
pub fn new(req: HttpRequest<S>, actor: A) -> WebsocketContext<A, S> {
|
||||
WebsocketContext::from_request(req).actor(actor)
|
||||
}
|
||||
|
||||
pub fn from_request(req: HttpRequest<S>) -> WebsocketContext<A, S> {
|
||||
WebsocketContext {
|
||||
inner: ContextImpl::new(None),
|
||||
stream: VecDeque::new(),
|
||||
request: req,
|
||||
disconnected: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn actor(mut self, actor: A) -> WebsocketContext<A, S> {
|
||||
self.inner.set_actor(actor);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||
|
||||
/// Write payload
|
||||
#[inline]
|
||||
fn write<B: Into<Binary>>(&mut self, data: B) {
|
||||
if !self.disconnected {
|
||||
self.stream.push_back(ContextFrame::Payload(Some(data.into())));
|
||||
} else {
|
||||
warn!("Trying to write to disconnected response");
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared application state
|
||||
#[inline]
|
||||
pub fn state(&self) -> &S {
|
||||
self.request.state()
|
||||
}
|
||||
|
||||
/// Incoming request
|
||||
#[inline]
|
||||
pub fn request(&mut self) -> &mut HttpRequest<S> {
|
||||
&mut self.request
|
||||
}
|
||||
|
||||
/// Send text frame
|
||||
pub fn text(&mut self, text: &str) {
|
||||
let mut frame = Frame::message(Vec::from(text), OpCode::Text, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
self.write(buf);
|
||||
}
|
||||
|
||||
/// Send binary frame
|
||||
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
|
||||
let mut frame = Frame::message(data, OpCode::Binary, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
self.write(buf);
|
||||
}
|
||||
|
||||
/// Send ping frame
|
||||
pub fn ping(&mut self, message: &str) {
|
||||
let mut frame = Frame::message(Vec::from(message), OpCode::Ping, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
self.write(buf);
|
||||
}
|
||||
|
||||
/// Send pong frame
|
||||
pub fn pong(&mut self, message: &str) {
|
||||
let mut frame = Frame::message(Vec::from(message), OpCode::Pong, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
self.write(buf);
|
||||
}
|
||||
|
||||
/// Send close frame
|
||||
pub fn close(&mut self, code: CloseCode, reason: &str) {
|
||||
let mut frame = Frame::close(code, reason);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
self.write(buf);
|
||||
}
|
||||
|
||||
/// Returns drain future
|
||||
pub fn drain(&mut self) -> Drain<A> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.inner.modify();
|
||||
self.stream.push_back(ContextFrame::Drain(tx));
|
||||
Drain::new(rx)
|
||||
}
|
||||
|
||||
/// Check if connection still open
|
||||
#[inline]
|
||||
pub fn connected(&self) -> bool {
|
||||
!self.disconnected
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> WebsocketContext<A, S> where A: Actor<Context=Self> {
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
|
||||
where A: Handler<M>, M: ResponseType + 'static
|
||||
{
|
||||
self.inner.subscriber()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
|
||||
where A: Handler<M>,
|
||||
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send,
|
||||
{
|
||||
self.inner.sync_subscriber()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ActorHttpContext for WebsocketContext<A, S> where A: Actor<Context=Self>, S: 'static {
|
||||
|
||||
#[inline]
|
||||
fn disconnected(&mut self) {
|
||||
self.disconnected = true;
|
||||
self.stop();
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<ContextFrame>, Error> {
|
||||
let ctx: &mut WebsocketContext<A, S> = unsafe {
|
||||
mem::transmute(self as &mut WebsocketContext<A, S>)
|
||||
};
|
||||
|
||||
if self.inner.alive() {
|
||||
match self.inner.poll(ctx) {
|
||||
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
|
||||
Err(_) => return Err(ErrorInternalServerError("error").into()),
|
||||
}
|
||||
}
|
||||
|
||||
// frames
|
||||
if let Some(frame) = self.stream.pop_front() {
|
||||
Ok(Async::Ready(Some(frame)))
|
||||
} else if self.inner.alive() {
|
||||
Ok(Async::NotReady)
|
||||
} else {
|
||||
Ok(Async::Ready(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> ToEnvelope<A> for WebsocketContext<A, S>
|
||||
where A: Actor<Context=WebsocketContext<A, S>>,
|
||||
{
|
||||
#[inline]
|
||||
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>,
|
||||
channel_on_drop: bool) -> Envelope<A>
|
||||
where A: Handler<M>,
|
||||
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send {
|
||||
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> From<WebsocketContext<A, S>> for Body
|
||||
where A: Actor<Context=WebsocketContext<A, S>>, S: 'static
|
||||
{
|
||||
fn from(ctx: WebsocketContext<A, S>) -> Body {
|
||||
Body::Actor(Box::new(ctx))
|
||||
}
|
||||
}
|
432
src/ws/frame.rs
Normal file
432
src/ws/frame.rs
Normal file
@ -0,0 +1,432 @@
|
||||
use std::{fmt, mem};
|
||||
use std::io::{Write, Error, ErrorKind};
|
||||
use std::iter::FromIterator;
|
||||
use bytes::BytesMut;
|
||||
|
||||
use body::Binary;
|
||||
use ws::proto::{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
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing a `WebSocket` frame.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Frame {
|
||||
finished: bool,
|
||||
rsv1: bool,
|
||||
rsv2: bool,
|
||||
rsv3: bool,
|
||||
opcode: OpCode,
|
||||
mask: Option<[u8; 4]>,
|
||||
payload: Binary,
|
||||
}
|
||||
|
||||
impl Frame {
|
||||
|
||||
/// Desctructe frame
|
||||
pub fn unpack(self) -> (bool, OpCode, Binary) {
|
||||
(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.mask.is_some() {
|
||||
header_length += 4;
|
||||
}
|
||||
|
||||
header_length + payload_len
|
||||
}
|
||||
|
||||
/// Create a new data frame.
|
||||
#[inline]
|
||||
pub fn message<B: Into<Binary>>(data: B, code: OpCode, finished: bool) -> Frame {
|
||||
Frame {
|
||||
finished: finished,
|
||||
opcode: code,
|
||||
payload: data.into(),
|
||||
.. 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();
|
||||
mem::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.into(),
|
||||
.. 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 {
|
||||
if size < 2 {
|
||||
return Ok(None)
|
||||
}
|
||||
let mut length_bytes = [0u8; 2];
|
||||
length_bytes.copy_from_slice(&buf[idx..idx+2]);
|
||||
size -= 2;
|
||||
idx += 2;
|
||||
|
||||
length = u64::from(unsafe{
|
||||
let mut wide: u16 = mem::transmute(length_bytes);
|
||||
wide = u16::from_be(wide);
|
||||
wide});
|
||||
header_length += 2;
|
||||
} else if length == 127 {
|
||||
if size < 8 {
|
||||
return Ok(None)
|
||||
}
|
||||
let mut length_bytes = [0u8; 8];
|
||||
length_bytes.copy_from_slice(&buf[idx..idx+8]);
|
||||
size -= 8;
|
||||
idx += 8;
|
||||
|
||||
unsafe { length = mem::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.into(),
|
||||
};
|
||||
|
||||
(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.finished {
|
||||
one |= 0x80;
|
||||
}
|
||||
if self.rsv1 {
|
||||
one |= 0x40;
|
||||
}
|
||||
if self.rsv2 {
|
||||
one |= 0x20;
|
||||
}
|
||||
if self.rsv3 {
|
||||
one |= 0x10;
|
||||
}
|
||||
one |= code;
|
||||
|
||||
let mut two = 0u8;
|
||||
|
||||
if self.mask.is_some() {
|
||||
two |= 0x80;
|
||||
}
|
||||
|
||||
if self.payload.len() < 126 {
|
||||
two |= self.payload.len() as u8;
|
||||
let headers = [one, two];
|
||||
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;
|
||||
mem::transmute(short.to_be())
|
||||
};
|
||||
let headers = [one, two, length_bytes[0], length_bytes[1]];
|
||||
w.write_all(&headers)?;
|
||||
} else {
|
||||
two |= 127;
|
||||
let length_bytes: [u8; 8] = unsafe {
|
||||
let long = self.payload.len() as u64;
|
||||
mem::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],
|
||||
];
|
||||
w.write_all(&headers)?;
|
||||
}
|
||||
|
||||
if self.mask.is_some() {
|
||||
let mask = self.mask.take().unwrap();
|
||||
let mut payload = Vec::from(self.payload.as_ref());
|
||||
apply_mask(&mut payload, &mask);
|
||||
w.write_all(&mask)?;
|
||||
w.write_all(payload.as_ref())?;
|
||||
} else {
|
||||
w.write_all(self.payload.as_ref())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Frame {
|
||||
fn default() -> Frame {
|
||||
Frame {
|
||||
finished: true,
|
||||
rsv1: false,
|
||||
rsv2: false,
|
||||
rsv3: false,
|
||||
opcode: OpCode::Close,
|
||||
mask: None,
|
||||
payload: Binary::from(&b""[..]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.as_ref().iter().map(
|
||||
|byte| format!("{:x}", byte)).collect::<String>())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000001u8][..]);
|
||||
assert!(Frame::parse(&mut buf).unwrap().is_none());
|
||||
buf.extend(b"1");
|
||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||
println!("FRAME: {}", frame);
|
||||
assert!(!frame.finished);
|
||||
assert_eq!(frame.opcode, OpCode::Text);
|
||||
assert_eq!(frame.payload.as_ref(), &b"1"[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_length0() {
|
||||
let mut buf = BytesMut::from(&[0b00000001u8, 0b00000000u8][..]);
|
||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||
assert!(!frame.finished);
|
||||
assert_eq!(frame.opcode, OpCode::Text);
|
||||
assert!(frame.payload.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_length2() {
|
||||
let mut buf = BytesMut::from(&[0b00000001u8, 126u8][..]);
|
||||
assert!(Frame::parse(&mut buf).unwrap().is_none());
|
||||
buf.extend(&[0u8, 4u8][..]);
|
||||
buf.extend(b"1234");
|
||||
|
||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||
assert!(!frame.finished);
|
||||
assert_eq!(frame.opcode, OpCode::Text);
|
||||
assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_length4() {
|
||||
let mut buf = BytesMut::from(&[0b00000001u8, 127u8][..]);
|
||||
assert!(Frame::parse(&mut buf).unwrap().is_none());
|
||||
buf.extend(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 4u8][..]);
|
||||
buf.extend(b"1234");
|
||||
|
||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||
assert!(!frame.finished);
|
||||
assert_eq!(frame.opcode, OpCode::Text);
|
||||
assert_eq!(frame.payload.as_ref(), &b"1234"[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_frame_mask() {
|
||||
let mut buf = BytesMut::from(&[0b00000001u8, 0b10000001u8][..]);
|
||||
buf.extend(b"0001");
|
||||
buf.extend(b"1");
|
||||
|
||||
let frame = Frame::parse(&mut buf).unwrap().unwrap();
|
||||
assert!(!frame.finished);
|
||||
assert_eq!(frame.opcode, OpCode::Text);
|
||||
assert_eq!(frame.payload, vec![1u8].into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ping_frame() {
|
||||
let mut frame = Frame::message(Vec::from("data"), OpCode::Ping, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
let mut v = vec![137u8, 4u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(buf, v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pong_frame() {
|
||||
let mut frame = Frame::message(Vec::from("data"), OpCode::Pong, true);
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
let mut v = vec![138u8, 4u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(buf, v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_close_frame() {
|
||||
let mut frame = Frame::close(CloseCode::Normal, "data");
|
||||
let mut buf = Vec::new();
|
||||
frame.format(&mut buf).unwrap();
|
||||
|
||||
let mut v = vec![136u8, 6u8, 3u8, 232u8];
|
||||
v.extend(b"data");
|
||||
assert_eq!(buf, v);
|
||||
}
|
||||
}
|
348
src/ws/mod.rs
Normal file
348
src/ws/mod.rs
Normal file
@ -0,0 +1,348 @@
|
||||
//! `WebSocket` support for Actix
|
||||
//!
|
||||
//! To setup a `WebSocket`, first do web socket handshake then on success convert `Payload`
|
||||
//! into a `WsStream` stream and then use `WsWriter` to communicate with the peer.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate actix;
|
||||
//! # extern crate actix_web;
|
||||
//! # use actix::*;
|
||||
//! # use actix_web::*;
|
||||
//! use actix_web::ws;
|
||||
//!
|
||||
//! // do websocket handshake and start actor
|
||||
//! fn ws_index(req: HttpRequest) -> Result<HttpResponse> {
|
||||
//! ws::start(req, Ws)
|
||||
//! }
|
||||
//!
|
||||
//! struct Ws;
|
||||
//!
|
||||
//! impl Actor for Ws {
|
||||
//! type Context = ws::WebsocketContext<Self>;
|
||||
//! }
|
||||
//!
|
||||
//! // Define Handler for ws::Message message
|
||||
//! impl Handler<ws::Message> for Ws {
|
||||
//! type Result = ();
|
||||
//!
|
||||
//! fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
//! match msg {
|
||||
//! ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
//! ws::Message::Text(text) => ctx.text(&text),
|
||||
//! ws::Message::Binary(bin) => ctx.binary(bin),
|
||||
//! _ => (),
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! #
|
||||
//! # fn main() {
|
||||
//! # Application::new()
|
||||
//! # .resource("/ws/", |r| r.f(ws_index)) // <- register websocket route
|
||||
//! # .finish();
|
||||
//! # }
|
||||
//! ```
|
||||
use bytes::BytesMut;
|
||||
use http::{Method, StatusCode, header};
|
||||
use futures::{Async, Poll, Stream};
|
||||
|
||||
use actix::{Actor, AsyncContext, ResponseType, Handler};
|
||||
|
||||
use body::Binary;
|
||||
use payload::ReadAny;
|
||||
use error::{Error, WsHandshakeError};
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::{ConnectionType, HttpResponse, HttpResponseBuilder};
|
||||
|
||||
mod frame;
|
||||
mod proto;
|
||||
mod context;
|
||||
|
||||
use ws::frame::Frame;
|
||||
use ws::proto::{hash_key, OpCode};
|
||||
pub use ws::proto::CloseCode;
|
||||
pub use ws::context::WebsocketContext;
|
||||
|
||||
const SEC_WEBSOCKET_ACCEPT: &str = "SEC-WEBSOCKET-ACCEPT";
|
||||
const SEC_WEBSOCKET_KEY: &str = "SEC-WEBSOCKET-KEY";
|
||||
const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION";
|
||||
// const SEC_WEBSOCKET_PROTOCOL: &'static str = "SEC-WEBSOCKET-PROTOCOL";
|
||||
|
||||
|
||||
/// `WebSocket` Message
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
Text(String),
|
||||
Binary(Binary),
|
||||
Ping(String),
|
||||
Pong(String),
|
||||
Close,
|
||||
Closed,
|
||||
Error
|
||||
}
|
||||
|
||||
impl ResponseType for Message {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
}
|
||||
|
||||
/// Do websocket handshake and start actor
|
||||
pub fn start<A, S>(mut req: HttpRequest<S>, actor: A) -> Result<HttpResponse, Error>
|
||||
where A: Actor<Context=WebsocketContext<A, S>> + Handler<Message>,
|
||||
S: 'static
|
||||
{
|
||||
let mut resp = handshake(&req)?;
|
||||
let stream = WsStream::new(req.payload_mut().readany());
|
||||
|
||||
let mut ctx = WebsocketContext::new(req, actor);
|
||||
ctx.add_message_stream(stream);
|
||||
|
||||
Ok(resp.body(ctx)?)
|
||||
}
|
||||
|
||||
/// Prepare `WebSocket` handshake response.
|
||||
///
|
||||
/// This function returns handshake `HttpResponse`, ready to send to peer.
|
||||
/// 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 handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, WsHandshakeError> {
|
||||
// WebSocket accepts only GET
|
||||
if *req.method() != Method::GET {
|
||||
return Err(WsHandshakeError::GetMethodRequired)
|
||||
}
|
||||
|
||||
// Check for "UPGRADE" to websocket header
|
||||
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
|
||||
};
|
||||
if !has_hdr {
|
||||
return Err(WsHandshakeError::NoWebsocketUpgrade)
|
||||
}
|
||||
|
||||
// Upgrade connection
|
||||
if !req.upgrade() {
|
||||
return Err(WsHandshakeError::NoConnectionUpgrade)
|
||||
}
|
||||
|
||||
// check supported version
|
||||
if !req.headers().contains_key(SEC_WEBSOCKET_VERSION) {
|
||||
return Err(WsHandshakeError::NoVersionHeader)
|
||||
}
|
||||
let supported_ver = {
|
||||
if let Some(hdr) = req.headers().get(SEC_WEBSOCKET_VERSION) {
|
||||
hdr == "13" || hdr == "8" || hdr == "7"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if !supported_ver {
|
||||
return Err(WsHandshakeError::UnsupportedVersion)
|
||||
}
|
||||
|
||||
// check client handshake for validity
|
||||
if !req.headers().contains_key(SEC_WEBSOCKET_KEY) {
|
||||
return Err(WsHandshakeError::BadWebsocketKey)
|
||||
}
|
||||
let key = {
|
||||
let key = req.headers().get(SEC_WEBSOCKET_KEY).unwrap();
|
||||
hash_key(key.as_ref())
|
||||
};
|
||||
|
||||
Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||
.connection_type(ConnectionType::Upgrade)
|
||||
.header(header::UPGRADE, "websocket")
|
||||
.header(header::TRANSFER_ENCODING, "chunked")
|
||||
.header(SEC_WEBSOCKET_ACCEPT, key.as_str())
|
||||
.take())
|
||||
}
|
||||
|
||||
/// Maps `Payload` stream into stream of `ws::Message` items
|
||||
pub struct WsStream {
|
||||
rx: ReadAny,
|
||||
buf: BytesMut,
|
||||
closed: bool,
|
||||
error_sent: bool,
|
||||
}
|
||||
|
||||
impl WsStream {
|
||||
pub fn new(payload: ReadAny) -> WsStream {
|
||||
WsStream { rx: payload,
|
||||
buf: BytesMut::new(),
|
||||
closed: false,
|
||||
error_sent: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WsStream {
|
||||
type Item = Message;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
let mut done = false;
|
||||
|
||||
if !self.closed {
|
||||
loop {
|
||||
match self.rx.poll() {
|
||||
Ok(Async::Ready(Some(chunk))) => {
|
||||
self.buf.extend_from_slice(&chunk)
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
done = true;
|
||||
self.closed = true;
|
||||
break;
|
||||
}
|
||||
Ok(Async::NotReady) => break,
|
||||
Err(_) => {
|
||||
self.closed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match Frame::parse(&mut self.buf) {
|
||||
Ok(Some(frame)) => {
|
||||
// trace!("WsFrame {}", frame);
|
||||
let (_finished, opcode, payload) = frame.unpack();
|
||||
|
||||
match opcode {
|
||||
OpCode::Continue => continue,
|
||||
OpCode::Bad =>
|
||||
return Ok(Async::Ready(Some(Message::Error))),
|
||||
OpCode::Close => {
|
||||
self.closed = true;
|
||||
self.error_sent = true;
|
||||
return Ok(Async::Ready(Some(Message::Closed)))
|
||||
},
|
||||
OpCode::Ping =>
|
||||
return Ok(Async::Ready(Some(
|
||||
Message::Ping(
|
||||
String::from_utf8_lossy(payload.as_ref()).into())))),
|
||||
OpCode::Pong =>
|
||||
return Ok(Async::Ready(Some(
|
||||
Message::Pong(
|
||||
String::from_utf8_lossy(payload.as_ref()).into())))),
|
||||
OpCode::Binary =>
|
||||
return Ok(Async::Ready(Some(Message::Binary(payload)))),
|
||||
OpCode::Text => {
|
||||
let tmp = Vec::from(payload.as_ref());
|
||||
match String::from_utf8(tmp) {
|
||||
Ok(s) =>
|
||||
return Ok(Async::Ready(Some(Message::Text(s)))),
|
||||
Err(_) =>
|
||||
return Ok(Async::Ready(Some(Message::Error))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
if done {
|
||||
return Ok(Async::Ready(None))
|
||||
} else if self.closed {
|
||||
if !self.error_sent {
|
||||
self.error_sent = true;
|
||||
return Ok(Async::Ready(Some(Message::Closed)))
|
||||
} else {
|
||||
return Ok(Async::Ready(None))
|
||||
}
|
||||
} else {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
self.closed = true;
|
||||
self.error_sent = true;
|
||||
return Ok(Async::Ready(Some(Message::Error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
use http::{Method, HeaderMap, Version, Uri, header};
|
||||
|
||||
#[test]
|
||||
fn test_handshake() {
|
||||
let req = HttpRequest::new(Method::POST, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, HeaderMap::new(), None);
|
||||
assert_eq!(WsHandshakeError::GetMethodRequired, handshake(&req).err().unwrap());
|
||||
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, HeaderMap::new(), None);
|
||||
assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("test"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
assert_eq!(WsHandshakeError::NoWebsocketUpgrade, handshake(&req).err().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
assert_eq!(WsHandshakeError::NoConnectionUpgrade, handshake(&req).err().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
assert_eq!(WsHandshakeError::NoVersionHeader, handshake(&req).err().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
headers.insert(SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("5"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
assert_eq!(WsHandshakeError::UnsupportedVersion, handshake(&req).err().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
headers.insert(SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
assert_eq!(WsHandshakeError::BadWebsocketKey, handshake(&req).err().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::UPGRADE,
|
||||
header::HeaderValue::from_static("websocket"));
|
||||
headers.insert(header::CONNECTION,
|
||||
header::HeaderValue::from_static("upgrade"));
|
||||
headers.insert(SEC_WEBSOCKET_VERSION,
|
||||
header::HeaderValue::from_static("13"));
|
||||
headers.insert(SEC_WEBSOCKET_KEY,
|
||||
header::HeaderValue::from_static("13"));
|
||||
let req = HttpRequest::new(Method::GET, Uri::from_str("/").unwrap(),
|
||||
Version::HTTP_11, headers, None);
|
||||
assert_eq!(StatusCode::SWITCHING_PROTOCOLS,
|
||||
handshake(&req).unwrap().finish().unwrap().status());
|
||||
}
|
||||
}
|
349
src/ws/proto.rs
Normal file
349
src/ws/proto.rs
Normal file
@ -0,0 +1,349 @@
|
||||
use std::fmt;
|
||||
use std::convert::{Into, From};
|
||||
use sha1;
|
||||
|
||||
|
||||
use self::OpCode::*;
|
||||
/// Operation codes as part of rfc6455.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub(crate) 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 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+/";
|
||||
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#![allow(unused_imports, unused_variables, dead_code)]
|
||||
use super::*;
|
||||
|
||||
macro_rules! opcode_into {
|
||||
($from:expr => $opcode:pat) => {
|
||||
match OpCode::from($from) {
|
||||
e @ $opcode => (),
|
||||
e => panic!("{:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! opcode_from {
|
||||
($from:expr => $opcode:pat) => {
|
||||
let res: u8 = $from.into();
|
||||
match res {
|
||||
e @ $opcode => (),
|
||||
e => panic!("{:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_opcode() {
|
||||
opcode_into!(0 => OpCode::Continue);
|
||||
opcode_into!(1 => OpCode::Text);
|
||||
opcode_into!(2 => OpCode::Binary);
|
||||
opcode_into!(8 => OpCode::Close);
|
||||
opcode_into!(9 => OpCode::Ping);
|
||||
opcode_into!(10 => OpCode::Pong);
|
||||
opcode_into!(99 => OpCode::Bad);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_opcode() {
|
||||
opcode_from!(OpCode::Continue => 0);
|
||||
opcode_from!(OpCode::Text => 1);
|
||||
opcode_from!(OpCode::Binary => 2);
|
||||
opcode_from!(OpCode::Close => 8);
|
||||
opcode_from!(OpCode::Ping => 9);
|
||||
opcode_from!(OpCode::Pong => 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_from_opcode_debug() {
|
||||
opcode_from!(OpCode::Bad => 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_opcode_display() {
|
||||
assert_eq!(format!("{}", OpCode::Continue), "CONTINUE");
|
||||
assert_eq!(format!("{}", OpCode::Text), "TEXT");
|
||||
assert_eq!(format!("{}", OpCode::Binary), "BINARY");
|
||||
assert_eq!(format!("{}", OpCode::Close), "CLOSE");
|
||||
assert_eq!(format!("{}", OpCode::Ping), "PING");
|
||||
assert_eq!(format!("{}", OpCode::Pong), "PONG");
|
||||
assert_eq!(format!("{}", OpCode::Bad), "BAD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closecode_from_u16() {
|
||||
assert_eq!(CloseCode::from(1000u16), CloseCode::Normal);
|
||||
assert_eq!(CloseCode::from(1001u16), CloseCode::Away);
|
||||
assert_eq!(CloseCode::from(1002u16), CloseCode::Protocol);
|
||||
assert_eq!(CloseCode::from(1003u16), CloseCode::Unsupported);
|
||||
assert_eq!(CloseCode::from(1005u16), CloseCode::Status);
|
||||
assert_eq!(CloseCode::from(1006u16), CloseCode::Abnormal);
|
||||
assert_eq!(CloseCode::from(1007u16), CloseCode::Invalid);
|
||||
assert_eq!(CloseCode::from(1008u16), CloseCode::Policy);
|
||||
assert_eq!(CloseCode::from(1009u16), CloseCode::Size);
|
||||
assert_eq!(CloseCode::from(1010u16), CloseCode::Extension);
|
||||
assert_eq!(CloseCode::from(1011u16), CloseCode::Error);
|
||||
assert_eq!(CloseCode::from(1012u16), CloseCode::Restart);
|
||||
assert_eq!(CloseCode::from(1013u16), CloseCode::Again);
|
||||
assert_eq!(CloseCode::from(1015u16), CloseCode::Tls);
|
||||
assert_eq!(CloseCode::from(0u16), CloseCode::Empty);
|
||||
assert_eq!(CloseCode::from(2000u16), CloseCode::Other(2000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closecode_into_u16() {
|
||||
assert_eq!(1000u16, Into::<u16>::into(CloseCode::Normal));
|
||||
assert_eq!(1001u16, Into::<u16>::into(CloseCode::Away));
|
||||
assert_eq!(1002u16, Into::<u16>::into(CloseCode::Protocol));
|
||||
assert_eq!(1003u16, Into::<u16>::into(CloseCode::Unsupported));
|
||||
assert_eq!(1005u16, Into::<u16>::into(CloseCode::Status));
|
||||
assert_eq!(1006u16, Into::<u16>::into(CloseCode::Abnormal));
|
||||
assert_eq!(1007u16, Into::<u16>::into(CloseCode::Invalid));
|
||||
assert_eq!(1008u16, Into::<u16>::into(CloseCode::Policy));
|
||||
assert_eq!(1009u16, Into::<u16>::into(CloseCode::Size));
|
||||
assert_eq!(1010u16, Into::<u16>::into(CloseCode::Extension));
|
||||
assert_eq!(1011u16, Into::<u16>::into(CloseCode::Error));
|
||||
assert_eq!(1012u16, Into::<u16>::into(CloseCode::Restart));
|
||||
assert_eq!(1013u16, Into::<u16>::into(CloseCode::Again));
|
||||
assert_eq!(1015u16, Into::<u16>::into(CloseCode::Tls));
|
||||
assert_eq!(0u16, Into::<u16>::into(CloseCode::Empty));
|
||||
assert_eq!(2000u16, Into::<u16>::into(CloseCode::Other(2000)));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user