1use crate::{BoxFuture, Gateway, HttpException, Injectable, Result};
2use bytes::Bytes;
3use std::{
4 collections::BTreeMap,
5 fmt,
6 net::SocketAddr,
7 sync::{
8 Arc,
9 atomic::{AtomicBool, Ordering},
10 },
11};
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum WebSocketCloseCode {
15 Normal,
16 GoingAway,
17 Protocol,
18 Unsupported,
19 InvalidData,
20 Policy,
21 MessageTooBig,
22 Internal,
23 Restart,
24 TryAgainLater,
25 MandatoryExtension,
26 BadGateway,
27 Other(u16),
28}
29
30impl WebSocketCloseCode {
31 pub fn as_u16(self) -> u16 {
32 match self {
33 Self::Normal => 1000,
34 Self::GoingAway => 1001,
35 Self::Protocol => 1002,
36 Self::Unsupported => 1003,
37 Self::InvalidData => 1007,
38 Self::Policy => 1008,
39 Self::MessageTooBig => 1009,
40 Self::Internal => 1011,
41 Self::Restart => 1012,
42 Self::TryAgainLater => 1013,
43 Self::MandatoryExtension => 1010,
44 Self::BadGateway => 1014,
45 Self::Other(code) => code,
46 }
47 }
48 pub fn from_u16(code: u16) -> Option<Self> {
49 Some(match code {
50 1000 => Self::Normal,
51 1001 => Self::GoingAway,
52 1002 => Self::Protocol,
53 1003 => Self::Unsupported,
54 1007 => Self::InvalidData,
55 1008 => Self::Policy,
56 1009 => Self::MessageTooBig,
57 1010 => Self::MandatoryExtension,
58 1011 => Self::Internal,
59 1012 => Self::Restart,
60 1013 => Self::TryAgainLater,
61 1014 => Self::BadGateway,
62 3000..=4999 => Self::Other(code),
63 _ => return None,
64 })
65 }
66 pub fn is_valid(self) -> bool {
67 Self::from_u16(self.as_u16()).is_some()
68 }
69 pub fn is_server_sendable(self) -> bool {
70 self.is_valid() && self != Self::MandatoryExtension
71 }
72}
73
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct WebSocketCloseFrame {
76 pub code: WebSocketCloseCode,
77 pub reason: String,
78}
79impl WebSocketCloseFrame {
80 pub fn new(code: WebSocketCloseCode, reason: impl Into<String>) -> Self {
81 Self {
82 code,
83 reason: reason.into(),
84 }
85 }
86}
87
88#[derive(Clone, Debug)]
89pub struct WebSocketRequest {
90 path: String,
91 query: String,
92 peer_addr: Option<SocketAddr>,
93 headers: BTreeMap<String, String>,
94}
95impl WebSocketRequest {
96 pub fn new(
97 path: impl Into<String>,
98 query: impl Into<String>,
99 peer_addr: Option<SocketAddr>,
100 headers: BTreeMap<String, String>,
101 ) -> Self {
102 Self {
103 path: path.into(),
104 query: query.into(),
105 peer_addr,
106 headers,
107 }
108 }
109 pub fn path(&self) -> &str {
110 &self.path
111 }
112 pub fn query_string(&self) -> &str {
113 &self.query
114 }
115 pub fn peer_addr(&self) -> Option<SocketAddr> {
116 self.peer_addr
117 }
118 pub fn header(&self, name: &str) -> Option<&str> {
119 self.headers
120 .get(&name.to_ascii_lowercase())
121 .map(String::as_str)
122 }
123 pub fn headers(&self) -> &BTreeMap<String, String> {
124 &self.headers
125 }
126}
127
128#[derive(Clone, Debug)]
129pub struct WebSocketError {
130 message: String,
131}
132impl WebSocketError {
133 pub fn new(message: impl Into<String>) -> Self {
134 Self {
135 message: message.into(),
136 }
137 }
138}
139impl fmt::Display for WebSocketError {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.write_str(&self.message)
142 }
143}
144impl std::error::Error for WebSocketError {}
145
146#[doc(hidden)]
147pub trait WebSocketTransport: Send + Sync {
148 fn send_text(&self, text: String) -> BoxFuture<'_, Result<()>>;
149 fn send_binary(&self, data: Bytes) -> BoxFuture<'_, Result<()>>;
150 fn ping(&self, data: Bytes) -> BoxFuture<'_, Result<()>>;
151 fn pong(&self, data: Bytes) -> BoxFuture<'_, Result<()>>;
152 fn close(&self, frame: Option<WebSocketCloseFrame>) -> BoxFuture<'_, Result<()>>;
153}
154
155#[derive(Clone)]
156pub struct WebSocketSession {
157 id: String,
158 open: Arc<AtomicBool>,
159 transport: Arc<dyn WebSocketTransport>,
160 close_frame: Arc<std::sync::Mutex<Option<WebSocketCloseFrame>>>,
161}
162impl WebSocketSession {
163 #[doc(hidden)]
164 pub fn new(
165 id: impl Into<String>,
166 open: Arc<AtomicBool>,
167 transport: Arc<dyn WebSocketTransport>,
168 ) -> Self {
169 Self {
170 id: id.into(),
171 open,
172 transport,
173 close_frame: Arc::new(std::sync::Mutex::new(None)),
174 }
175 }
176 pub fn id(&self) -> &str {
177 &self.id
178 }
179 pub fn is_open(&self) -> bool {
180 self.open.load(Ordering::Acquire)
181 }
182 pub async fn send_text(&self, text: impl Into<String>) -> Result<()> {
183 self.ensure_open()?;
184 self.transport.send_text(text.into()).await
185 }
186 pub async fn send_binary(&self, data: impl Into<Bytes>) -> Result<()> {
187 self.ensure_open()?;
188 self.transport.send_binary(data.into()).await
189 }
190 pub async fn ping(&self, data: impl Into<Bytes>) -> Result<()> {
191 self.ensure_open()?;
192 self.transport.ping(data.into()).await
193 }
194 pub async fn pong(&self, data: impl Into<Bytes>) -> Result<()> {
195 self.ensure_open()?;
196 self.transport.pong(data.into()).await
197 }
198 pub async fn close(&self, frame: Option<WebSocketCloseFrame>) -> Result<()> {
199 *self
200 .close_frame
201 .lock()
202 .expect("websocket close state lock poisoned") = frame.clone();
203 self.transport.close(frame).await
204 }
205 #[doc(hidden)]
206 pub fn take_local_close_frame(&self) -> Option<WebSocketCloseFrame> {
207 self.close_frame
208 .lock()
209 .expect("websocket close state lock poisoned")
210 .take()
211 }
212 fn ensure_open(&self) -> Result<()> {
213 if self.is_open() {
214 Ok(())
215 } else {
216 Err(HttpException::new(
217 http::StatusCode::INTERNAL_SERVER_ERROR,
218 "Internal Server Error",
219 "websocket session is closed",
220 ))
221 }
222 }
223}
224
225pub trait WebSocketGateway: Injectable {
226 fn on_connect(
227 &self,
228 _session: Arc<WebSocketSession>,
229 _request: WebSocketRequest,
230 ) -> BoxFuture<'_, Result<()>> {
231 Box::pin(async { Ok(()) })
232 }
233 fn on_text(&self, _session: Arc<WebSocketSession>, _text: String) -> BoxFuture<'_, Result<()>> {
234 Box::pin(async { Ok(()) })
235 }
236 fn on_binary(
237 &self,
238 _session: Arc<WebSocketSession>,
239 _data: Bytes,
240 ) -> BoxFuture<'_, Result<()>> {
241 Box::pin(async { Ok(()) })
242 }
243 fn on_error(
244 &self,
245 _session: Arc<WebSocketSession>,
246 _error: WebSocketError,
247 ) -> BoxFuture<'_, ()> {
248 Box::pin(async {})
249 }
250 fn on_close(
251 &self,
252 _session: Arc<WebSocketSession>,
253 _frame: Option<WebSocketCloseFrame>,
254 ) -> BoxFuture<'_, ()> {
255 Box::pin(async {})
256 }
257}
258
259pub trait RegisteredWebSocketGateway: WebSocketGateway + Gateway {}
264
265impl<T: WebSocketGateway + Gateway> RegisteredWebSocketGateway for T {}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn close_codes_round_trip_without_corruption() {
273 for code in [1000, 1012, 1013, 3000, 3999, 4000, 4999] {
274 assert_eq!(WebSocketCloseCode::from_u16(code).unwrap().as_u16(), code);
275 }
276 }
277
278 #[test]
279 fn prohibited_wire_close_codes_are_rejected() {
280 for code in [0, 999, 1004, 1005, 1006, 1015, 2000, 2999, 5000] {
281 assert_eq!(WebSocketCloseCode::from_u16(code), None);
282 }
283 assert!(!WebSocketCloseCode::Other(1005).is_valid());
284 assert!(!WebSocketCloseCode::Other(5000).is_valid());
285 assert!(WebSocketCloseCode::MandatoryExtension.is_valid());
286 assert!(!WebSocketCloseCode::MandatoryExtension.is_server_sendable());
287 }
288}