clawft_kernel/infer_proxy/
upstream.rs1use std::net::IpAddr;
9use std::time::Duration;
10
11use super::types::{Method, ProxyError, ProxyLimits, ProxyRequest, ResponseSink};
12
13pub fn parse_loopback_base(base: &str) -> Result<(IpAddr, u16), ProxyError> {
16 let bad = || ProxyError::NotLoopback(base.to_string());
17 let rest = base.strip_prefix("http://").ok_or_else(bad)?;
18 let rest = rest.trim_end_matches('/');
19 let sock: std::net::SocketAddr = rest.parse().map_err(|_| bad())?;
20 if !sock.ip().is_loopback() || sock.port() == 0 {
21 return Err(bad());
22 }
23 Ok((sock.ip(), sock.port()))
24}
25
26pub struct Upstream {
28 http: reqwest::Client,
29 limits: ProxyLimits,
30}
31
32impl Upstream {
33 pub fn new(limits: ProxyLimits) -> Result<Self, ProxyError> {
35 let http = reqwest::Client::builder()
36 .redirect(reqwest::redirect::Policy::none())
37 .no_proxy()
38 .pool_max_idle_per_host(0)
39 .connect_timeout(limits.connect_timeout)
40 .build()
41 .map_err(|e| ProxyError::Io(format!("http client: {e}")))?;
42 Ok(Self { http, limits })
43 }
44
45 pub fn limits(&self) -> &ProxyLimits {
47 &self.limits
48 }
49
50 pub async fn forward(
58 &self,
59 base: &str,
60 req: &ProxyRequest,
61 send_auth: bool,
62 sink: &mut dyn ResponseSink,
63 ) -> Result<(), ProxyError> {
64 let (ip, port) = parse_loopback_base(base)?;
65 let authority = match ip {
66 IpAddr::V4(a) => format!("{a}:{port}"),
67 IpAddr::V6(a) => format!("[{a}]:{port}"),
68 };
69 let url = format!("http://{authority}{}", req.path);
70 let parsed = reqwest::Url::parse(&url).map_err(|e| ProxyError::BadRequest(e.to_string()))?;
71 if parsed.port() != Some(port) || parsed.host_str().is_none() {
73 return Err(ProxyError::Forbidden("destination changed".into()));
74 }
75 let mut b = match req.method {
76 Method::Get => self.http.get(parsed),
77 Method::Post => self.http.post(parsed).body(req.body.clone()),
78 };
79 if let Some(ct) = &req.content_type {
80 b = b.header("content-type", ct);
81 }
82 if let Some(a) = &req.accept {
83 b = b.header("accept", a);
84 }
85 if send_auth && let Some(a) = &req.authorization {
86 b = b.header("authorization", a);
87 }
88 let stall = self.limits.stall_timeout;
89 let mut resp = tokio::time::timeout(stall, b.send())
90 .await
91 .map_err(|_| ProxyError::Timeout("upstream did not answer".into()))?
92 .map_err(|e| ProxyError::Upstream(e.without_url().to_string()))?;
93 let ct = resp
94 .headers()
95 .get("content-type")
96 .and_then(|v| v.to_str().ok())
97 .map(str::to_string);
98 let code = resp.status().as_u16();
99 if !(200..=599).contains(&code) {
100 return Err(ProxyError::Upstream(format!("unexpected status {code}")));
103 }
104 sink.head(code, ct.as_deref()).await?;
105 let mut total: u64 = 0;
106 loop {
107 let next = tokio::time::timeout(stall, resp.chunk())
108 .await
109 .map_err(|_| ProxyError::Timeout("upstream stalled".into()))?
110 .map_err(|e| ProxyError::Upstream(e.without_url().to_string()))?;
111 let Some(c) = next else { break };
112 total += c.len() as u64;
113 if total > self.limits.max_response_body {
114 return Err(ProxyError::TooLarge("response body".into()));
115 }
116 sink.chunk(&c).await?;
117 }
118 Ok(())
119 }
120}
121
122pub const PROBE_TIMEOUT: Duration = Duration::from_millis(300);