1#![forbid(unsafe_code)]
2#![deny(
3 clippy::dbg_macro,
4 missing_copy_implementations,
5 rustdoc::missing_crate_level_docs,
6 missing_debug_implementations,
7 missing_docs,
8 nonstandard_style,
9 unused_qualifications
10)]
11
12#[cfg(test)]
15#[doc = include_str!("../README.md")]
16mod readme {}
17
18mod body_streamer;
19mod forward_proxy_connect;
20pub mod upstream;
21
22use body_streamer::stream_body;
23pub use forward_proxy_connect::ForwardProxyConnect;
24use full_duplex_async_copy::full_duplex_copy;
25use futures_lite::future::zip;
26use size::{Base, Size};
27use std::{borrow::Cow, fmt::Debug, future::IntoFuture};
28use trillium::{
29 Conn, Handler, KnownHeaderName,
30 Status::{NotFound, SwitchingProtocols},
31 Upgrade,
32};
33use trillium_client::ConnExt as _;
34pub use trillium_client::{Client, Connector};
35use trillium_forwarding::Forwarded;
36use trillium_http::{HeaderName, Headers, HttpContext, Status, Version};
37use upstream::{IntoUpstreamSelector, UpstreamSelector};
38pub use url::Url;
39
40pub fn proxy<I>(client: impl Into<Client>, upstream: I) -> Proxy<I::UpstreamSelector>
42where
43 I: IntoUpstreamSelector,
44{
45 Proxy::new(client, upstream)
46}
47
48#[derive(Debug)]
50pub struct Proxy<U> {
51 upstream: U,
52 client: Client,
53 pass_through_not_found: bool,
54 halt: bool,
55 via_pseudonym: Option<Cow<'static, str>>,
56 allow_websocket_upgrade: bool,
57}
58
59impl<U: UpstreamSelector> Proxy<U> {
60 pub fn new<I>(client: impl Into<Client>, upstream: I) -> Self
73 where
74 I: IntoUpstreamSelector<UpstreamSelector = U>,
75 {
76 let client = client
77 .into()
78 .without_default_header(KnownHeaderName::UserAgent)
79 .without_default_header(KnownHeaderName::Accept);
80
81 Self {
82 upstream: upstream.into_upstream(),
83 client,
84 pass_through_not_found: true,
85 halt: true,
86 via_pseudonym: None,
87 allow_websocket_upgrade: false,
88 }
89 }
90
91 pub fn proxy_not_found(mut self) -> Self {
105 self.pass_through_not_found = false;
106 self
107 }
108
109 pub fn without_halting(mut self) -> Self {
127 self.halt = false;
128 self
129 }
130
131 pub fn with_via_pseudonym(mut self, via_pseudonym: impl Into<Cow<'static, str>>) -> Self {
136 self.via_pseudonym = Some(via_pseudonym.into());
137 self
138 }
139
140 pub fn with_websocket_upgrades(mut self) -> Self {
145 self.allow_websocket_upgrade = true;
146 self
147 }
148
149 fn set_via_pseudonym(&self, headers: &mut Headers, version: Version) {
150 if self.via_pseudonym.is_none() {
151 return;
152 }
153
154 use std::fmt::Write;
155 let mut via = String::new();
156 let _ = write!(&mut via, "{version}");
157
158 if let Some(pseudonym) = &self.via_pseudonym {
159 let _ = write!(&mut via, " {pseudonym}");
160 }
161
162 if let Some(old_via) = headers.get_values(KnownHeaderName::Via) {
163 for old_via in old_via {
164 let _ = write!(&mut via, ", {old_via}");
165 }
166 }
167
168 headers.insert(KnownHeaderName::Via, via);
169 }
170
171 fn halt_unless_configured_otherwise(&self, conn: Conn) -> Conn {
172 if self.halt { conn.halt() } else { conn }
173 }
174}
175
176#[derive(Debug)]
177struct UpstreamUpgrade(Upgrade);
178
179impl<U: UpstreamSelector> Handler for Proxy<U> {
180 async fn init(&mut self, info: &mut trillium::Info) {
181 let old_context = self.client.context();
184 let new_context = HttpContext::default()
185 .with_config(*old_context.config())
186 .with_swansong(info.swansong().clone());
187 self.client.set_context(new_context);
188 log::info!("proxying to {:?}", self.upstream);
189 }
190
191 async fn run(&self, mut conn: Conn) -> Conn {
192 let Some(request_url) = self.upstream.determine_upstream(&mut conn) else {
193 return conn;
194 };
195
196 log::debug!("proxying to {}", request_url.as_str());
197
198 let mut forwarded = Forwarded::from_headers(conn.request_headers())
199 .ok()
200 .flatten()
201 .unwrap_or_default()
202 .into_owned();
203
204 if let Some(peer_ip) = conn.peer_ip() {
205 forwarded.add_for(peer_ip.to_string());
206 };
207
208 if let Some(host) = conn.host() {
209 forwarded.set_host(host);
210 }
211
212 let mut request_headers = conn
213 .request_headers()
214 .clone()
215 .without_headers([
216 KnownHeaderName::Connection,
217 KnownHeaderName::KeepAlive,
218 KnownHeaderName::ProxyAuthenticate,
219 KnownHeaderName::ProxyAuthorization,
220 KnownHeaderName::Te,
221 KnownHeaderName::Trailer,
222 KnownHeaderName::TransferEncoding,
223 KnownHeaderName::Upgrade,
224 KnownHeaderName::Host,
225 KnownHeaderName::XforwardedBy,
226 KnownHeaderName::XforwardedFor,
227 KnownHeaderName::XforwardedHost,
228 KnownHeaderName::XforwardedProto,
229 KnownHeaderName::XforwardedSsl,
230 KnownHeaderName::AltUsed,
231 ])
232 .with_inserted_header(KnownHeaderName::Forwarded, forwarded.to_string());
233
234 let mut connection_is_upgrade = false;
235 for header in conn
236 .request_headers()
237 .token_iter(KnownHeaderName::Connection)
238 .map(HeaderName::from)
239 {
240 if header == KnownHeaderName::Upgrade {
241 connection_is_upgrade = true;
242 }
243 request_headers.remove(header);
244 }
245
246 if self.allow_websocket_upgrade
247 && connection_is_upgrade
248 && conn
249 .request_headers()
250 .eq_ignore_ascii_case(KnownHeaderName::Upgrade, "websocket")
251 {
252 request_headers.extend([
253 (KnownHeaderName::Upgrade, "WebSocket"),
254 (KnownHeaderName::Connection, "Upgrade"),
255 ]);
256 }
257
258 self.set_via_pseudonym(&mut request_headers, conn.http_version());
259
260 let method = conn.method();
266 let (body_fut, request_body) = stream_body(&mut conn);
267
268 let client_fut = self
269 .client
270 .build_conn(method, request_url)
271 .with_request_headers(request_headers)
272 .with_body(request_body)
273 .into_future();
274
275 let conn_result = zip(body_fut, client_fut).await.1;
276
277 let mut client_conn = match conn_result {
278 Ok(client_conn) => client_conn,
279 Err(e) => {
280 let conn = conn.with_status(Status::BadGateway).with_state(e);
281 return self.halt_unless_configured_otherwise(conn);
282 }
283 };
284
285 let client_conn_version = client_conn.http_version();
286
287 let mut conn = match client_conn.status() {
288 Some(SwitchingProtocols) => {
289 conn.response_headers_mut()
290 .extend(std::mem::take(client_conn.response_headers_mut()));
291
292 conn.with_state(UpstreamUpgrade(
293 trillium_http::Upgrade::from(client_conn).into(),
294 ))
295 .with_status(SwitchingProtocols)
296 }
297
298 Some(NotFound) if self.pass_through_not_found => {
299 client_conn.recycle().await;
300 return conn;
301 }
302
303 Some(status) => {
304 conn.response_headers_mut().remove(KnownHeaderName::Server);
305 conn.response_headers_mut()
306 .append_all(client_conn.response_headers().clone());
307 conn.with_body(client_conn).with_status(status)
308 }
309
310 None => {
311 return self.halt_unless_configured_otherwise(conn.with_status(Status::BadGateway));
312 }
313 };
314
315 if Some(SwitchingProtocols) != conn.status()
316 || !conn
317 .response_headers()
318 .eq_ignore_ascii_case(KnownHeaderName::Connection, "Upgrade")
319 {
320 let connection = conn
321 .response_headers_mut()
322 .remove(KnownHeaderName::Connection);
323
324 conn.response_headers_mut().remove_all(
325 connection
326 .iter()
327 .flatten()
328 .filter_map(|s| s.as_str())
329 .flat_map(|s| s.split(','))
330 .map(|t| HeaderName::from(t.trim()).into_owned()),
331 );
332 }
333
334 conn.response_headers_mut().remove_all([
335 KnownHeaderName::KeepAlive,
336 KnownHeaderName::ProxyAuthenticate,
337 KnownHeaderName::ProxyAuthorization,
338 KnownHeaderName::Te,
339 KnownHeaderName::Trailer,
340 KnownHeaderName::TransferEncoding,
341 ]);
342
343 self.set_via_pseudonym(conn.response_headers_mut(), client_conn_version);
344
345 self.halt_unless_configured_otherwise(conn)
346 }
347
348 fn has_upgrade(&self, upgrade: &Upgrade) -> bool {
349 upgrade.state().contains::<UpstreamUpgrade>()
350 }
351
352 async fn upgrade(&self, mut upgrade: Upgrade) {
353 let Some(UpstreamUpgrade(upstream)) = upgrade.state_mut().take() else {
354 return;
355 };
356 let downstream = upgrade;
357 match full_duplex_copy(upstream, downstream).await {
358 Err(e) => log::error!("upgrade stream error: {:?}", e),
359 Ok((up, down)) => {
360 log::debug!("streamed upgrade {} up and {} down", bytes(up), bytes(down))
361 }
362 }
363 }
364}
365
366fn bytes(bytes: u64) -> String {
367 Size::from_bytes(bytes)
368 .format()
369 .with_base(Base::Base10)
370 .to_string()
371}