Commit 49230f5
fix(netty): preserve all redirect body types (#2316)
## Summary
- Build keep-body redirects from the original request, preserving every
supported body representation and per-request setting.
- Clear target-specific routing and credential state when a redirect
crosses an origin, including separately stored Cookie objects.
- Preserve an explicit `Content-Length` when replaying a raw
`InputStream` or an unknown-length `InputStreamBodyGenerator`.
- Replay resettable streams and fail promptly for consumed raw streams,
streamed multipart parts, and vanished files that cannot be replayed
safely.
- Pin body bytes, headers, body-selection precedence, caller-owned
`ByteBuf` references, and the new failure modes with focused tests.
## Problem
`Redirect30xInterceptor` rebuilds a request when it follows a strict
302, 307, or 308 redirect. Its keep-body copy chain handled form
parameters, strings, byte arrays, `ByteBuffer`, body generators, and
multipart bodies, but omitted four real request send paths:
- `List<byte[]>` / composite byte arrays
- Netty `ByteBuf`
- `InputStream`
- `File`
The redirected request therefore kept its method and Content-Type but
sent zero bytes. The `File` case is especially risky for uploads because
the target can accept an apparently valid empty PUT or POST.
Reconstructing the request field by field also omitted unrelated
per-request state such as the read timeout and range offset, and
maintaining a second body-selection chain alongside
`NettyRequestFactory` made future drift likely.
This is a pre-existing omission. AHC issue
[#1643](#1643)
previously fixed the same class of bug for multipart bodies. The copy
chain was carried through pull request
[#1843](#1843)
without a policy discussion. Focused searches found no existing issue or
pull request covering these four representations.
## Change
Build a keep-body redirect with `request.toBuilder()` and then replace
only redirect-specific state. This preserves all current and future body
representations and per-request options without duplicating
`NettyRequestFactory.body`. Headers are copied before redirect-only
values are removed, so the original request is not mutated.
On a cross-origin redirect, the copied request drops the previous
resolved address, virtual host, realm, authorization headers, and Cookie
objects before the cookie store adds cookies that legitimately match the
new URI. The body is not covered by that boundary. It follows the
existing keep-body policy, which means a `File` or `InputStream` body
that a cross-origin redirect leg previously received as empty is now
sent in full, and a target that keeps redirecting can receive it once
per hop up to `maxRedirects`. That is the same exposure byte arrays,
strings, form parameters, and multipart bodies already have today.
Composite byte arrays, caller-owned `ByteBuf`s, and files are
repeatable. A resettable `InputStream`, such as `ByteArrayInputStream`,
also replays. A caller-supplied `Content-Length` is retained for a raw
`InputStream` or an `InputStreamBodyGenerator` without a declared
length, because neither has an intrinsic size from which to recompute
it. A consumed stream that cannot be reset reaches the existing
fail-fast guard added in #2312 and completes the future with
`IOException`; that is preferable to silently succeeding with an empty
body.
An `InputStreamPart` is closed by the first multipart send and has no
equivalent replay guard, so a keep-body redirect now fails promptly
instead of risking a hang or incomplete multipart request. A selected
`File` or `FileBodyGenerator` is also checked before dispatching the
redirect; if it disappeared after the first send, the future fails with
`IOException` before a target pooled channel can be removed and an
unchecked constructor exception can escape. The validation follows
`NettyRequestFactory` precedence so a sticky `File` field is ignored
when a higher-priority body representation was actually sent.
The change does not alter which methods or status codes keep a body, nor
does it introduce a new cross-origin policy. It makes the existing
strict-302, 307, and 308 behavior complete for every supported
request-body representation.
## Compatibility
There is no public API change. Requests that previously sent an empty
body on a keep-body redirect now resend their configured body.
**Behavior changes:**
- A non-resettable `InputStream` on a keep-body redirect previously
completed successfully after sending an empty redirected request. It now
completes the request future exceptionally with `IOException`. This
includes `FileInputStream`, which is closed after the first send and
cannot be reset for replay.
- A multipart `InputStreamPart` now fails promptly with `IOException`
when a keep-body redirect requires replay. Reusing its already-consumed
and closed stream could previously hang or send incomplete multipart
content.
- A selected file that disappears between the first request and redirect
now fails with `IOException` before redirect dispatch rather than
allowing an unchecked `IllegalArgumentException` to escape while
constructing the next request.
- A `File` or `InputStream` body is now sent on a keep-body redirect to
a different origin, where the redirected request previously carried no
body. Credentials are still stripped at that boundary, but the payload
is not.
Callers that accidentally relied on an empty or incomplete redirected
request will observe an exception, but the failure is explicit instead
of silently losing configured content. There is no public API change.
## AI disclosure
OpenAI Codex on behalf of Matthias Kurz. The commit includes
`Co-Authored-By: OpenAI Codex <codex@openai.com>` per `AGENTS.md`.
## Test plan
- [x] On untouched `upstream/main`, the focused suite reproduced five
failures: four body types arrived as zero bytes and a non-resettable
stream incorrectly completed successfully.
- [x] Before the generator fix, a one-argument
`InputStreamBodyGenerator` sent `Content-Length: 13` on the first leg
and no `Content-Length` on the redirected leg.
- [x] `./mvnw -pl client
-Dtest=RedirectBodyTest,RedirectCredentialSecurityTest test` on JDK 11:
40 tests passed, including Netty leak detection.
- [x] `./mvnw clean verify` on JDK 11: 1,496 tests passed and Revapi
completed without failures (`BUILD SUCCESS`).
Generated with OpenAI Codex.
---------
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: Aayush Atharva <24762260+hyperxpro@users.noreply.github.com>1 parent 492eb2d commit 49230f5
3 files changed
Lines changed: 446 additions & 36 deletions
File tree
- client/src
- main/java/org/asynchttpclient/netty/handler/intercept
- test/java/org/asynchttpclient
Lines changed: 99 additions & 30 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
35 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
36 | 40 | | |
37 | 41 | | |
38 | 42 | | |
39 | 43 | | |
| 44 | + | |
| 45 | + | |
40 | 46 | | |
41 | 47 | | |
42 | 48 | | |
| |||
56 | 62 | | |
57 | 63 | | |
58 | 64 | | |
59 | | - | |
60 | 65 | | |
61 | 66 | | |
62 | 67 | | |
| |||
132 | 137 | | |
133 | 138 | | |
134 | 139 | | |
135 | | - | |
136 | | - | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
137 | 159 | | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | 160 | | |
| 161 | + | |
142 | 162 | | |
143 | 163 | | |
144 | 164 | | |
| |||
154 | 174 | | |
155 | 175 | | |
156 | 176 | | |
| 177 | + | |
| 178 | + | |
157 | 179 | | |
158 | 180 | | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | | - | |
169 | | - | |
170 | | - | |
171 | | - | |
172 | | - | |
173 | | - | |
174 | | - | |
175 | | - | |
176 | | - | |
177 | | - | |
178 | 181 | | |
179 | 182 | | |
180 | 183 | | |
| |||
192 | 195 | | |
193 | 196 | | |
194 | 197 | | |
195 | | - | |
| 198 | + | |
196 | 199 | | |
197 | 200 | | |
198 | 201 | | |
| |||
229 | 232 | | |
230 | 233 | | |
231 | 234 | | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
232 | 298 | | |
233 | | - | |
234 | | - | |
235 | | - | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
236 | 305 | | |
237 | 306 | | |
238 | 307 | | |
| |||
0 commit comments