1use std::{
4 io::{Read, Write},
5 net::{SocketAddr, TcpStream, ToSocketAddrs},
6 result::Result as StdResult,
7};
8
9use http::{request::Parts, HeaderName, Uri};
10use log::*;
11
12use crate::{
13 handshake::client::{generate_key, Request, Response},
14 protocol::WebSocketConfig,
15 stream::MaybeTlsStream,
16};
17
18#[cfg(feature = "proxy")]
19use crate::proxy;
20use crate::{
21 error::{Error, Result, UrlError},
22 handshake::{client::ClientHandshake, HandshakeError},
23 protocol::WebSocket,
24 stream::{Mode, NoDelay},
25};
26
27pub fn connect_with_config<Req: IntoClientRequest>(
49 request: Req,
50 config: Option<WebSocketConfig>,
51 max_redirects: u8,
52) -> Result<(WebSocket<MaybeTlsStream<TcpStream>>, Response)> {
53 fn try_client_handshake(
54 request: Request,
55 config: Option<WebSocketConfig>,
56 ) -> Result<(WebSocket<MaybeTlsStream<TcpStream>>, Response)> {
57 let uri = request.uri();
58 let mode = uri_mode(uri)?;
59
60 #[cfg(not(any(feature = "native-tls", feature = "__rustls-tls")))]
61 if let Mode::Tls = mode {
62 return Err(Error::Url(UrlError::TlsFeatureNotEnabled));
63 }
64
65 let host = request.uri().host().ok_or(Error::Url(UrlError::NoHostName))?;
66 let host = if host.starts_with('[') { &host[1..host.len() - 1] } else { host };
67 let port = uri.port_u16().unwrap_or(match mode {
68 Mode::Plain => 80,
69 Mode::Tls => 443,
70 });
71 let mut stream = connect_stream(request.uri(), host, port)?;
72 NoDelay::set_nodelay(&mut stream, true)?;
73
74 #[cfg(not(any(feature = "native-tls", feature = "__rustls-tls")))]
75 let client = client_with_config(request, MaybeTlsStream::Plain(stream), config);
76 #[cfg(any(feature = "native-tls", feature = "__rustls-tls"))]
77 let client = crate::tls::client_tls_with_config(request, stream, config, None);
78
79 client.map_err(|e| match e {
80 HandshakeError::Failure(f) => f,
81 HandshakeError::Interrupted(_) => panic!("Bug: blocking handshake not blocked"),
82 })
83 }
84
85 fn create_request(parts: &Parts, uri: &Uri) -> Request {
86 let mut builder =
87 Request::builder().uri(uri.clone()).method(parts.method.clone()).version(parts.version);
88 *builder.headers_mut().expect("Failed to create `Request`") = parts.headers.clone();
89 builder.body(()).expect("Failed to create `Request`")
90 }
91
92 let (parts, _) = request.into_client_request()?.into_parts();
93 let mut uri = parts.uri.clone();
94
95 for attempt in 0..=max_redirects {
96 let request = create_request(&parts, &uri);
97
98 match try_client_handshake(request, config) {
99 Err(Error::Http(res)) if res.status().is_redirection() && attempt < max_redirects => {
100 if let Some(location) = res.headers().get("Location") {
101 uri = location.to_str()?.parse::<Uri>()?;
102 debug!("Redirecting to {uri:?}");
103 continue;
104 } else {
105 warn!("No `Location` found in redirect");
106 return Err(Error::Http(res));
107 }
108 }
109 other => return other,
110 }
111 }
112
113 unreachable!("Bug in a redirect handling logic")
114}
115
116pub fn connect<Req: IntoClientRequest>(
129 request: Req,
130) -> Result<(WebSocket<MaybeTlsStream<TcpStream>>, Response)> {
131 connect_with_config(request, None, 3)
132}
133
134fn connect_to_some(addrs: &[SocketAddr], uri: &Uri) -> Result<TcpStream> {
135 for addr in addrs {
136 debug!("Trying to contact {uri} at {addr}...");
137 if let Ok(stream) = TcpStream::connect(addr) {
138 return Ok(stream);
139 }
140 }
141 Err(Error::Url(UrlError::UnableToConnect(uri.to_string())))
142}
143
144fn connect_stream(uri: &Uri, host: &str, port: u16) -> Result<TcpStream> {
145 #[cfg(feature = "proxy")]
146 if let Some(stream) = proxy::connect_proxy_stream(uri, host, port)? {
147 return Ok(stream);
148 }
149
150 let addrs = (host, port).to_socket_addrs()?;
151 connect_to_some(addrs.as_slice(), uri)
152}
153
154pub fn uri_mode(uri: &Uri) -> Result<Mode> {
159 match uri.scheme_str() {
160 Some("ws") => Ok(Mode::Plain),
161 Some("wss") => Ok(Mode::Tls),
162 _ => Err(Error::Url(UrlError::UnsupportedUrlScheme)),
163 }
164}
165
166pub fn client_with_config<Stream, Req>(
173 request: Req,
174 stream: Stream,
175 config: Option<WebSocketConfig>,
176) -> StdResult<(WebSocket<Stream>, Response), HandshakeError<ClientHandshake<Stream>>>
177where
178 Stream: Read + Write,
179 Req: IntoClientRequest,
180{
181 ClientHandshake::start(stream, request.into_client_request()?, config)?.handshake()
182}
183
184pub fn client<Stream, Req>(
190 request: Req,
191 stream: Stream,
192) -> StdResult<(WebSocket<Stream>, Response), HandshakeError<ClientHandshake<Stream>>>
193where
194 Stream: Read + Write,
195 Req: IntoClientRequest,
196{
197 client_with_config(request, stream, None)
198}
199
200pub trait IntoClientRequest {
209 fn into_client_request(self) -> Result<Request>;
211}
212
213impl IntoClientRequest for &str {
214 fn into_client_request(self) -> Result<Request> {
215 self.parse::<Uri>()?.into_client_request()
216 }
217}
218
219impl IntoClientRequest for &String {
220 fn into_client_request(self) -> Result<Request> {
221 <&str as IntoClientRequest>::into_client_request(self)
222 }
223}
224
225impl IntoClientRequest for String {
226 fn into_client_request(self) -> Result<Request> {
227 <&str as IntoClientRequest>::into_client_request(&self)
228 }
229}
230
231impl IntoClientRequest for &Uri {
232 fn into_client_request(self) -> Result<Request> {
233 self.clone().into_client_request()
234 }
235}
236
237impl IntoClientRequest for Uri {
238 fn into_client_request(self) -> Result<Request> {
239 let authority = self.authority().ok_or(Error::Url(UrlError::NoHostName))?.as_str();
240 let host = authority
241 .find('@')
242 .map(|idx| authority.split_at(idx + 1).1)
243 .unwrap_or_else(|| authority);
244
245 if host.is_empty() {
246 return Err(Error::Url(UrlError::EmptyHostName));
247 }
248
249 let req = Request::builder()
250 .method("GET")
251 .header("Host", host)
252 .header("Connection", "Upgrade")
253 .header("Upgrade", "websocket")
254 .header("Sec-WebSocket-Version", "13")
255 .header("Sec-WebSocket-Key", generate_key())
256 .uri(self)
257 .body(())?;
258 Ok(req)
259 }
260}
261
262#[cfg(feature = "url")]
263impl IntoClientRequest for &url::Url {
264 fn into_client_request(self) -> Result<Request> {
265 self.as_str().into_client_request()
266 }
267}
268
269#[cfg(feature = "url")]
270impl IntoClientRequest for url::Url {
271 fn into_client_request(self) -> Result<Request> {
272 self.as_str().into_client_request()
273 }
274}
275
276impl IntoClientRequest for Request {
277 fn into_client_request(self) -> Result<Request> {
278 Ok(self)
279 }
280}
281
282impl IntoClientRequest for httparse::Request<'_, '_> {
283 fn into_client_request(self) -> Result<Request> {
284 use crate::handshake::headers::FromHttparse;
285 Request::from_httparse(self)
286 }
287}
288
289#[derive(Debug, Clone)]
307pub struct ClientRequestBuilder {
308 uri: Uri,
309 additional_headers: Vec<(String, String)>,
311 subprotocols: Vec<String>,
313}
314
315impl ClientRequestBuilder {
316 #[must_use]
318 pub const fn new(uri: Uri) -> Self {
319 Self { uri, additional_headers: Vec::new(), subprotocols: Vec::new() }
320 }
321
322 pub fn with_header<K, V>(mut self, key: K, value: V) -> Self
324 where
325 K: Into<String>,
326 V: Into<String>,
327 {
328 self.additional_headers.push((key.into(), value.into()));
329 self
330 }
331
332 pub fn with_sub_protocol<P>(mut self, protocol: P) -> Self
334 where
335 P: Into<String>,
336 {
337 self.subprotocols.push(protocol.into());
338 self
339 }
340}
341
342impl IntoClientRequest for ClientRequestBuilder {
343 fn into_client_request(self) -> Result<Request> {
344 let mut request = self.uri.into_client_request()?;
345 let headers = request.headers_mut();
346 for (k, v) in self.additional_headers {
347 let key = HeaderName::try_from(k)?;
348 let value = v.parse()?;
349 headers.append(key, value);
350 }
351 if !self.subprotocols.is_empty() {
352 let protocols = self.subprotocols.join(", ").parse()?;
353 headers.append("Sec-WebSocket-Protocol", protocols);
354 }
355 Ok(request)
356 }
357}