rust_web_server/proxy/
upgrade.rs1use std::io::{Read, Write};
8use std::net::{Shutdown, TcpStream, ToSocketAddrs};
9use std::time::Duration;
10
11use crate::core::New;
12use crate::header::Header;
13use crate::request::Request;
14use crate::response::Response;
15use crate::websocket::WebSocket;
16
17const MAX_HEAD: usize = 64 * 1024;
19
20pub fn is_upgrade(request: &Request) -> bool {
23 let connection = request.header("connection").map(|h| h.value.to_ascii_lowercase()).unwrap_or_default();
24 request.header("upgrade").is_some() && connection.split(',').any(|t| t.trim() == "upgrade")
25}
26
27pub fn request_head(request: &Request, client: crate::proxy::Peer<'_>, host: &str) -> Vec<u8> {
31 let mut head = format!("{} {} HTTP/1.1\r\nHost: {host}\r\n", request.method, request.request_uri);
32 let forwarded = crate::proxy::forwarded_value(request, client);
33 for header in &request.headers {
34 if header.name.eq_ignore_ascii_case("host") || header.name.eq_ignore_ascii_case("content-length") || (forwarded.is_some() && header.name.eq_ignore_ascii_case("forwarded")) {
35 continue;
36 }
37 head.push_str(&format!("{}: {}\r\n", header.name, header.value));
38 }
39 head.push_str(&format!("X-Forwarded-For: {}\r\n", client.ip));
40 if let Some(value) = &forwarded {
41 head.push_str(&format!("Forwarded: {value}\r\n"));
42 }
43 if request.header("X-Forwarded-Proto").is_none() {
44 head.push_str(&format!("X-Forwarded-Proto: {}\r\n", client.proto()));
45 }
46 if request.header("X-Forwarded-Host").is_none() {
47 if let Some(original) = request.header("host").filter(|h| !h.value.is_empty()) {
48 head.push_str(&format!("X-Forwarded-Host: {}\r\n", original.value));
49 }
50 }
51 if !request.body.is_empty() {
52 head.push_str(&format!("Content-Length: {}\r\n", request.body.len()));
53 }
54 head.push_str("\r\n");
55 head.into_bytes()
56}
57
58pub fn forward(request: &Request, client: crate::proxy::Peer<'_>, ip: &str, port: u16, host: &str, timeout: Duration) -> Result<Response, String> {
60 let address = (ip, port).to_socket_addrs().map_err(|e| e.to_string())?.next().ok_or("no address")?;
61 let mut backend = TcpStream::connect_timeout(&address, timeout.min(Duration::from_secs(10))).map_err(|e| e.to_string())?;
62 backend.set_read_timeout(Some(timeout)).map_err(|e| e.to_string())?;
63 backend.write_all(&request_head(request, client, host)).map_err(|e| e.to_string())?;
64 backend.write_all(&request.body).map_err(|e| e.to_string())?;
65
66 let mut received = Vec::new();
67 let head_end = loop {
68 if let Some(at) = received.windows(4).position(|w| w == b"\r\n\r\n") {
69 break at + 4;
70 }
71 if received.len() > MAX_HEAD {
72 return Err("backend response head too large".to_string());
73 }
74 let mut buffer = [0u8; 8192];
75 let n = backend.read(&mut buffer).map_err(|e| e.to_string())?;
76 if n == 0 {
77 return Err("backend closed before responding".to_string());
78 }
79 received.extend_from_slice(&buffer[..n]);
80 };
81 let head = String::from_utf8_lossy(&received[..head_end]).to_string();
82 let status: i16 = head.split_whitespace().nth(1).and_then(|s| s.parse().ok()).ok_or("bad status line")?;
83 if status != 101 {
84 let length = head
86 .lines()
87 .find_map(|l| l.split_once(':').filter(|(n, _)| n.trim().eq_ignore_ascii_case("content-length")).and_then(|(_, v)| v.trim().parse::<usize>().ok()))
88 .unwrap_or(0);
89 while received.len() < head_end + length {
90 let mut buffer = [0u8; 8192];
91 match backend.read(&mut buffer) {
92 Ok(0) | Err(_) => break,
93 Ok(n) => received.extend_from_slice(&buffer[..n]),
94 }
95 }
96 return Ok(crate::proxy::normalize_upstream_response(Response::parse(&received)?));
97 }
98 let mut response = Response::new();
99 response.status_code = 101;
100 response.reason_phrase = "Switching Protocols".to_string();
101 response.headers = head
102 .lines()
103 .skip(1)
104 .filter_map(|l| l.split_once(':'))
105 .map(|(n, v)| Header::new(n.trim().to_string(), v.trim().to_string()))
106 .collect();
107 let leftover = received[head_end..].to_vec();
108 backend.set_read_timeout(None).map_err(|e| e.to_string())?;
109 let to_backend = backend.try_clone().map_err(|e| e.to_string())?;
110 Ok(WebSocket::upgrade_raw(response, move |client_reader, client_writer| relay(client_reader, client_writer, backend, to_backend, leftover)))
111}
112
113fn relay(mut client_reader: Box<dyn Read + Send>, mut client_writer: Box<dyn Write + Send>, mut from_backend: TcpStream, mut to_backend: TcpStream, leftover: Vec<u8>) {
116 std::thread::spawn(move || {
117 let _ = std::io::copy(&mut client_reader, &mut to_backend);
118 let _ = to_backend.shutdown(Shutdown::Write);
119 });
120 if client_writer.write_all(&leftover).is_ok() {
121 let _ = std::io::copy(&mut from_backend, &mut client_writer);
122 }
123 let _ = client_writer.flush();
124 let _ = from_backend.shutdown(Shutdown::Both);
125}
126
127#[cfg(test)]
128mod tests;