1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-28 10:02:38 +01:00
actix-web/src/ws/client.rs

564 lines
17 KiB
Rust
Raw Normal View History

2018-01-28 07:03:03 +01:00
//! Http client request
use std::cell::UnsafeCell;
2018-04-14 01:02:01 +02:00
use std::rc::Rc;
2018-03-09 22:12:14 +01:00
use std::time::Duration;
2018-04-14 01:02:01 +02:00
use std::{fmt, io, str};
2018-01-28 07:03:03 +01:00
use base64;
use bytes::Bytes;
use cookie::Cookie;
2018-04-14 01:02:01 +02:00
use futures::unsync::mpsc::{unbounded, UnboundedSender};
use futures::{Async, Future, Poll, Stream};
2018-01-28 07:03:03 +01:00
use http::header::{self, HeaderName, HeaderValue};
2018-04-14 01:02:01 +02:00
use http::{Error as HttpError, HttpTryFrom, StatusCode};
use rand;
2018-01-28 07:03:03 +01:00
use sha1::Sha1;
2018-01-30 08:01:20 +01:00
use actix::prelude::*;
2018-04-14 01:02:01 +02:00
use body::{Binary, Body};
use error::{Error, UrlParseError};
use header::IntoHeaderValue;
use httpmessage::HttpMessage;
2018-04-14 01:02:01 +02:00
use payload::PayloadHelper;
2018-01-28 07:03:03 +01:00
2018-04-14 01:02:01 +02:00
use client::{ClientConnector, ClientRequest, ClientRequestBuilder, ClientResponse,
HttpResponseParserError, SendRequest, SendRequestError};
2018-01-28 07:03:03 +01:00
2018-02-10 09:05:20 +01:00
use super::frame::Frame;
use super::proto::{CloseReason, OpCode};
2018-04-14 01:02:01 +02:00
use super::{Message, ProtocolError};
2018-01-30 00:45:37 +01:00
2018-01-31 18:28:53 +01:00
/// Websocket client error
2018-01-28 07:03:03 +01:00
#[derive(Fail, Debug)]
pub enum ClientError {
2018-04-14 01:02:01 +02:00
#[fail(display = "Invalid url")]
2018-01-28 07:03:03 +01:00
InvalidUrl,
2018-04-14 01:02:01 +02:00
#[fail(display = "Invalid response status")]
2018-02-28 00:38:57 +01:00
InvalidResponseStatus(StatusCode),
2018-04-14 01:02:01 +02:00
#[fail(display = "Invalid upgrade header")]
2018-01-28 07:03:03 +01:00
InvalidUpgradeHeader,
2018-04-14 01:02:01 +02:00
#[fail(display = "Invalid connection header")]
2018-02-28 00:38:57 +01:00
InvalidConnectionHeader(HeaderValue),
2018-04-14 01:02:01 +02:00
#[fail(display = "Missing CONNECTION header")]
2018-02-28 00:38:57 +01:00
MissingConnectionHeader,
2018-04-14 01:02:01 +02:00
#[fail(display = "Missing SEC-WEBSOCKET-ACCEPT header")]
2018-02-28 00:38:57 +01:00
MissingWebSocketAcceptHeader,
2018-04-14 01:02:01 +02:00
#[fail(display = "Invalid challenge response")]
2018-02-28 00:38:57 +01:00
InvalidChallengeResponse(String, HeaderValue),
2018-04-14 01:02:01 +02:00
#[fail(display = "Http parsing error")]
Http(Error),
2018-04-14 01:02:01 +02:00
#[fail(display = "Url parsing error")]
2018-01-28 07:03:03 +01:00
Url(UrlParseError),
2018-04-14 01:02:01 +02:00
#[fail(display = "Response parsing error")]
2018-01-28 07:03:03 +01:00
ResponseParseError(HttpResponseParserError),
2018-04-14 01:02:01 +02:00
#[fail(display = "{}", _0)]
2018-02-26 22:58:23 +01:00
SendRequest(SendRequestError),
2018-04-14 01:02:01 +02:00
#[fail(display = "{}", _0)]
Protocol(#[cause] ProtocolError),
2018-04-14 01:02:01 +02:00
#[fail(display = "{}", _0)]
2018-01-28 07:03:03 +01:00
Io(io::Error),
2018-04-14 01:02:01 +02:00
#[fail(display = "Disconnected")]
2018-01-28 07:03:03 +01:00
Disconnected,
}
impl From<Error> for ClientError {
fn from(err: Error) -> ClientError {
ClientError::Http(err)
2018-01-28 07:03:03 +01:00
}
}
impl From<UrlParseError> for ClientError {
fn from(err: UrlParseError) -> ClientError {
ClientError::Url(err)
2018-01-28 07:03:03 +01:00
}
}
impl From<SendRequestError> for ClientError {
fn from(err: SendRequestError) -> ClientError {
ClientError::SendRequest(err)
2018-02-26 22:58:23 +01:00
}
}
impl From<ProtocolError> for ClientError {
fn from(err: ProtocolError) -> ClientError {
ClientError::Protocol(err)
2018-01-28 07:03:03 +01:00
}
}
impl From<io::Error> for ClientError {
fn from(err: io::Error) -> ClientError {
ClientError::Io(err)
2018-01-28 07:03:03 +01:00
}
}
impl From<HttpResponseParserError> for ClientError {
fn from(err: HttpResponseParserError) -> ClientError {
ClientError::ResponseParseError(err)
2018-01-28 07:03:03 +01:00
}
}
/// `WebSocket` client
2018-01-31 00:26:58 +01:00
///
/// Example of `WebSocket` client usage is available in
2018-01-31 00:26:58 +01:00
/// [websocket example](
/// https://github.com/actix/actix-web/blob/master/examples/websocket/src/client.rs#L24)
pub struct Client {
request: ClientRequestBuilder,
err: Option<ClientError>,
2018-01-28 07:03:03 +01:00
http_err: Option<HttpError>,
origin: Option<HeaderValue>,
protocols: Option<String>,
2018-02-13 01:08:04 +01:00
conn: Addr<Unsync, ClientConnector>,
2018-02-27 19:09:24 +01:00
max_size: usize,
2018-01-28 07:03:03 +01:00
}
impl Client {
2018-01-30 20:17:17 +01:00
/// Create new websocket connection
pub fn new<S: AsRef<str>>(uri: S) -> Client {
Client::with_connector(uri, ClientConnector::from_registry())
2018-01-30 20:17:17 +01:00
}
/// Create new websocket connection with custom `ClientConnector`
2018-04-14 01:02:01 +02:00
pub fn with_connector<S: AsRef<str>>(
2018-04-29 07:55:47 +02:00
uri: S, conn: Addr<Unsync, ClientConnector>,
2018-04-14 01:02:01 +02:00
) -> Client {
let mut cl = Client {
request: ClientRequest::build(),
2018-01-28 07:03:03 +01:00
err: None,
http_err: None,
origin: None,
2018-01-30 20:17:17 +01:00
protocols: None,
2018-02-27 19:09:24 +01:00
max_size: 65_536,
2018-02-26 23:33:56 +01:00
conn,
2018-01-30 20:17:17 +01:00
};
cl.request.uri(uri.as_ref());
2018-01-28 07:03:03 +01:00
cl
}
2018-01-30 20:17:17 +01:00
/// Set supported websocket protocols
2018-02-24 05:36:50 +01:00
pub fn protocols<U, V>(mut self, protos: U) -> Self
2018-04-14 01:02:01 +02:00
where
U: IntoIterator<Item = V> + 'static,
V: AsRef<str>,
2018-01-28 07:03:03 +01:00
{
2018-04-29 18:09:08 +02:00
let mut protos = protos
.into_iter()
.fold(String::new(), |acc, s| acc + s.as_ref() + ",");
2018-01-28 07:03:03 +01:00
protos.pop();
self.protocols = Some(protos);
self
}
2018-01-30 20:17:17 +01:00
/// Set cookie for handshake request
pub fn cookie(mut self, cookie: Cookie) -> Self {
self.request.cookie(cookie);
2018-01-28 07:03:03 +01:00
self
}
/// Set request Origin
2018-02-24 05:36:50 +01:00
pub fn origin<V>(mut self, origin: V) -> Self
2018-04-14 01:02:01 +02:00
where
HeaderValue: HttpTryFrom<V>,
2018-01-28 07:03:03 +01:00
{
match HeaderValue::try_from(origin) {
Ok(value) => self.origin = Some(value),
Err(e) => self.http_err = Some(e.into()),
}
self
}
2018-02-27 19:09:24 +01:00
/// Set max frame size
///
/// By default max size is set to 64kb
pub fn max_frame_size(mut self, size: usize) -> Self {
self.max_size = size;
self
}
2018-03-09 19:09:13 +01:00
/// Set write buffer capacity
///
/// Default buffer capacity is 32kb
pub fn write_buffer_capacity(mut self, cap: usize) -> Self {
self.request.write_buffer_capacity(cap);
self
}
2018-01-30 20:17:17 +01:00
/// Set request header
2018-02-24 05:36:50 +01:00
pub fn header<K, V>(mut self, key: K, value: V) -> Self
2018-04-14 01:02:01 +02:00
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
2018-01-28 07:03:03 +01:00
{
self.request.header(key, value);
2018-01-28 07:03:03 +01:00
self
}
2018-03-19 03:27:51 +01:00
/// Set websocket handshake timeout
///
/// Handshake timeout is a total time for successful handshake.
/// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.request.timeout(timeout);
self
}
2018-01-30 20:17:17 +01:00
/// Connect to websocket server and do ws handshake
pub fn connect(&mut self) -> ClientHandshake {
2018-01-28 07:03:03 +01:00
if let Some(e) = self.err.take() {
ClientHandshake::error(e)
2018-04-14 01:02:01 +02:00
} else if let Some(e) = self.http_err.take() {
ClientHandshake::error(Error::from(e).into())
} else {
// origin
if let Some(origin) = self.origin.take() {
self.request.set_header(header::ORIGIN, origin);
}
2018-01-28 07:03:03 +01:00
self.request.upgrade();
self.request.set_header(header::UPGRADE, "websocket");
self.request.set_header(header::CONNECTION, "upgrade");
2018-04-29 18:09:08 +02:00
self.request
.set_header(header::SEC_WEBSOCKET_VERSION, "13");
self.request.with_connector(self.conn.clone());
2018-01-28 07:03:03 +01:00
if let Some(protocols) = self.protocols.take() {
2018-04-14 01:02:01 +02:00
self.request
.set_header(header::SEC_WEBSOCKET_PROTOCOL, protocols.as_str());
}
let request = match self.request.finish() {
Ok(req) => req,
Err(err) => return ClientHandshake::error(err.into()),
};
if request.uri().host().is_none() {
2018-04-14 01:02:01 +02:00
return ClientHandshake::error(ClientError::InvalidUrl);
}
if let Some(scheme) = request.uri().scheme_part() {
2018-04-14 01:02:01 +02:00
if scheme != "http" && scheme != "https" && scheme != "ws"
&& scheme != "wss"
{
return ClientHandshake::error(ClientError::InvalidUrl);
}
} else {
2018-04-14 01:02:01 +02:00
return ClientHandshake::error(ClientError::InvalidUrl);
}
2018-01-28 07:03:03 +01:00
// start handshake
ClientHandshake::new(request, self.max_size)
}
2018-01-28 07:03:03 +01:00
}
}
struct Inner {
2018-02-26 22:58:23 +01:00
tx: UnboundedSender<Bytes>,
rx: PayloadHelper<ClientResponse>,
2018-01-28 07:03:03 +01:00
closed: bool,
}
/// Future that implementes client websocket handshake process.
///
/// It resolves to a pair of `ClientReadr` and `ClientWriter` that
/// can be used for reading and writing websocket frames.
pub struct ClientHandshake {
2018-02-26 22:58:23 +01:00
request: Option<SendRequest>,
tx: Option<UnboundedSender<Bytes>>,
2018-01-28 07:03:03 +01:00
key: String,
error: Option<ClientError>,
2018-02-27 19:09:24 +01:00
max_size: usize,
2018-01-28 07:03:03 +01:00
}
impl ClientHandshake {
2018-04-14 01:02:01 +02:00
fn new(mut request: ClientRequest, max_size: usize) -> ClientHandshake {
2018-01-28 07:03:03 +01:00
// Generate a random key for the `Sec-WebSocket-Key` header.
// a base64-encoded (see Section 4 of [RFC4648]) value that,
// when decoded, is 16 bytes in length (RFC 6455)
let sec_key: [u8; 16] = rand::random();
let key = base64::encode(&sec_key);
2018-02-27 19:09:24 +01:00
request.headers_mut().insert(
2018-02-28 23:16:55 +01:00
header::SEC_WEBSOCKET_KEY,
2018-04-14 01:02:01 +02:00
HeaderValue::try_from(key.as_str()).unwrap(),
);
2018-02-27 19:09:24 +01:00
let (tx, rx) = unbounded();
2018-04-14 01:02:01 +02:00
request.set_body(Body::Streaming(Box::new(rx.map_err(|_| {
io::Error::new(io::ErrorKind::Other, "disconnected").into()
}))));
2018-02-27 19:09:24 +01:00
ClientHandshake {
2018-02-27 19:09:24 +01:00
key,
max_size,
request: Some(request.send()),
2018-02-27 19:09:24 +01:00
tx: Some(tx),
error: None,
}
}
fn error(err: ClientError) -> ClientHandshake {
ClientHandshake {
2018-02-27 19:09:24 +01:00
key: String::new(),
request: None,
tx: None,
error: Some(err),
2018-03-09 22:12:14 +01:00
max_size: 0,
}
}
/// Set handshake timeout
///
/// Handshake timeout is a total time before handshake should be completed.
/// Default value is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
if let Some(request) = self.request.take() {
self.request = Some(request.timeout(timeout));
2018-01-28 07:03:03 +01:00
}
2018-03-09 22:12:14 +01:00
self
}
/// Set connection timeout
///
/// Connection timeout includes resolving hostname and actual connection to
/// the host.
/// Default value is 1 second.
pub fn conn_timeout(mut self, timeout: Duration) -> Self {
if let Some(request) = self.request.take() {
self.request = Some(request.conn_timeout(timeout));
}
self
2018-01-28 07:03:03 +01:00
}
}
impl Future for ClientHandshake {
type Item = (ClientReader, ClientWriter);
type Error = ClientError;
2018-01-28 07:03:03 +01:00
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(err) = self.error.take() {
2018-04-14 01:02:01 +02:00
return Err(err);
}
2018-02-26 22:58:23 +01:00
let resp = match self.request.as_mut().unwrap().poll()? {
Async::Ready(response) => {
self.request.take();
response
2018-04-14 01:02:01 +02:00
}
Async::NotReady => return Ok(Async::NotReady),
2018-02-26 22:58:23 +01:00
};
2018-01-28 07:03:03 +01:00
2018-02-26 22:58:23 +01:00
// verify response
if resp.status() != StatusCode::SWITCHING_PROTOCOLS {
2018-04-14 01:02:01 +02:00
return Err(ClientError::InvalidResponseStatus(resp.status()));
2018-01-28 07:03:03 +01:00
}
2018-02-26 22:58:23 +01:00
// Check for "UPGRADE" to websocket header
let has_hdr = if let Some(hdr) = resp.headers().get(header::UPGRADE) {
if let Ok(s) = hdr.to_str() {
s.to_lowercase().contains("websocket")
} else {
false
}
} else {
false
};
if !has_hdr {
2018-02-28 00:38:57 +01:00
trace!("Invalid upgrade header");
2018-04-14 01:02:01 +02:00
return Err(ClientError::InvalidUpgradeHeader);
2018-02-26 22:58:23 +01:00
}
// Check for "CONNECTION" header
2018-02-28 00:38:57 +01:00
if let Some(conn) = resp.headers().get(header::CONNECTION) {
2018-02-26 22:58:23 +01:00
if let Ok(s) = conn.to_str() {
2018-02-28 00:38:57 +01:00
if !s.to_lowercase().contains("upgrade") {
trace!("Invalid connection header: {}", s);
2018-04-14 01:02:01 +02:00
return Err(ClientError::InvalidConnectionHeader(conn.clone()));
2018-02-28 00:38:57 +01:00
}
} else {
trace!("Invalid connection header: {:?}", conn);
2018-04-14 01:02:01 +02:00
return Err(ClientError::InvalidConnectionHeader(conn.clone()));
2018-02-28 00:38:57 +01:00
}
} else {
trace!("Missing connection header");
2018-04-14 01:02:01 +02:00
return Err(ClientError::MissingConnectionHeader);
2018-01-28 07:03:03 +01:00
}
2018-04-14 01:02:01 +02:00
if let Some(key) = resp.headers().get(header::SEC_WEBSOCKET_ACCEPT) {
2018-02-26 22:58:23 +01:00
// field is constructed by concatenating /key/
// with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455)
const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let mut sha1 = Sha1::new();
sha1.update(self.key.as_ref());
sha1.update(WS_GUID);
2018-02-28 00:38:57 +01:00
let encoded = base64::encode(&sha1.digest().bytes());
if key.as_bytes() != encoded.as_bytes() {
trace!(
"Invalid challenge response: expected: {} received: {:?}",
2018-04-14 01:02:01 +02:00
encoded,
key
);
2018-04-29 18:09:08 +02:00
return Err(ClientError::InvalidChallengeResponse(
encoded,
key.clone(),
));
2018-02-28 00:38:57 +01:00
}
2018-02-26 22:58:23 +01:00
} else {
2018-02-28 00:38:57 +01:00
trace!("Missing SEC-WEBSOCKET-ACCEPT header");
2018-04-14 01:02:01 +02:00
return Err(ClientError::MissingWebSocketAcceptHeader);
2018-02-26 22:58:23 +01:00
};
2018-01-28 07:03:03 +01:00
let inner = Inner {
2018-02-26 22:58:23 +01:00
tx: self.tx.take().unwrap(),
rx: PayloadHelper::new(resp),
closed: false,
};
2018-01-28 07:03:03 +01:00
2018-02-26 22:58:23 +01:00
let inner = Rc::new(UnsafeCell::new(inner));
2018-04-14 01:02:01 +02:00
Ok(Async::Ready((
ClientReader {
inner: Rc::clone(&inner),
max_size: self.max_size,
},
2018-04-29 18:09:08 +02:00
ClientWriter { inner },
2018-04-14 01:02:01 +02:00
)))
2018-01-28 07:03:03 +01:00
}
}
pub struct ClientReader {
inner: Rc<UnsafeCell<Inner>>,
2018-02-27 19:09:24 +01:00
max_size: usize,
2018-01-28 07:03:03 +01:00
}
impl fmt::Debug for ClientReader {
2018-01-31 00:13:33 +01:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ws::ClientReader()")
2018-01-31 00:13:33 +01:00
}
}
impl ClientReader {
2018-01-28 07:03:03 +01:00
#[inline]
fn as_mut(&mut self) -> &mut Inner {
2018-04-14 01:02:01 +02:00
unsafe { &mut *self.inner.get() }
2018-01-28 07:03:03 +01:00
}
}
impl Stream for ClientReader {
2018-01-28 07:03:03 +01:00
type Item = Message;
type Error = ProtocolError;
2018-01-28 07:03:03 +01:00
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
2018-02-27 19:09:24 +01:00
let max_size = self.max_size;
2018-01-28 07:03:03 +01:00
let inner = self.as_mut();
2018-02-26 22:58:23 +01:00
if inner.closed {
2018-04-14 01:02:01 +02:00
return Ok(Async::Ready(None));
2018-01-28 07:03:03 +01:00
}
// read
2018-02-27 19:09:24 +01:00
match Frame::parse(&mut inner.rx, false, max_size) {
2018-02-26 22:58:23 +01:00
Ok(Async::Ready(Some(frame))) => {
let (_finished, opcode, payload) = frame.unpack();
2018-02-27 19:09:24 +01:00
2018-01-28 07:03:03 +01:00
match opcode {
2018-03-19 21:12:36 +01:00
// continuation is not supported
OpCode::Continue => {
inner.closed = true;
Err(ProtocolError::NoContinuation)
2018-04-14 01:02:01 +02:00
}
2018-02-27 19:09:24 +01:00
OpCode::Bad => {
inner.closed = true;
Err(ProtocolError::BadOpCode)
2018-04-14 01:02:01 +02:00
}
2018-01-28 07:03:03 +01:00
OpCode::Close => {
inner.closed = true;
let close_reason = Frame::parse_close_payload(&payload);
Ok(Async::Ready(Some(Message::Close(close_reason))))
2018-04-14 01:02:01 +02:00
}
OpCode::Ping => Ok(Async::Ready(Some(Message::Ping(
String::from_utf8_lossy(payload.as_ref()).into(),
)))),
OpCode::Pong => Ok(Async::Ready(Some(Message::Pong(
String::from_utf8_lossy(payload.as_ref()).into(),
)))),
OpCode::Binary => Ok(Async::Ready(Some(Message::Binary(payload)))),
2018-01-28 07:03:03 +01:00
OpCode::Text => {
let tmp = Vec::from(payload.as_ref());
match String::from_utf8(tmp) {
2018-04-14 01:02:01 +02:00
Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))),
2018-02-27 19:09:24 +01:00
Err(_) => {
inner.closed = true;
Err(ProtocolError::BadEncoding)
2018-02-27 19:09:24 +01:00
}
2018-01-28 07:03:03 +01:00
}
}
}
}
2018-02-26 22:58:23 +01:00
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
Ok(Async::NotReady) => Ok(Async::NotReady),
2018-02-27 19:09:24 +01:00
Err(e) => {
inner.closed = true;
2018-02-27 19:09:24 +01:00
Err(e)
2018-01-28 07:03:03 +01:00
}
}
}
}
pub struct ClientWriter {
2018-04-14 01:02:01 +02:00
inner: Rc<UnsafeCell<Inner>>,
2018-01-28 07:03:03 +01:00
}
impl ClientWriter {
2018-01-28 07:03:03 +01:00
#[inline]
fn as_mut(&mut self) -> &mut Inner {
2018-04-14 01:02:01 +02:00
unsafe { &mut *self.inner.get() }
2018-01-28 07:03:03 +01:00
}
}
impl ClientWriter {
2018-01-28 07:03:03 +01:00
/// Write payload
#[inline]
2018-02-26 22:58:23 +01:00
fn write(&mut self, mut data: Binary) {
if !self.as_mut().closed {
2018-02-26 22:58:23 +01:00
let _ = self.as_mut().tx.unbounded_send(data.take());
2018-01-28 07:03:03 +01:00
} else {
warn!("Trying to write to disconnected response");
}
}
/// Send text frame
2018-02-10 05:43:14 +01:00
#[inline]
2018-03-04 19:18:42 +01:00
pub fn text<T: Into<Binary>>(&mut self, text: T) {
2018-02-20 07:48:27 +01:00
self.write(Frame::message(text.into(), OpCode::Text, true, true));
2018-01-28 07:03:03 +01:00
}
/// Send binary frame
2018-02-10 05:43:14 +01:00
#[inline]
2018-01-28 07:03:03 +01:00
pub fn binary<B: Into<Binary>>(&mut self, data: B) {
2018-02-20 07:48:27 +01:00
self.write(Frame::message(data, OpCode::Binary, true, true));
2018-01-28 07:03:03 +01:00
}
/// Send ping frame
2018-02-10 05:43:14 +01:00
#[inline]
2018-01-28 07:03:03 +01:00
pub fn ping(&mut self, message: &str) {
2018-04-29 18:09:08 +02:00
self.write(Frame::message(
Vec::from(message),
OpCode::Ping,
true,
true,
));
2018-01-28 07:03:03 +01:00
}
/// Send pong frame
2018-02-10 05:43:14 +01:00
#[inline]
2018-01-28 07:03:03 +01:00
pub fn pong(&mut self, message: &str) {
2018-04-29 18:09:08 +02:00
self.write(Frame::message(
Vec::from(message),
OpCode::Pong,
true,
true,
));
2018-01-28 07:03:03 +01:00
}
/// Send close frame
2018-02-10 05:43:14 +01:00
#[inline]
pub fn close(&mut self, reason: Option<CloseReason>) {
self.write(Frame::close(reason, true));
2018-01-28 07:03:03 +01:00
}
}