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 content_length = !matches!(
261 conn.request_headers()
262 .get_str(KnownHeaderName::ContentLength),
263 Some("0") | None
264 );
265
266 let chunked = conn
267 .request_headers()
268 .eq_ignore_ascii_case(KnownHeaderName::TransferEncoding, "chunked");
269
270 let method = conn.method();
271 let conn_result = if chunked || content_length {
272 let (body_fut, request_body) = stream_body(&mut conn);
273
274 let client_fut = self
275 .client
276 .build_conn(method, request_url)
277 .with_request_headers(request_headers)
278 .with_body(request_body)
279 .into_future();
280
281 zip(body_fut, client_fut).await.1
282 } else {
283 self.client
284 .build_conn(method, request_url)
285 .with_request_headers(request_headers)
286 .await
287 };
288
289 let mut client_conn = match conn_result {
290 Ok(client_conn) => client_conn,
291 Err(e) => {
292 let conn = conn.with_status(Status::BadGateway).with_state(e);
293 return self.halt_unless_configured_otherwise(conn);
294 }
295 };
296
297 let client_conn_version = client_conn.http_version();
298
299 let mut conn = match client_conn.status() {
300 Some(SwitchingProtocols) => {
301 conn.response_headers_mut()
302 .extend(std::mem::take(client_conn.response_headers_mut()));
303
304 conn.with_state(UpstreamUpgrade(
305 trillium_http::Upgrade::from(client_conn).into(),
306 ))
307 .with_status(SwitchingProtocols)
308 }
309
310 Some(NotFound) if self.pass_through_not_found => {
311 client_conn.recycle().await;
312 return conn;
313 }
314
315 Some(status) => {
316 conn.response_headers_mut().remove(KnownHeaderName::Server);
317 conn.response_headers_mut()
318 .append_all(client_conn.response_headers().clone());
319 conn.with_body(client_conn).with_status(status)
320 }
321
322 None => {
323 return self.halt_unless_configured_otherwise(conn.with_status(Status::BadGateway));
324 }
325 };
326
327 if Some(SwitchingProtocols) != conn.status()
328 || !conn
329 .response_headers()
330 .eq_ignore_ascii_case(KnownHeaderName::Connection, "Upgrade")
331 {
332 let connection = conn
333 .response_headers_mut()
334 .remove(KnownHeaderName::Connection);
335
336 conn.response_headers_mut().remove_all(
337 connection
338 .iter()
339 .flatten()
340 .filter_map(|s| s.as_str())
341 .flat_map(|s| s.split(','))
342 .map(|t| HeaderName::from(t.trim()).into_owned()),
343 );
344 }
345
346 conn.response_headers_mut().remove_all([
347 KnownHeaderName::KeepAlive,
348 KnownHeaderName::ProxyAuthenticate,
349 KnownHeaderName::ProxyAuthorization,
350 KnownHeaderName::Te,
351 KnownHeaderName::Trailer,
352 KnownHeaderName::TransferEncoding,
353 ]);
354
355 self.set_via_pseudonym(conn.response_headers_mut(), client_conn_version);
356
357 self.halt_unless_configured_otherwise(conn)
358 }
359
360 fn has_upgrade(&self, upgrade: &Upgrade) -> bool {
361 upgrade.state().contains::<UpstreamUpgrade>()
362 }
363
364 async fn upgrade(&self, mut upgrade: Upgrade) {
365 let Some(UpstreamUpgrade(upstream)) = upgrade.state_mut().take() else {
366 return;
367 };
368 let downstream = upgrade;
369 match full_duplex_copy(upstream, downstream).await {
370 Err(e) => log::error!("upgrade stream error: {:?}", e),
371 Ok((up, down)) => {
372 log::debug!("streamed upgrade {} up and {} down", bytes(up), bytes(down))
373 }
374 }
375 }
376}
377
378fn bytes(bytes: u64) -> String {
379 Size::from_bytes(bytes)
380 .format()
381 .with_base(Base::Base10)
382 .to_string()
383}