diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/DefaultRedirectStrategy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/DefaultRedirectStrategy.java new file mode 100644 index 0000000000000..6e4ddfd7929e8 --- /dev/null +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/DefaultRedirectStrategy.java @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.core.http.policy; + +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.util.CoreUtils; +import com.azure.core.util.logging.ClientLogger; + +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A default implementation of {@link RedirectStrategy} that uses the provided try count, header name, http methods + * and status code to determine if request should be redirected. + */ +public final class DefaultRedirectStrategy implements RedirectStrategy { + private final ClientLogger logger = new ClientLogger(DefaultRedirectStrategy.class); + + // Based on Stamp specific redirects design doc + private static final int DEFAULT_MAX_REDIRECT_ATTEMPTS = 10; + private static final String DEFAULT_REDIRECT_LOCATION_HEADER_NAME = "Location"; + private static final int PERMANENT_REDIRECT_STATUS_CODE = 308; + private static final HashMap DEFAULT_ALLOWED_METHODS = new HashMap() { + { + put(HttpMethod.GET.ordinal(), HttpMethod.GET); + put(HttpMethod.HEAD.ordinal(), HttpMethod.HEAD); + } + }; + + private final int maxAttempts; + private final String locationHeader; + private final Map redirectMethods; + private final Set attemptedRedirectUrls = new HashSet<>(); + + /** + * Creates an instance of {@link DefaultRedirectStrategy} with a maximum number of redirect attempts 10, + * header name "Location" to locate the redirect url in the response headers and {@link HttpMethod#GET} + * and {@link HttpMethod#HEAD} as allowed methods for performing the redirect. + * + * @throws NullPointerException if {@code locationHeader} is {@code null}. + * @throws IllegalArgumentException if {@code maxAttempts} is less than 0. + */ + public DefaultRedirectStrategy() { + this(DEFAULT_MAX_REDIRECT_ATTEMPTS, DEFAULT_REDIRECT_LOCATION_HEADER_NAME, new HashMap() { + { + put(HttpMethod.GET.ordinal(), HttpMethod.GET); + put(HttpMethod.HEAD.ordinal(), HttpMethod.HEAD); + } + }); + } + + /** + * Creates an instance of {@link DefaultRedirectStrategy} with the provided number of redirect attempts. + * + * @param maxAttempts The max number of redirect attempts that can be made. + * @throws NullPointerException if {@code locationHeader} is {@code null}. + * @throws IllegalArgumentException if {@code maxAttempts} is less than 0. + */ + public DefaultRedirectStrategy(int maxAttempts) { + this(maxAttempts, DEFAULT_REDIRECT_LOCATION_HEADER_NAME, DEFAULT_ALLOWED_METHODS); + } + + /** + * Creates an instance of {@link DefaultRedirectStrategy}. + * + * @param maxAttempts The max number of redirect attempts that can be made. + * @param locationHeader The header name containing the redirect URL. + * @param allowedMethods The set of {@link HttpMethod} that are allowed to be redirected. + * @throws IllegalArgumentException if {@code maxAttempts} is less than 0. + */ + public DefaultRedirectStrategy(int maxAttempts, String locationHeader, Map allowedMethods) { + if (maxAttempts < 0) { + ClientLogger logger = new ClientLogger(DefaultRedirectStrategy.class); + throw logger.logExceptionAsError(new IllegalArgumentException("Max attempts cannot be less than 0.")); + } + this.maxAttempts = maxAttempts; + this.locationHeader = locationHeader == null ? DEFAULT_REDIRECT_LOCATION_HEADER_NAME : locationHeader; + this.redirectMethods = allowedMethods == null ? DEFAULT_ALLOWED_METHODS : allowedMethods; + } + + @Override + public boolean shouldAttemptRedirect(HttpResponse httpResponse, int tryCount) { + String redirectUrl = + tryGetRedirectHeader(httpResponse.getHeaders(), this.getLocationHeader()); + + if (isValidRedirectCount(tryCount) + && !alreadyAttemptedRedirectUrl(redirectUrl) + && isValidRedirectStatusCode(httpResponse.getStatusCode()) + && isAllowedRedirectMethod(httpResponse.getRequest().getHttpMethod())) { + logger.verbose("[Redirecting] Try count: {}, Attempted Redirect URLs: {}", tryCount, + attemptedRedirectUrls.toString()); + return true; + } else { + return false; + } + } + + @Override + public HttpRequest createRedirect(HttpResponse httpResponse) { + String responseLocation = + tryGetRedirectHeader(httpResponse.getHeaders(), this.getLocationHeader()); + if (responseLocation != null) { + attemptedRedirectUrls.add(responseLocation); + return httpResponse.getRequest().setUrl(responseLocation); + } else { + return httpResponse.getRequest(); + } + } + + @Override + public int getMaxAttempts() { + return maxAttempts; + } + + @Override + public String getLocationHeader() { + return locationHeader; + } + + @Override + public Map getAllowedMethods() { + return redirectMethods; + } + + /** + * Check if the redirect url provided in the response headers is already attempted. + * + * @param redirectUrl the redirect url provided in the response header. + * @return {@code true} if the redirectUrl provided in the response header is already being attempted for redirect. + */ + private boolean alreadyAttemptedRedirectUrl(String redirectUrl) { + if (attemptedRedirectUrls.contains(redirectUrl)) { + logger.error(String.format("Request was redirected more than once to: %s", redirectUrl)); + return true; + } + return false; + } + + /** + * Check if the attempt count of the redirect is less than the {@code maxAttempts} + * + * @param tryCount the try count for the HTTP request associated to the HTTP response. + * @return {@code true} if the {@code tryCount} is greater than the {@code maxAttempts}. + */ + private boolean isValidRedirectCount(int tryCount) { + if (tryCount >= getMaxAttempts()) { + logger.error(String.format("Request has been redirected more than %d times.", getMaxAttempts())); + return false; + } + return true; + } + + /** + * Check if the request http method is a valid redirect method. + * + * @param httpMethod the http method of the request. + * @return {@code true} if the request {@code httpMethod} is a valid http redirect method. + */ + private boolean isAllowedRedirectMethod(HttpMethod httpMethod) { + if (getAllowedMethods().containsKey(httpMethod.ordinal())) { + return true; + } else { + logger.error( + String.format("Request was redirected from a non redirect-able method: %s", httpMethod)); + return false; + } + } + + /** + * Checks if the incoming request status code is a valid redirect status code. + * + * @param statusCode the status code of the incoming request. + * @return {@code true} if the request {@code statusCode} is a valid http redirect method. + */ + private boolean isValidRedirectStatusCode(int statusCode) { + return statusCode == HttpURLConnection.HTTP_MOVED_TEMP + || statusCode == HttpURLConnection.HTTP_MOVED_PERM + || statusCode == PERMANENT_REDIRECT_STATUS_CODE; + } + + /** + * Gets the redirect url from the response headers. + * + * @param headers the http response headers. + * @param headerName the header name to look up value for. + * @return the header value for the provided header name. + */ + private static String tryGetRedirectHeader(HttpHeaders headers, String headerName) { + String headerValue = headers.getValue(headerName); + return CoreUtils.isNullOrEmpty(headerValue) ? null : headerValue; + } +} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/MaxAttemptRedirectStrategy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/MaxAttemptRedirectStrategy.java deleted file mode 100644 index 4a382c8c0967c..0000000000000 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/MaxAttemptRedirectStrategy.java +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.core.http.policy; - -import com.azure.core.http.HttpMethod; -import com.azure.core.util.logging.ClientLogger; - -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -/** - * A default implementation of {@link RedirectStrategy} that uses the provided try count, and status code - * to determine if request should be redirected. - */ -public class MaxAttemptRedirectStrategy implements RedirectStrategy { - - // Based on Stamp specific redirects design doc - static final int MAX_REDIRECT_ATTEMPTS = 10; - private static final String LOCATION_HEADER_NAME = "Location"; - - private int maxAttempts; - private final String locationHeader; - private final Set redirectMethods; - - /** - * Creates an instance of {@link MaxAttemptRedirectStrategy}. - * - * @param maxAttempts The max number of redirect attempts that can be made. - * @param locationHeader The header name containing the redirect URL. - * @param allowedMethods The set of {@link HttpMethod} that are allowed to be redirected. - * - * @throws NullPointerException if {@code locationHeader} is {@code null}. - */ - public MaxAttemptRedirectStrategy(int maxAttempts, String locationHeader, Set allowedMethods) { - this.maxAttempts = maxAttempts; - this.locationHeader = Objects.requireNonNull(locationHeader, "'locationHeader' cannot be null."); - this.redirectMethods = allowedMethods; - } - - /** - * Creates an instance of {@link MaxAttemptRedirectStrategy}. - * - * @param maxAttempts The max number of redirect attempts that can be made. - */ - public MaxAttemptRedirectStrategy(int maxAttempts) { - this.maxAttempts = maxAttempts; - if (maxAttempts < 0){ - ClientLogger logger = new ClientLogger(MaxAttemptRedirectStrategy.class); - throw logger.logExceptionAsError(new IllegalArgumentException("Max attempts cannot be less than 0.")); - } - this.locationHeader = LOCATION_HEADER_NAME; - this.redirectMethods = new HashSet() { - { - add(HttpMethod.GET); - add(HttpMethod.HEAD); - } - }; - } - - @Override - public int getMaxAttempts() { - return maxAttempts; - } - - @Override - public boolean shouldAttemptRedirect(String redirectUrl, int tryCount, int maxRedirects, - Set attemptedRedirectUrls) { - if (tryCount >= maxRedirects) { - logger.error(String.format("Request has been redirected more than %d times.", MAX_REDIRECT_ATTEMPTS)); - return false; - } - - if (attemptedRedirectUrls.contains(redirectUrl)) { - logger.error(String.format("Request was redirected more than once to: %s", redirectUrl)); - return false; - } - return true; - } - - @Override - public String getLocationHeader() { - return locationHeader; - } - - @Override - public Set getAllowedMethods() { - return redirectMethods; - } -} diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectPolicy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectPolicy.java index 1798975124c32..89b9e95e7038b 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectPolicy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectPolicy.java @@ -3,45 +3,31 @@ package com.azure.core.http.policy; -import com.azure.core.http.HttpHeaders; -import com.azure.core.http.HttpMethod; import com.azure.core.http.HttpPipelineCallContext; import com.azure.core.http.HttpPipelineNextPolicy; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; -import com.azure.core.util.CoreUtils; import reactor.core.publisher.Mono; -import java.net.HttpURLConnection; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; /** - * A HttpPipeline policy that redirects when an HTTP Redirect is received as response. + * A {@link HttpPipelinePolicy} that redirects a {@link HttpRequest} when an HTTP Redirect is received as response. */ public final class RedirectPolicy implements HttpPipelinePolicy { - - private static final int PERMANENT_REDIRECT_STATUS_CODE = 308; - - // Based on Stamp specific redirects design doc - private static final int MAX_REDIRECT_ATTEMPTS = 10; private final RedirectStrategy redirectStrategy; - private Set attemptedRedirectUrls = new HashSet<>(); - private String redirectedEndpointUrl; - /** - * Creates {@link RedirectPolicy} with default {@link MaxAttemptRedirectStrategy} as {@link RedirectStrategy} and + * Creates {@link RedirectPolicy} with default {@link DefaultRedirectStrategy} as {@link RedirectStrategy} and * use the provided {@code statusCode} to determine if this request should be redirected * and MAX_REDIRECT_ATTEMPTS for the try count. */ public RedirectPolicy() { - this(new MaxAttemptRedirectStrategy(MAX_REDIRECT_ATTEMPTS)); + this(new DefaultRedirectStrategy()); } /** - * Creates {@link RedirectPolicy} with default {@link MaxAttemptRedirectStrategy} as {@link RedirectStrategy} and + * Creates {@link RedirectPolicy} with default {@link DefaultRedirectStrategy} as {@link RedirectStrategy} and * use the provided {@code statusCode} to determine if this request should be redirected. * * @param redirectStrategy The {@link RedirectStrategy} used for redirection. @@ -64,40 +50,19 @@ private Mono attemptRedirect(final HttpPipelineCallContext context final HttpPipelineNextPolicy next, final HttpRequest originalHttpRequest, final int redirectAttempt) { - // make sure the context is not modified during redirect, except for the URL context.setHttpRequest(originalHttpRequest.copy()); - if (this.redirectedEndpointUrl != null) { - context.getHttpRequest().setUrl(this.redirectedEndpointUrl); - } + return next.clone().process() .flatMap(httpResponse -> { - String responseLocation = - tryGetRedirectHeader(httpResponse.getHeaders(), redirectStrategy.getLocationHeader()); - if (isValidRedirectStatusCode(httpResponse.getStatusCode()) && - isAllowedRedirectMethod(httpResponse.getRequest().getHttpMethod()) && - responseLocation != null && - redirectStrategy.shouldAttemptRedirect(responseLocation, redirectAttempt, - redirectStrategy.getMaxAttempts(), attemptedRedirectUrls)) { - attemptedRedirectUrls.add(responseLocation); - this.redirectedEndpointUrl = responseLocation; - return attemptRedirect(context, next, originalHttpRequest, redirectAttempt + 1); + if (redirectStrategy.shouldAttemptRedirect(httpResponse, redirectAttempt)) { + HttpRequest redirectRequestCopy = redirectStrategy.createRedirect(httpResponse); + return httpResponse.getBody() + .ignoreElements() + .then(attemptRedirect(context, next, redirectRequestCopy, redirectAttempt + 1)); + } else { + return Mono.just(httpResponse); } - return Mono.just(httpResponse); }); } - private boolean isAllowedRedirectMethod(HttpMethod httpMethod) { - return redirectStrategy.getAllowedMethods().contains(httpMethod); - } - - private boolean isValidRedirectStatusCode(int statusCode) { - return statusCode == HttpURLConnection.HTTP_MOVED_TEMP - || statusCode == HttpURLConnection.HTTP_MOVED_PERM - || statusCode == PERMANENT_REDIRECT_STATUS_CODE; - } - - private static String tryGetRedirectHeader(HttpHeaders headers, String headerName) { - String headerValue = headers.getValue(headerName); - return CoreUtils.isNullOrEmpty(headerValue) ? null : headerValue; - } } diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectStrategy.java b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectStrategy.java index 4b2f30322e688..650506b6228d4 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectStrategy.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/RedirectStrategy.java @@ -4,16 +4,15 @@ package com.azure.core.http.policy; import com.azure.core.http.HttpMethod; -import com.azure.core.util.logging.ClientLogger; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; -import java.util.Set; +import java.util.Map; /** * The interface for determining the redirect strategy used in {@link RedirectPolicy}. */ public interface RedirectStrategy { - ClientLogger logger = new ClientLogger(RedirectStrategy.class); - /** * Max number of redirect attempts to be made. * @@ -29,19 +28,23 @@ public interface RedirectStrategy { /** * @return the set of redirect allowed methods. */ - Set getAllowedMethods(); + Map getAllowedMethods(); /** * Determines if the url should be redirected between each try. * - * @param redirectUrl the redirect url present in the response headers + * @param httpResponse the {@link HttpRequest} containing the redirect url present in the response headers * @param tryCount redirect attempts so far - * @param maxRedirects maximum number of redirects allowed - * @param attemptedRedirectUrls attempted redirect locations so far - * * @return {@code true} if the request should be redirected, {@code false} * otherwise */ - boolean shouldAttemptRedirect(String redirectUrl, int tryCount, int maxRedirects, - Set attemptedRedirectUrls); + boolean shouldAttemptRedirect(HttpResponse httpResponse, int tryCount); + + /** + * Creates the {@link HttpRequest request} for the redirect attempt. + * + * @param httpResponse the {@link HttpRequest} containing the redirect url present in the response headers + * @return the modified {@link HttpRequest} to redirect the incoming request. + */ + HttpRequest createRedirect(HttpResponse httpResponse); } diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/http/policy/RedirectPolicyTest.java b/sdk/core/azure-core/src/test/java/com/azure/core/http/policy/RedirectPolicyTest.java index c802ac175003a..6b4b60a740a3e 100644 --- a/sdk/core/azure-core/src/test/java/com/azure/core/http/policy/RedirectPolicyTest.java +++ b/sdk/core/azure-core/src/test/java/com/azure/core/http/policy/RedirectPolicyTest.java @@ -26,6 +26,31 @@ public class RedirectPolicyTest { + @Test + public void noRedirectPolicyTest() throws Exception { + final HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(new NoOpHttpClient() { + + @Override + public Mono send(HttpRequest request) { + if (request.getUrl().toString().equals("http://localhost/")) { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/"); + HttpHeaders httpHeader = new HttpHeaders(headers); + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else { + return Mono.just(new MockHttpResponse(request, 200)); + } + } + }) + .build(); + + HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET, + new URL("http://localhost/"))).block(); + + assertEquals(308, response.getStatusCode()); + } + @Test public void defaultRedirectWhen308() throws Exception { RecordingHttpClient httpClient = new RecordingHttpClient(request -> { @@ -55,50 +80,171 @@ public void defaultRedirectWhen308() throws Exception { public void redirectForNAttempts() throws MalformedURLException { final int[] requestCount = {1}; RecordingHttpClient httpClient = new RecordingHttpClient(request -> { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/" + requestCount[0]); + HttpHeaders httpHeader = new HttpHeaders(headers); + requestCount[0]++; + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + }); + + HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(httpClient) + .policies(new RedirectPolicy(new DefaultRedirectStrategy(5))) + .build(); + + HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET, + new URL("http://localhost/"))).block(); + + assertEquals(5, httpClient.getCount()); + assertEquals(308, response.getStatusCode()); + } + + @Test + public void redirectNonAllowedMethodTest() throws Exception { + RecordingHttpClient httpClient = new RecordingHttpClient(request -> { + if (request.getUrl().toString().equals("http://localhost/")) { Map headers = new HashMap<>(); - headers.put("Location", "http://redirecthost/" + requestCount[0]); + headers.put("Location", "http://redirecthost/"); + HttpHeaders httpHeader = new HttpHeaders(headers); + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else { + return Mono.just(new MockHttpResponse(request, 200)); + } + }); + + HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(httpClient) + .policies(new RedirectPolicy(new DefaultRedirectStrategy(5))) + .build(); + + HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.POST, + new URL("http://localhost/"))).block(); + + // not redirected to 200 + assertEquals(1, httpClient.getCount()); + assertEquals(308, response.getStatusCode()); + } + + @Test + public void redirectAllowedStatusCodesTest() throws Exception { + RecordingHttpClient httpClient = new RecordingHttpClient(request -> { + if (request.getUrl().toString().equals("http://localhost/")) { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/"); HttpHeaders httpHeader = new HttpHeaders(headers); - requestCount[0]++; return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else { + return Mono.just(new MockHttpResponse(request, 200)); + } }); HttpPipeline pipeline = new HttpPipelineBuilder() .httpClient(httpClient) - .policies(new RedirectPolicy(new MaxAttemptRedirectStrategy(5))) + .policies(new RedirectPolicy(new DefaultRedirectStrategy())) .build(); HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost/"))).block(); - assertEquals(5, httpClient.getCount()); + assertEquals(2, httpClient.getCount()); + assertEquals(200, response.getStatusCode()); + } + + @Test + public void alreadyAttemptedUrlsTest() throws Exception { + RecordingHttpClient httpClient = new RecordingHttpClient(request -> { + if (request.getUrl().toString().equals("http://localhost/")) { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/"); + HttpHeaders httpHeader = new HttpHeaders(headers); + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else if (request.getUrl().toString().equals("http://redirecthost/")) { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/"); + HttpHeaders httpHeader = new HttpHeaders(headers); + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else { + return Mono.just(new MockHttpResponse(request, 200)); + } + }); + + HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(httpClient) + .policies(new RedirectPolicy(new DefaultRedirectStrategy())) + .build(); + + HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET, + new URL("http://localhost/"))).block(); + + assertEquals(2, httpClient.getCount()); assertEquals(308, response.getStatusCode()); } @Test - public void noRedirectPolicyTest() throws Exception { - final HttpPipeline pipeline = new HttpPipelineBuilder() - .httpClient(new NoOpHttpClient() { + public void redirectForProvidedHeader() throws MalformedURLException { + final int[] requestCount = {1}; + RecordingHttpClient httpClient = new RecordingHttpClient(request -> { + Map headers = new HashMap<>(); + headers.put("Location1", "http://redirecthost/" + requestCount[0]); + HttpHeaders httpHeader = new HttpHeaders(headers); + requestCount[0]++; + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + }); - @Override - public Mono send(HttpRequest request) { - if (request.getUrl().toString().equals("http://localhost/")) { - Map headers = new HashMap<>(); - headers.put("Location", "http://redirecthost/"); - HttpHeaders httpHeader = new HttpHeaders(headers); - return Mono.just(new MockHttpResponse(request, 308, httpHeader)); - } else { - return Mono.just(new MockHttpResponse(request, 200)); - } - } - }) + HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(httpClient) + .policies(new RedirectPolicy(new DefaultRedirectStrategy(5, "Location1", null))) .build(); HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost/"))).block(); + assertEquals(5, httpClient.getCount()); assertEquals(308, response.getStatusCode()); } + @Test + public void redirectForProvidedMethods() throws MalformedURLException { + HashMap allowedMethods = new HashMap<>() { + { + put(HttpMethod.GET.ordinal(), HttpMethod.GET); + put(HttpMethod.PUT.ordinal(), HttpMethod.PUT); + put(HttpMethod.POST.ordinal(), HttpMethod.POST); + } + }; + final int[] requestCount = {1}; + RecordingHttpClient httpClient = new RecordingHttpClient(request -> { + if (request.getUrl().toString().equals("http://localhost/")) { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/" + requestCount[0]++); + HttpHeaders httpHeader = new HttpHeaders(headers); + request.setHttpMethod(HttpMethod.PUT); + requestCount[0]++; + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else if (request.getUrl().toString().equals("http://redirecthost/" + requestCount[0]) + && requestCount[0] == 2) { + Map headers = new HashMap<>(); + headers.put("Location", "http://redirecthost/" + requestCount[0]++); + HttpHeaders httpHeader = new HttpHeaders(headers); + request.setHttpMethod(HttpMethod.POST); + return Mono.just(new MockHttpResponse(request, 308, httpHeader)); + } else { + return Mono.just(new MockHttpResponse(request, 200)); + } + }); + + HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(httpClient) + .policies(new RedirectPolicy(new DefaultRedirectStrategy(5, null, allowedMethods))) + .build(); + + HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET, + new URL("http://localhost/"))).block(); + + assertEquals(2, httpClient.getCount()); + assertEquals(200, response.getStatusCode()); + } + static class RecordingHttpClient implements HttpClient { private final AtomicInteger count = new AtomicInteger();