Skip to main content

actix_http/ws/
mod.rs

1//! WebSocket protocol implementation.
2//!
3//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a
4//! `WsStream` stream and then use `WsWriter` to communicate with the peer.
5
6use std::io;
7
8use derive_more::{Display, Error, From};
9use http::{header, Method, StatusCode};
10
11use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
12
13mod codec;
14mod dispatcher;
15mod frame;
16mod mask;
17mod proto;
18
19pub use self::{
20    codec::{Codec, Frame, Item, Message},
21    dispatcher::Dispatcher,
22    frame::Parser,
23    proto::{hash_key, CloseCode, CloseReason, OpCode},
24};
25
26/// WebSocket protocol errors.
27#[derive(Debug, Display, Error, From)]
28pub enum ProtocolError {
29    /// Received an unmasked frame from client.
30    #[display("received an unmasked frame from client")]
31    UnmaskedFrame,
32
33    /// Received a masked frame from server.
34    #[display("received a masked frame from server")]
35    MaskedFrame,
36
37    /// Encountered invalid opcode.
38    #[display("invalid opcode ({})", _0)]
39    InvalidOpcode(#[error(not(source))] u8),
40
41    // TODO(semver-major):
42    // /// Received a frame with non-zero reserved bits.
43    // #[display("received a frame with non-zero reserved bits")]
44    // InvalidReservedBits,
45    //
46    /// Invalid control frame length
47    #[display("invalid control frame length ({})", _0)]
48    InvalidLength(#[error(not(source))] usize),
49
50    /// Bad opcode.
51    #[display("bad opcode")]
52    BadOpCode,
53
54    /// A payload reached size limit.
55    #[display("payload reached size limit")]
56    Overflow,
57
58    /// Continuation has not started.
59    #[display("continuation has not started")]
60    ContinuationNotStarted,
61
62    /// Received new continuation but it is already started.
63    #[display("received new continuation but it has already started")]
64    ContinuationStarted,
65
66    /// Unknown continuation fragment.
67    #[display("unknown continuation fragment: {}", _0)]
68    ContinuationFragment(#[error(not(source))] OpCode),
69
70    /// I/O error.
71    #[display("I/O error: {}", _0)]
72    Io(io::Error),
73}
74
75/// WebSocket handshake errors
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)]
77pub enum HandshakeError {
78    /// Only get method is allowed.
79    #[display("method not allowed")]
80    GetMethodRequired,
81
82    /// Upgrade header if not set to WebSocket.
83    #[display("WebSocket upgrade is expected")]
84    NoWebsocketUpgrade,
85
86    /// Connection header is not set to upgrade.
87    #[display("connection upgrade is expected")]
88    NoConnectionUpgrade,
89
90    /// WebSocket version header is not set.
91    #[display("WebSocket version header is required")]
92    NoVersionHeader,
93
94    /// Unsupported WebSocket version.
95    #[display("unsupported WebSocket version")]
96    UnsupportedVersion,
97
98    /// WebSocket key is not set or wrong.
99    #[display("unknown WebSocket key")]
100    BadWebsocketKey,
101}
102
103impl From<HandshakeError> for Response<BoxBody> {
104    fn from(err: HandshakeError) -> Self {
105        match err {
106            HandshakeError::GetMethodRequired => {
107                let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
108                #[allow(clippy::declare_interior_mutable_const)]
109                const HV_GET: HeaderValue = HeaderValue::from_static("GET");
110                res.headers_mut().insert(header::ALLOW, HV_GET);
111                res
112            }
113
114            HandshakeError::NoWebsocketUpgrade => {
115                let mut res = Response::bad_request();
116                res.head_mut().reason = Some("No WebSocket Upgrade header found");
117                res
118            }
119
120            HandshakeError::NoConnectionUpgrade => {
121                let mut res = Response::bad_request();
122                res.head_mut().reason = Some("No Connection upgrade");
123                res
124            }
125
126            HandshakeError::NoVersionHeader => {
127                let mut res = Response::bad_request();
128                res.head_mut().reason = Some("WebSocket version header is required");
129                res
130            }
131
132            HandshakeError::UnsupportedVersion => {
133                let mut res = Response::bad_request();
134                res.head_mut().reason = Some("Unsupported WebSocket version");
135                res
136            }
137
138            HandshakeError::BadWebsocketKey => {
139                let mut res = Response::bad_request();
140                res.head_mut().reason = Some("Handshake error");
141                res
142            }
143        }
144    }
145}
146
147impl From<&HandshakeError> for Response<BoxBody> {
148    fn from(err: &HandshakeError) -> Self {
149        (*err).into()
150    }
151}
152
153/// Verify WebSocket handshake request and create handshake response.
154pub fn handshake(req: &RequestHead) -> Result<ResponseBuilder, HandshakeError> {
155    verify_handshake(req)?;
156    Ok(handshake_response(req))
157}
158
159/// Verify WebSocket handshake request.
160pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> {
161    // WebSocket accepts only GET
162    if req.method != Method::GET {
163        return Err(HandshakeError::GetMethodRequired);
164    }
165
166    // Check for "UPGRADE" to WebSocket header
167    let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
168        if let Ok(s) = hdr.to_str() {
169            s.to_ascii_lowercase().contains("websocket")
170        } else {
171            false
172        }
173    } else {
174        false
175    };
176    if !has_hdr {
177        return Err(HandshakeError::NoWebsocketUpgrade);
178    }
179
180    // Upgrade connection
181    if !req.upgrade() {
182        return Err(HandshakeError::NoConnectionUpgrade);
183    }
184
185    // check supported version
186    if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
187        return Err(HandshakeError::NoVersionHeader);
188    }
189    let supported_ver = {
190        if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
191            hdr == "13" || hdr == "8" || hdr == "7"
192        } else {
193            false
194        }
195    };
196    if !supported_ver {
197        return Err(HandshakeError::UnsupportedVersion);
198    }
199
200    // check client handshake for validity
201    if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) {
202        return Err(HandshakeError::BadWebsocketKey);
203    }
204    Ok(())
205}
206
207/// Create WebSocket handshake response.
208///
209/// This function returns handshake `Response`, ready to send to peer.
210pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
211    let key = {
212        let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
213        proto::hash_key(key.as_ref())
214    };
215
216    Response::build(StatusCode::SWITCHING_PROTOCOLS)
217        .upgrade("websocket")
218        .insert_header((
219            header::SEC_WEBSOCKET_ACCEPT,
220            // key is known to be header value safe ascii
221            HeaderValue::from_bytes(&key).unwrap(),
222        ))
223        .take()
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229    use crate::{header, test::TestRequest};
230
231    #[test]
232    fn test_handshake() {
233        let req = TestRequest::default().method(Method::POST).finish();
234        assert_eq!(
235            HandshakeError::GetMethodRequired,
236            verify_handshake(req.head()).unwrap_err(),
237        );
238
239        let req = TestRequest::default().finish();
240        assert_eq!(
241            HandshakeError::NoWebsocketUpgrade,
242            verify_handshake(req.head()).unwrap_err(),
243        );
244
245        let req = TestRequest::default()
246            .insert_header((header::UPGRADE, header::HeaderValue::from_static("test")))
247            .finish();
248        assert_eq!(
249            HandshakeError::NoWebsocketUpgrade,
250            verify_handshake(req.head()).unwrap_err(),
251        );
252
253        let req = TestRequest::default()
254            .insert_header((
255                header::UPGRADE,
256                header::HeaderValue::from_static("websocket"),
257            ))
258            .finish();
259        assert_eq!(
260            HandshakeError::NoConnectionUpgrade,
261            verify_handshake(req.head()).unwrap_err(),
262        );
263
264        let req = TestRequest::default()
265            .insert_header((
266                header::UPGRADE,
267                header::HeaderValue::from_static("websocket"),
268            ))
269            .insert_header((
270                header::CONNECTION,
271                header::HeaderValue::from_static("upgrade"),
272            ))
273            .finish();
274        assert_eq!(
275            HandshakeError::NoVersionHeader,
276            verify_handshake(req.head()).unwrap_err(),
277        );
278
279        let req = TestRequest::default()
280            .insert_header((
281                header::UPGRADE,
282                header::HeaderValue::from_static("websocket"),
283            ))
284            .insert_header((
285                header::CONNECTION,
286                header::HeaderValue::from_static("upgrade"),
287            ))
288            .insert_header((
289                header::SEC_WEBSOCKET_VERSION,
290                header::HeaderValue::from_static("5"),
291            ))
292            .finish();
293        assert_eq!(
294            HandshakeError::UnsupportedVersion,
295            verify_handshake(req.head()).unwrap_err(),
296        );
297
298        let req = TestRequest::default()
299            .insert_header((
300                header::UPGRADE,
301                header::HeaderValue::from_static("websocket"),
302            ))
303            .insert_header((
304                header::CONNECTION,
305                header::HeaderValue::from_static("upgrade"),
306            ))
307            .insert_header((
308                header::SEC_WEBSOCKET_VERSION,
309                header::HeaderValue::from_static("13"),
310            ))
311            .finish();
312        assert_eq!(
313            HandshakeError::BadWebsocketKey,
314            verify_handshake(req.head()).unwrap_err(),
315        );
316
317        let req = TestRequest::default()
318            .insert_header((
319                header::UPGRADE,
320                header::HeaderValue::from_static("websocket"),
321            ))
322            .insert_header((
323                header::CONNECTION,
324                header::HeaderValue::from_static("upgrade"),
325            ))
326            .insert_header((
327                header::SEC_WEBSOCKET_VERSION,
328                header::HeaderValue::from_static("13"),
329            ))
330            .insert_header((
331                header::SEC_WEBSOCKET_KEY,
332                header::HeaderValue::from_static("13"),
333            ))
334            .finish();
335        assert_eq!(
336            StatusCode::SWITCHING_PROTOCOLS,
337            handshake_response(req.head()).finish().status()
338        );
339    }
340
341    #[test]
342    fn test_ws_error_http_response() {
343        let resp: Response<BoxBody> = HandshakeError::GetMethodRequired.into();
344        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
345        let resp: Response<BoxBody> = HandshakeError::NoWebsocketUpgrade.into();
346        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
347        let resp: Response<BoxBody> = HandshakeError::NoConnectionUpgrade.into();
348        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
349        let resp: Response<BoxBody> = HandshakeError::NoVersionHeader.into();
350        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
351        let resp: Response<BoxBody> = HandshakeError::UnsupportedVersion.into();
352        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
353        let resp: Response<BoxBody> = HandshakeError::BadWebsocketKey.into();
354        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
355    }
356}