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.into_websocket_with_config(WebSocketConfig::default())
33 .await
34 }
35
36 pub async fn into_websocket_with_config(
38 mut self,
39 config: WebSocketConfig,
40 ) -> Result<WebSocketConn, WebSocketUpgradeError> {
41 if self.status().is_some() {
42 return Err(WebSocketUpgradeError::new(self, ErrorKind::AlreadyExecuted));
43 }
44
45 self.protocol = Some(Cow::Borrowed("websocket"));
49 self.request_headers_mut()
50 .try_insert(SecWebsocketVersion, "13");
51
52 if let Err(e) = (&mut self).await {
53 let kind = match e {
54 trillium_http::Error::ExtendedConnectUnsupported => {
55 ErrorKind::ExtendedConnectUnsupported
56 }
57 other => other.into(),
58 };
59 return Err(WebSocketUpgradeError::new(self, kind));
60 }
61
62 let status = self.status().expect("Response did not include status");
63 match self.http_version() {
64 Version::Http2 | Version::Http3 => {
65 if status != Status::Ok {
66 return Err(WebSocketUpgradeError::new(self, ErrorKind::Status(status)));
67 }
68 }
69 _ => {
70 if status != Status::SwitchingProtocols {
71 return Err(WebSocketUpgradeError::new(self, ErrorKind::Status(status)));
72 }
73 let key = self
74 .request_headers()
75 .get_str(SecWebsocketKey)
76 .expect("h1 websocket request did not include Sec-WebSocket-Key");
77 let accept_key = websocket_accept_hash(key);
78 if self.response_headers().get_str(SecWebsocketAccept) != Some(&accept_key) {
79 return Err(WebSocketUpgradeError::new(self, ErrorKind::InvalidAccept));
80 }
81 }
82 }
83
84 let peer_ip = self.peer_addr().map(|addr| addr.ip());
85 let mut conn = WebSocketConn::new(Upgrade::from(self), Some(config), Role::Client).await;
86 conn.set_peer_ip(peer_ip);
87 Ok(conn)
88 }
89}
90
91#[derive(thiserror::Error, Debug)]
93#[non_exhaustive]
94pub enum ErrorKind {
95 #[error(transparent)]
97 Http(#[from] trillium_http::Error),
98
99 #[error("Unexpected response status {0} for websocket upgrade")]
102 Status(Status),
103
104 #[error("Response Sec-WebSocket-Accept was missing or invalid")]
106 InvalidAccept,
107
108 #[error(
112 "Conn::into_websocket called after execution — build the conn and await into_websocket \
113 instead of awaiting the conn separately"
114 )]
115 AlreadyExecuted,
116
117 #[error("peer does not support extended CONNECT")]
122 ExtendedConnectUnsupported,
123}
124
125#[derive(Debug)]
130pub struct WebSocketUpgradeError {
131 pub kind: ErrorKind,
133 conn: Box<Conn>,
134}
135
136impl WebSocketUpgradeError {
137 fn new(conn: Conn, kind: ErrorKind) -> Self {
138 let conn = Box::new(conn);
139 Self { conn, kind }
140 }
141}
142
143impl From<WebSocketUpgradeError> for Conn {
144 fn from(value: WebSocketUpgradeError) -> Self {
145 *value.conn
146 }
147}
148
149impl Deref for WebSocketUpgradeError {
150 type Target = Conn;
151
152 fn deref(&self) -> &Self::Target {
153 &self.conn
154 }
155}
156impl DerefMut for WebSocketUpgradeError {
157 fn deref_mut(&mut self) -> &mut Self::Target {
158 &mut self.conn
159 }
160}
161
162impl Error for WebSocketUpgradeError {}
163
164impl Display for WebSocketUpgradeError {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 self.kind.fmt(f)
167 }
168}