-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Enhance
RedirectingHttpRequesterFilter
: redirect headers and messag…
…e body (#1792) Motivation: With pre-existing implementation of `RedirectingHttpRequesterFilter` its hard for users to redirect headers and a message body. There is an opportunity to improve API for request customization and automatically redirect those components for relative redirects. Modifications: - Introduce `RedirectConfig` and `RedirectConfigBuilder` for extensive redirect configuration; - Automatically redirect headers and message body for relative locations; - Allow configuring what methods should be redirected; - Let users provide a custom predicate to avoid redirects in some conditions; - Make "change POST -> GET" behavior opt-in; - Give users access to make any changes for the redirecting request, keeping the context of the original request and redirect response; - Deprecate `MultiAddressHttpClientBuilder#maxRedirects`; - Add `MultiAddressHttpClientBuilder#followRedirects(RedirectConfig)`; - Add `RedirectingHttpRequesterFilter(RedirectConfig)` ctor; - Deprecate unnecessary ctors for `RedirectingHttpRequesterFilter`; - Update tests and improve test coverage; - Update redirect examples; Result: Full automatic handling for relative redirects, extensible API to configure non-relative redirects, simplified user experience.
- Loading branch information
1 parent
759f589
commit 5cc648c
Showing
23 changed files
with
1,735 additions
and
722 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
...s/src/main/java/io/servicetalk/examples/http/redirects/MultiAddressUrlRedirectClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* Copyright © 2021 Apple Inc. and the ServiceTalk project authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.servicetalk.examples.http.redirects; | ||
|
||
import io.servicetalk.http.api.HttpClient; | ||
import io.servicetalk.http.api.HttpRequestMethod; | ||
import io.servicetalk.http.api.MultiAddressHttpClientBuilder; | ||
import io.servicetalk.http.api.RedirectConfig; | ||
import io.servicetalk.http.api.RedirectConfigBuilder; | ||
import io.servicetalk.http.netty.HttpClients; | ||
import io.servicetalk.test.resources.DefaultTestCerts; | ||
import io.servicetalk.transport.api.ClientSslConfigBuilder; | ||
|
||
import static io.servicetalk.examples.http.redirects.RedirectingServer.CUSTOM_HEADER; | ||
import static io.servicetalk.examples.http.redirects.RedirectingServer.NON_SECURE_SERVER_PORT; | ||
import static io.servicetalk.examples.http.redirects.RedirectingServer.SECURE_SERVER_PORT; | ||
import static io.servicetalk.http.api.HttpHeaderNames.LOCATION; | ||
import static io.servicetalk.http.api.HttpRequestMethod.GET; | ||
import static io.servicetalk.http.api.HttpRequestMethod.POST; | ||
import static io.servicetalk.http.api.HttpSerializationProviders.textDeserializer; | ||
|
||
/** | ||
* Async `Hello World` example that demonstrates how redirects can be handled automatically by a | ||
* {@link HttpClients#forMultiAddressUrl() multi-address} client. It demonstrates how users can preserve headers and | ||
* payload body of the original request while redirecting to non-relative locations. | ||
* <p> | ||
* For security reasons, request methods other than {@link HttpRequestMethod#GET GET} or | ||
* {@link HttpRequestMethod#HEAD HEAD}, headers and message body are not automatically redirected for non-relative | ||
* locations. Users have to explicitly configure what should be redirected when they are sure that redirect does not | ||
* forward to a malicious target server. Relative redirects always carry forward headers and message body. For more | ||
* information, see {@link MultiAddressHttpClientBuilder#followRedirects(RedirectConfig)} and | ||
* {@link RedirectConfigBuilder}. | ||
*/ | ||
public final class MultiAddressUrlRedirectClient { | ||
|
||
public static void main(String... args) throws Exception { | ||
try (HttpClient client = HttpClients.forMultiAddressUrl() | ||
// Enables redirection: | ||
.followRedirects(new RedirectConfigBuilder() | ||
// All following config options are optional: | ||
.maxRedirects(3) | ||
// by default, only relative redirects are allowed | ||
.allowNonRelativeRedirects(true) | ||
// by default, POST requests don't follow redirects: | ||
.allowedMethods(GET, POST) | ||
// apply additional restrictions which redirects to follow: | ||
.redirectPredicate((relative, redirectCount, prevRequest, redirectResponse) -> | ||
relative // allow only relative redirects | ||
// OR non-relative redirects to a trusted server: | ||
|| redirectResponse.headers().get(LOCATION, "").toString() | ||
.startsWith("https://localhost:" + SECURE_SERVER_PORT)) | ||
// explicitly specify what headers should be redirected to non-relative locations: | ||
.headersToRedirect(CUSTOM_HEADER) | ||
// explicitly specify that payload body should be redirected to non-relative locations: | ||
.redirectPayloadBody(true) | ||
// custom modifications for a redirected request: | ||
.redirectRequestTransformer((relative, prevRequest, redirectResponse, redirectedRequest) -> { | ||
// if necessary, apply addition modifications for redirectedRequest based on the context of | ||
// prevRequest and redirectResponse: check/copy other headers, modify request method, etc. | ||
return redirectedRequest; | ||
}) | ||
.build()) | ||
.initializer((scheme, address, builder) -> { | ||
// The custom SSL configuration here is necessary only because this example uses self-signed | ||
// certificates. For cases when it's enough to use the local trust store, MultiAddressUrl client | ||
// already provides default SSL configuration and this step may be skipped. | ||
if ("https".equalsIgnoreCase(scheme)) { | ||
builder.sslConfig(new ClientSslConfigBuilder(DefaultTestCerts::loadServerCAPem).build()); | ||
} | ||
}) | ||
.build()) { | ||
|
||
final String serverThatRedirects = "http://localhost:" + NON_SECURE_SERVER_PORT; | ||
System.out.println("- Simple GET request:"); | ||
client.request(client.get(serverThatRedirects + "/relative")) | ||
.whenOnSuccess(resp -> { | ||
System.out.println(resp.toString((name, value) -> value)); | ||
System.out.println(resp.payloadBody(textDeserializer())); | ||
System.out.println(); | ||
}) | ||
// This example is demonstrating asynchronous execution, but needs to prevent the main thread from | ||
// exiting before the response has been processed. This isn't typical usage for an asynchronous API | ||
// but is useful for demonstration purposes. | ||
.toFuture().get(); | ||
|
||
System.out.println("- Relative redirect for POST request with headers and payload body:"); | ||
client.request(client.post(serverThatRedirects + "/relative") | ||
.addHeader(CUSTOM_HEADER, "value") | ||
.payloadBody(client.executionContext().bufferAllocator().fromAscii("some_content"))) | ||
.whenOnSuccess(resp -> { | ||
System.out.println(resp.toString((name, value) -> value)); | ||
System.out.println(resp.payloadBody(textDeserializer())); | ||
System.out.println(); | ||
}) | ||
// This example is demonstrating asynchronous execution, but needs to prevent the main thread from | ||
// exiting before the response has been processed. This isn't typical usage for an asynchronous API | ||
// but is useful for demonstration purposes. | ||
.toFuture().get(); | ||
|
||
System.out.println("- Non-relative redirect for POST request with headers and payload body:"); | ||
client.request(client.post(serverThatRedirects + "/non-relative") | ||
.addHeader(CUSTOM_HEADER, "value") | ||
.payloadBody(client.executionContext().bufferAllocator().fromAscii("some_content"))) | ||
.whenOnSuccess(resp -> { | ||
System.out.println(resp.toString((name, value) -> value)); | ||
System.out.println(resp.payloadBody(textDeserializer())); | ||
System.out.println(); | ||
}) | ||
// This example is demonstrating asynchronous execution, but needs to prevent the main thread from | ||
// exiting before the response has been processed. This isn't typical usage for an asynchronous API | ||
// but is useful for demonstration purposes. | ||
.toFuture().get(); | ||
} | ||
} | ||
} |
Oops, something went wrong.