volo_http/client/layer/
http_proxy.rs1use std::fmt;
6
7use bytes::Bytes;
8use http::{
9 uri::{Authority, PathAndQuery, Scheme, Uri},
10 version::Version,
11};
12use motore::{layer::Layer, service::Service};
13use volo::{client::Apply, context::Context};
14
15use crate::{
16 client::{Target, target::RemoteHost, utils::is_default_port},
17 context::ClientContext,
18 error::{
19 ClientError,
20 client::{Result, request_error},
21 },
22 request::Request,
23};
24
25pub struct HttpProxy {
29 target: Option<Target>,
30}
31
32impl<S> Layer<S> for HttpProxy {
33 type Service = HttpProxyService<S>;
34
35 fn layer(self, inner: S) -> Self::Service {
36 HttpProxyService {
37 inner,
38 target: self.target,
39 }
40 }
41}
42
43#[derive(Debug)]
45pub struct GatewayError {
46 inner: ClientError,
47 gateway: Target,
48}
49
50impl fmt::Display for GatewayError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(
53 f,
54 "error while using proxy {}: {}",
55 self.gateway, self.inner
56 )
57 }
58}
59
60impl std::error::Error for GatewayError {
61 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
62 Some(&self.inner)
63 }
64}
65
66fn parse_uri(uri: Uri) -> Option<Target> {
67 if let Some(scheme) = uri.scheme() {
68 if scheme != &Scheme::HTTP {
69 tracing::warn!(
70 "[Volo-HTTP] HttpProxy: `{uri}` is not a valid proxy url, only HTTP protocol is \
71 supported"
72 );
73 return None;
74 }
75 }
76 let target = match Target::from_uri(&uri) {
77 Ok(target) => target,
78 Err(err) => {
79 tracing::warn!("[Volo-HTTP] HttpProxy: failed to parse uri `{uri}`: {err}");
80 return None;
81 }
82 };
83
84 tracing::info!("[Volo-HTTP] HttpProxy: `{uri}` is used as http proxy");
85 Some(target)
86}
87
88fn parse_env() -> Option<Uri> {
89 let env = if let Ok(uri) = std::env::var("http_proxy") {
90 uri
91 } else if let Ok(uri) = std::env::var("HTTP_PROXY") {
92 uri
93 } else {
94 return None;
95 };
96 Uri::from_maybe_shared(env).ok()
97}
98
99impl HttpProxy {
100 pub fn env() -> Self {
105 let target = parse_env().and_then(parse_uri);
106 Self { target }
107 }
108
109 pub fn new<U>(uri: U) -> Self
113 where
114 U: TryInto<Uri>,
115 U::Error: std::error::Error,
116 {
117 let proxy_uri = match uri.try_into() {
118 Ok(uri) => Some(uri),
119 Err(e) => {
120 tracing::warn!("[Volo-HTTP] HttpProxy: failed to build http proxy: {e}");
121 None
122 }
123 };
124 let target = proxy_uri.and_then(parse_uri);
125 Self { target }
126 }
127}
128
129pub struct HttpProxyService<S> {
133 inner: S,
134 target: Option<Target>,
135}
136
137impl<S> HttpProxyService<S> {
138 fn update_req<B>(&self, cx: &mut ClientContext, req: &mut Request<B>) -> Option<Target> {
139 let Some(target) = &self.target else {
140 return None;
141 };
142
143 if req.version() != Version::HTTP_11 {
146 tracing::info!("[Volo-HTTP] HttpProxy only works for HTTP/1.1");
147 return None;
148 }
149 if let Some(scheme) = cx.target().scheme() {
150 if scheme != &Scheme::HTTP {
151 tracing::info!("[Volo-HTTP] HttpProxy only supports HTTP protocol");
152 return None;
153 }
154 }
155
156 let Some(authority) = gen_authority(cx.target()) else {
159 tracing::warn!(
160 "[Volo-HTTP] HttpProxy: failed to gen authority by {:?}",
161 cx.target()
162 );
163 return None;
164 };
165 let authority = match Authority::from_maybe_shared(Bytes::from(authority)) {
166 Ok(authority) => authority,
167 Err(e) => {
168 tracing::warn!("[Volo-HTTP] HttpProxy: failed to parse authority: {e}");
169 return None;
170 }
171 };
172 let mut parts = req.uri().to_owned().into_parts();
173 parts.scheme = Some(Scheme::HTTP);
174 parts.authority = Some(authority);
175 parts.path_and_query = Some(
176 parts
177 .path_and_query
178 .unwrap_or(PathAndQuery::from_static("/")),
179 );
180 let uri = match Uri::from_parts(parts) {
181 Ok(uri) => uri,
182 Err(e) => {
183 tracing::warn!("[Volo-HTTP] HttpProxy: failed to build uri: {e}");
184 return None;
185 }
186 };
187 *req.uri_mut() = uri;
188
189 cx.rpc_info_mut().callee_mut().clear();
192 let old_target = target
193 .to_owned()
194 .apply_and_replace(cx)
195 .expect("infallible: failed to parse target in HttpProxy");
196
197 Some(old_target)
198 }
199}
200
201fn gen_authority(target: &Target) -> Option<String> {
202 let rt = match target {
203 Target::None => return None,
204 Target::Remote(rt) => rt,
205 #[cfg(target_family = "unix")]
206 Target::Local(_) => return None,
207 };
208 let default_port = is_default_port(&rt.scheme, rt.port);
209 let host = match &rt.host {
210 RemoteHost::Ip(ip) => {
211 if default_port {
212 if ip.is_ipv4() {
213 format!("{ip}")
214 } else {
215 format!("[{ip}]")
216 }
217 } else {
218 let port = rt.port;
219 if ip.is_ipv4() {
220 format!("{ip}:{port}")
221 } else {
222 format!("[{ip}]:{port}")
223 }
224 }
225 }
226 RemoteHost::Name(name) => {
227 let port = rt.port;
228 if default_port {
229 name.as_str().to_owned()
230 } else {
231 format!("{name}:{port}")
232 }
233 }
234 };
235 Some(host)
236}
237
238fn restore_target(cx: &mut ClientContext, target: Target) -> Result<()> {
239 cx.rpc_info_mut().callee_mut().clear();
240 target.apply(cx)
241}
242
243impl<B, S> Service<ClientContext, Request<B>> for HttpProxyService<S>
244where
245 B: Send,
246 S: Service<ClientContext, Request<B>, Error = ClientError> + Send + Sync,
247{
248 type Response = S::Response;
249 type Error = S::Error;
250
251 async fn call(
252 &self,
253 cx: &mut ClientContext,
254 mut req: Request<B>,
255 ) -> Result<Self::Response, Self::Error> {
256 let old_target = self.update_req(cx, &mut req);
257 let result = self.inner.call(cx, req).await;
258
259 if let Some(target) = old_target {
263 restore_target(cx, target)?;
264 };
265
266 match result {
267 Ok(resp) => Ok(resp),
268 Err(e) => {
269 if let Some(target) = &self.target {
270 let err = GatewayError {
271 inner: e,
272 gateway: target.to_owned(),
273 };
274 Err(request_error(err))
275 } else {
276 Err(e)
277 }
278 }
279 }
280 }
281}