Skip to content

Commit a508f09

Browse files
committed
Revert "Refactor HttpClient request handling to support redirect logic."
This reverts commit 1acf361.
1 parent 1acf361 commit a508f09

File tree

1 file changed

+44
-221
lines changed

1 file changed

+44
-221
lines changed

src/net/http.rs

Lines changed: 44 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@ use core::ptr::NonNull;
99
use std::io;
1010

1111
use bytes::Bytes;
12-
use http::uri::{PathAndQuery, Scheme};
13-
use http::{Method, Request, Response, StatusCode, Uri};
12+
use http::uri::Scheme;
13+
use http::{Request, Response};
1414
use http_body::Body;
1515
use http_body_util::BodyExt;
1616
use nginx_sys::{ngx_log_t, ngx_resolver_t, NGX_LOG_WARN};
1717
use ngx::allocator::Box;
1818
use ngx::async_::resolver::Resolver;
1919
use ngx::async_::spawn;
2020
use ngx::ngx_log_error;
21-
use std::format;
22-
use std::string::ToString;
2321
use thiserror::Error;
2422

2523
use super::peer_conn::PeerConnection;
@@ -42,9 +40,6 @@ const NGX_ACME_USER_AGENT: &str = constcat::concat!(
4240
NGINX_VER,
4341
);
4442

