trillium_client/
websocket.rs1use crate::{Conn, WebSocketConfig, WebSocketConn};
4use std::{
5 borrow::Cow,
6 error::Error,
7 fmt::{self, Display},
8 ops::{Deref, DerefMut},
9};
10use trillium_http::{
11 KnownHeaderName::{SecWebsocketAccept, SecWebsocketKey, SecWebsocketVersion},
12 Status, Upgrade, Version,
13};
14pub use trillium_websockets::Message;
15use trillium_websockets::{Role, websocket_accept_hash};
16
17impl Conn {
18 pub async fn into_websocket(self) -> Result<WebSocketConn, WebSocketUpgradeError> {
32 self.upgrade_to_websocket(None).await
33 }
34
35 pub async fn into_websocket_with_config(
37 self,
38 config: WebSocketConfig,
39 ) -> Result<WebSocketConn, WebSocketUpgradeError> {
40 self.upgrade_to_websocket(Some(config)).await
41 }
42
43 async fn upgrade_to_websocket(
44 mut self,
45 config: Option<WebSocketConfig>,
46 ) -> Result<WebSocketConn, WebSocketUpgradeError> {
47 if self.status().is_some() {
48 return Err(WebSocketUpgradeError::new(self, ErrorKind::AlreadyExecuted));
49 }
50
51 self.protocol = Some(Cow::Borrowed("websocket"));
55 self.request_headers_mut()
56 .try_insert(SecWebsocketVersion, "13");
57
58 if let Err(e) = (&mut self).await {
59 let kind = match e {
60 trillium_http::Error::ExtendedConnectUnsupported => {
61 ErrorKind::ExtendedConnectUnsupported
62 }
63 other => other.into(),
64 };
65 return Err(WebSocketUpgradeError::new(self, kind));
66 }
67
68 let status = self.status().expect("Response did not include status");
69 match self.http_version() {
70 Version::Http2 | Version::Http3 => {
71 if status != Status::Ok {
72 return Err(WebSocketUpgradeError::new(self, ErrorKind::Status(status)));
73 }
74 }
75 _ => {
76 if status != Status::SwitchingProtocols {
77 return Err(WebSocketUpgradeError::new(self, ErrorKind::Status(status)));
78 }
79 let key = self
80 .request_headers()
81 .get_str(SecWebsocketKey)
82 .expect("h1 websocket request did not include Sec-WebSocket-Key");
83 let accept_key = websocket_accept_hash(key);
84 if self.response_headers().get_str(SecWebsocketAccept) != Some(&accept_key) {
85 return Err(WebSocketUpgradeError::new(self, ErrorKind::InvalidAccept));
86 }
87 }
88 }
89
90 let peer_ip = self.peer_addr().map(|addr| addr.ip());
91 let mut conn = WebSocketConn::new(Upgrade::from(self), config, Role::Client).await;
92 conn.set_peer_ip(peer_ip);
93 Ok(conn)
94 }
95}
96
97#[derive(thiserror::Error, Debug)]
99#[non_exhaustive]
100pub enum ErrorKind {
101 #[error(transparent)]
103 Http(#[from] trillium_http::Error),
104
105 #[error("Unexpected response status {0} for websocket upgrade")]
108 Status(Status),
109
110 #[error("Response Sec-WebSocket-Accept was missing or invalid")]
112 InvalidAccept,
113
114 #[error(
118 "Conn::into_websocket called after execution — build the conn and await into_websocket \
119 instead of awaiting the conn separately"
120 )]
121 AlreadyExecuted,
122
123 #[error("peer does not support extended CONNECT")]
128 ExtendedConnectUnsupported,
129}
130
131#[derive(Debug)]
136pub struct WebSocketUpgradeError {
137 pub kind: ErrorKind,
139 conn: Box<Conn>,
140}
141
142impl WebSocketUpgradeError {
143 fn new(conn: Conn, kind: ErrorKind) -> Self {
144 let conn = Box::new(conn);
145 Self { conn, kind }
146 }
147}
148
149impl From<WebSocketUpgradeError> for Conn {
150 fn from(value: WebSocketUpgradeError) -> Self {
151 *value.conn
152 }
153}
154
155impl Deref for WebSocketUpgradeError {
156 type Target = Conn;
157
158 fn deref(&self) -> &Self::Target {
159 &self.conn
160 }
161}
162impl DerefMut for WebSocketUpgradeError {
163 fn deref_mut(&mut self) -> &mut Self::Target {
164 &mut self.conn
165 }
166}
167
168impl Error for WebSocketUpgradeError {}
169
170impl Display for WebSocketUpgradeError {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 self.kind.fmt(f)
173 }
174}