-
Notifications
You must be signed in to change notification settings - Fork 181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix response leak that can be caused by an exception during redirect #3095
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ | |
import static io.servicetalk.http.api.HttpContextKeys.HTTP_EXECUTION_STRATEGY_KEY; | ||
import static io.servicetalk.http.api.HttpHeaderNames.HOST; | ||
import static io.servicetalk.http.api.HttpResponseStatus.StatusClass.REDIRECTION_3XX; | ||
import static io.servicetalk.utils.internal.ThrowableUtils.addSuppressed; | ||
|
||
/** | ||
* An operator, which implements redirect logic for {@link StreamingHttpClient}. | ||
|
@@ -184,14 +185,19 @@ public void onSuccess(@Nullable final StreamingHttpResponse response) { | |
LOGGER.trace("Executing redirect to '{}' for request '{}'", location, request); | ||
} | ||
|
||
// Consume any payload of the redirect response | ||
final Single<StreamingHttpResponse> nextResponse = response.messageBody().ignoreElements() | ||
.concat(redirectSingle.requester.request(newRequest)); | ||
final RedirectSubscriber redirectSubscriber = new RedirectSubscriber(target, redirectSingle, newRequest, | ||
redirectCount + 1, sequentialCancellable); | ||
terminalDelivered = true; // Mark as "delivered" because we do not own `target` from this point | ||
toSource(response.messageBody().ignoreElements() // Consume any payload of the redirect response | ||
.concat(redirectSingle.requester.request(newRequest))) | ||
.subscribe(new RedirectSubscriber(target, redirectSingle, newRequest, redirectCount + 1, | ||
sequentialCancellable)); | ||
toSource(nextResponse).subscribe(redirectSubscriber); | ||
} catch (Throwable cause) { | ||
if (!terminalDelivered) { | ||
safeOnError(target, cause); | ||
// Drain response payload body before propagating the cause | ||
sequentialCancellable.nextCancellable(response.messageBody().ignoreElements() | ||
.whenOnError(suppressed -> safeOnError(target, addSuppressed(cause, suppressed))) | ||
.subscribe(() -> safeOnError(target, cause))); | ||
} else { | ||
LOGGER.info("Ignoring exception from onSuccess of Subscriber {}.", target, cause); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the
If it is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic here is that we set the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we may be talking about different cases as I'm talking about those higher up which you didn't modify. For example:
There are a few others that follow the same pattern. Trying to determine if the body has been drained is perhaps too defensive but we also don't normally expect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change otherwise LGTM, but I'd also like to understand how the flow works in the case that @bryce-anderson outlined. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, if users have an unexpected exception inside an operator applied later (for example, |
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I recall asking this previously (sorry if dup-question), but what if the payload body is very large or doesn't complete (malicious client, networking broken, etc.)? Will our timeouts kick-in at this level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It depends on how users configure timeouts for their use cases. If they have it at the response payload body level, then yes.
We can add more protection as a separate work item and unify all places, bcz this is not the only place that drains payload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
followup PR sgtm