45-
// Maximum number of redirects to follow for a single request.
46-
const MAX_REDIRECTS: usize = 10;
47-
4843
#[allow(async_fn_in_trait)]
4944
pub trait HttpClient {
5045
type Error: StdError + Send + Sync + 'static;
@@ -106,41 +101,26 @@ impl<'a> NgxHttpClient<'a> {
106101
impl HttpClient for NgxHttpClient<'_> {
107102
type Error = HttpClientError;
108103

109-
async fn request<B>(&self, req: Request<B>) -> Result<Response<Bytes>, Self::Error>
104+
async fn request<B>(&self, mut req: Request<B>) -> Result<Response<Bytes>, Self::Error>
110105
where
111106
B: Body + Send + 'static,
112107
<B as Body>::Data: Send,
113108
<B as Body>::Error: StdError + Send + Sync,
114109
{
115-
// Buffer the request body so it can be retransmitted across redirects safely.
116-
let (mut parts, body) = req.into_parts();
117-
let orig_method = parts.method.clone();
118-
let mut body_bytes = BodyExt::collect(body)
119-
.await
120-
.map_err(|e| HttpClientError::Body(std::boxed::Box::new(e)))?
121-
.to_bytes();
122-
123-
let mut current_uri = parts.uri.clone();
110+
let uri = req.uri().clone();
124111

125-
// Basic validation of the starting URI.
126-
let mut authority = current_uri
112+
let authority = uri
127113
.authority()
128-
.cloned()
129114
.ok_or(HttpClientError::Uri("missing authority"))?;
130115

131-
let mut redirects_followed = 0usize;
132-
133-
loop {
134-
// Prepare request for the current target by setting origin-form URI and required headers.
135-
let path_and_query: PathAndQuery = current_uri
136-
.path_and_query()
137-
.cloned()
138-
.ok_or(HttpClientError::Uri("missing path"))?;
116+
let path_and_query = uri
117+
.path_and_query()
118+
.ok_or(HttpClientError::Uri("missing path"))?;
139119

140-
parts.uri = Uri::from(path_and_query.clone());
120+
*req.uri_mut() = path_and_query.clone().into();
141121

142-
// Build a fresh headers map for each attempt to avoid header leakage across hosts.
143-
let mut headers = parts.headers.clone();
122+
{
123+
let headers = req.headers_mut();
144124
headers.insert(
145125
http::header::HOST,
146126
http::HeaderValue::from_str(authority.as_str())
@@ -154,207 +134,50 @@ impl HttpClient for NgxHttpClient<'_> {
154134
http::header::CONNECTION,
155135
http::HeaderValue::from_static("close"),
156136
);
137+
}
157138

158-
// Ensure Content-Length matches the body we will send; adjust for GET/HEAD.
159-
let sending_method = parts.method.clone();
160-
let mut send_body = http_body_util::Full::new(body_bytes.clone());
161-
if sending_method == Method::GET || sending_method == Method::HEAD {
162-
headers.insert(
163-
http::header::CONTENT_LENGTH,
164-
http::HeaderValue::from_static("0"),
165-
);
166-
// Empty the body for GET/HEAD attempts.
167-
send_body = http_body_util::Full::new(Bytes::new());
168-
} else {
169-
headers.insert(
170-
http::header::CONTENT_LENGTH,
171-
http::HeaderValue::from_str(&body_bytes.len().to_string())
172-
.map_err(|_| HttpClientError::Uri("bad content length"))?,
173-
);
174-
}
175-
176-
let mut req = Request::new(send_body);
177-
*req.method_mut() = parts.method.clone();
178-
*req.uri_mut() = parts.uri.clone();
179-
*req.version_mut() = parts.version;
180-
{
181-
let hm = req.headers_mut();
182-
hm.clear();
183-
for (k, v) in headers.iter() {
184-
hm.insert(k, v.clone());
185-
}
186-
}
187-
*req.extensions_mut() = parts.extensions.clone();
188-
189-
// Establish connection for current target.
190-
let ssl = if current_uri.scheme() == Some(&Scheme::HTTPS) {
191-
Some(self.ssl.as_ref())
192-
} else {
193-
None
194-
};
195-
196-
let mut peer = Box::pin(PeerConnection::new(self.log)?);
197-
peer.as_mut()
198-
.connect_to(authority.as_str(), &self.resolver, ssl)
199-
.await?;
200-
201-
if ssl.is_some() && self.ssl_verify {
202-
if let Err(err) = peer.verify_peer() {
203-
let _ = future::poll_fn(|cx| peer.as_mut().poll_shutdown(cx)).await;
204-
return Err(err.into());
205-
}
206-
}
207-
208-
if let Some(c) = peer.connection_mut() {
209-
c.requests += 1;
210-
}
211-
212-
let (mut sender, conn) = hyper::client::conn::http1::handshake(peer).await?;
213-
214-
let log = self.log;
215-
spawn(async move {
216-
if let Err(err) = conn.await {
217-
ngx_log_error!(NGX_LOG_WARN, log.as_ptr(), "connection error: {err}");
218-
}
219-
})
220-
.detach();
139+
let ssl = if uri.scheme() == Some(&Scheme::HTTPS) {
140+
Some(self.ssl.as_ref())
141+
} else {
142+
None
143+
};
221144

222-
let resp = sender.send_request(req).await?;
145+
let mut peer = Box::pin(PeerConnection::new(self.log)?);
223146

224-
// If not a redirect, return the response with its body collected.
225-
if !is_redirect(resp.status()) {
226-
let (parts, body) = resp.into_parts();
227-
let body = http_body_util::Limited::new(body, NGX_ACME_MAX_BODY_SIZE)
228-
.collect()
229-
.await
230-
.map_err(HttpClientError::Body)?
231-
.to_bytes();
232-
return Ok(Response::from_parts(parts, body));
233-
}
147+
peer.as_mut()
148+
.connect_to(authority.as_str(), &self.resolver, ssl)
149+
.await?;
234150

235-
// For non-GET/HEAD, do not auto-follow redirects.
236-
// ACME JWS 'url' must match the request URL; on redirect the client must re-sign and
237-
// POST to the new Location itself (RFC 8555 §6.2). Return the 3xx so the ACME layer
238-
// can handle it.
239-
if !(parts.method == Method::GET || parts.method == Method::HEAD) {
240-
let (parts_r, body) = resp.into_parts();
241-
let body = http_body_util::Limited::new(body, NGX_ACME_MAX_BODY_SIZE)
242-
.collect()
243-
.await
244-
.map_err(HttpClientError::Body)?
245-
.to_bytes();
246-
return Ok(Response::from_parts(parts_r, body));
151+
if ssl.is_some() && self.ssl_verify {
152+
if let Err(err) = peer.verify_peer() {
153+
let _ = future::poll_fn(|cx| peer.as_mut().poll_shutdown(cx)).await;
154+
return Err(err.into());
247155
}
156+
}
248157

249-
// Handle redirect logic.
250-
redirects_followed += 1;
251-
if redirects_followed > MAX_REDIRECTS {
252-
return Err(HttpClientError::Uri("too many redirects"));
253-
}
254-
255-
let location = resp
256-
.headers()
257-
.get(http::header::LOCATION)
258-
.ok_or(HttpClientError::Uri("redirect without location"))?;
259-
260-
let new_uri = resolve_location(&current_uri, location)?;
158+
if let Some(c) = peer.connection_mut() {
159+
c.requests += 1;
160+
}
261161

262-
// Prevent HTTPS -> HTTP downgrade unless the original request was HTTP.
263-
if current_uri.scheme() == Some(&Scheme::HTTPS)
264-
&& new_uri.scheme() == Some(&Scheme::HTTP)
265-
{
266-
return Err(HttpClientError::Uri("insecure redirect from https to http"));
267-
}
162+
let (mut sender, conn) = hyper::client::conn::http1::handshake(peer).await?;
268163

269-
// Adjust method and body according to status code per RFC 9110.
270-
match resp.status() {
271-
StatusCode::SEE_OTHER => {
272-
// 303: switch to GET, except HEAD remains HEAD
273-
parts.method = if orig_method == Method::HEAD {
274-
Method::HEAD
275-
} else {
276-
Method::GET
277-
};
278-
body_bytes = Bytes::new();
279-
// Drop content-type if any
280-
parts.headers.remove(http::header::CONTENT_TYPE);
281-
}
282-
StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND => {
283-
// 301/302: preserve method and body
284-
parts.method = orig_method.clone();
285-
}
286-
StatusCode::PERMANENT_REDIRECT | StatusCode::TEMPORARY_REDIRECT => {
287-
// 308/307: MUST NOT change method
288-
// no-op; parts.method unchanged
289-
}
290-
_ => {}
164+
let log = self.log;
165+
spawn(async move {
166+
if let Err(err) = conn.await {
167+
ngx_log_error!(NGX_LOG_WARN, log.as_ptr(), "connection error: {err}");
291168
}
169+
})
170+
.detach();
292171

293-
// Update target for next loop iteration.
294-
authority = new_uri
295-
.authority()
296-
.cloned()
297-
.ok_or(HttpClientError::Uri("bad redirect authority"))?;
298-
current_uri = new_uri;
299-
}
300-
}
301-
}
302-
303-
fn is_redirect(status: StatusCode) -> bool {
304-
matches!(
305-
status,
306-
StatusCode::MOVED_PERMANENTLY
307-
| StatusCode::FOUND
308-
| StatusCode::SEE_OTHER
309-
| StatusCode::TEMPORARY_REDIRECT
310-
| StatusCode::PERMANENT_REDIRECT
311-
)
312-
}
313-
314-
fn resolve_location(base: &Uri, location: &http::HeaderValue) -> Result<Uri, HttpClientError> {
315-
let loc_str = location
316-
.to_str()
317-
.map_err(|_| HttpClientError::Uri("bad location header"))?;
172+
let resp = sender.send_request(req).await?;
173+
let (parts, body) = resp.into_parts();
318174

319-
// Absolute URI
320-
if let Ok(uri) = loc_str.parse::<Uri>() {
321-
if uri.scheme().is_some() && uri.authority().is_some() {
322-
return Ok(uri);
323-
}
324-
}
175+
let body = http_body_util::Limited::new(body, NGX_ACME_MAX_BODY_SIZE)
176+
.collect()
177+
.await
178+
.map_err(HttpClientError::Body)?
179+
.to_bytes();
325180

326-
// Relative forms
327-
if loc_str.starts_with('/') {
328-
// absolute-path reference
329-
let mut builder = Uri::builder();
330-
if let Some(scheme) = base.scheme() {
331-
builder = builder.scheme(scheme.clone());
332-
}
333-
if let Some(auth) = base.authority() {
334-
builder = builder.authority(auth.clone());
335-
}
336-
builder
337-
.path_and_query(loc_str)
338-
.build()
339-
.map_err(|_| HttpClientError::Uri("bad redirect path"))
340-
} else {
341-
// path-relative; join with base path directory
342-
let base_path = base.path_and_query().map(|pq| pq.as_str()).unwrap_or("/");
343-
let joined = if let Some(idx) = base_path.rfind('/') {
344-
format!("{}{}", &base_path[..=idx], loc_str)
345-
} else {
346-
format!("/{}", loc_str)
347-
};
348-
let mut builder = Uri::builder();
349-
if let Some(scheme) = base.scheme() {
350-
builder = builder.scheme(scheme.clone());
351-
}
352-
if let Some(auth) = base.authority() {
353-
builder = builder.authority(auth.clone());
354-
}
355-
builder
356-
.path_and_query(joined.as_str())
357-
.build()
358-
.map_err(|_| HttpClientError::Uri("bad relative redirect path"))
181+
Ok(Response::from_parts(parts, body))
359182
}
360183
}

0 commit comments

Comments
 (0)