Support for re-writing Location Headers (#1863)
* Support for re-writing Location Headers * Added additional constructor for Route, removed auto-configuration of LocationRewriteFilter * Polished docs, more tests
This commit is contained in:
committed by
Spencer Gibb
parent
70a26fec26
commit
0579b326be
@@ -1864,6 +1864,32 @@ class MyFallbackProvider implements ZuulFallbackProvider {
|
||||
}
|
||||
----
|
||||
|
||||
[[zuul-redirect-location-rewrite]]
|
||||
=== Rewriting `Location` header
|
||||
|
||||
If Zuul is fronting a web application then there may be a need to re-write the `Location` header when the web application redirects through a http status code of 3XX, otherwise the browser will end up redirecting to the web application's url instead of the Zuul url.
|
||||
A `LocationRewriteFilter` Zuul filter can be configured to re-write the Location header to the Zuul's url, it also adds back the stripped global and route specific prefixes. The filter can be added the following way via a Spring Configuration file:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
|
||||
...
|
||||
|
||||
@Configuration
|
||||
@EnableZuulProxy
|
||||
public class ZuulConfig {
|
||||
@Bean
|
||||
public LocationRewriteFilter locationRewriteFilter() {
|
||||
return new LocationRewriteFilter();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Use this filter with caution though, the filter acts on the `Location` header of ALL 3XX response codes which may not be appropriate in all scenarios, say if the user is redirecting to an external URL.
|
||||
====
|
||||
|
||||
[[zuul-developer-guide]]
|
||||
=== Zuul Developer Guide
|
||||
|
||||
|
||||
@@ -42,6 +42,12 @@ public class Route {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Route(String id, String path, String location, String prefix,
|
||||
Boolean retryable, Set<String> ignoredHeaders, boolean prefixStripped) {
|
||||
this(id, path, location, prefix, retryable, ignoredHeaders);
|
||||
this.prefixStripped = prefixStripped;
|
||||
}
|
||||
|
||||
private String id;
|
||||
|
||||
@@ -58,6 +64,8 @@ public class Route {
|
||||
private Set<String> sensitiveHeaders = new LinkedHashSet<>();
|
||||
|
||||
private boolean customSensitiveHeaders;
|
||||
|
||||
private boolean prefixStripped = true;
|
||||
|
||||
public boolean isCustomSensitiveHeaders() {
|
||||
return this.customSensitiveHeaders;
|
||||
|
||||
@@ -151,7 +151,8 @@ public class SimpleRouteLocator implements RouteLocator, Ordered {
|
||||
}
|
||||
return new Route(route.getId(), targetPath, route.getLocation(), prefix,
|
||||
retryable,
|
||||
route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null);
|
||||
route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null,
|
||||
route.isStripPrefix());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -304,7 +304,8 @@ public class ZuulProperties {
|
||||
|
||||
public Route getRoute(String prefix) {
|
||||
return new Route(this.id, this.path, getLocation(), prefix, this.retryable,
|
||||
isCustomSensitiveHeaders() ? this.sensitiveHeaders : null);
|
||||
isCustomSensitiveHeaders() ? this.sensitiveHeaders : null,
|
||||
this.stripPrefix);
|
||||
}
|
||||
|
||||
public void setSensitiveHeaders(Set<String> headers) {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or 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 org.springframework.cloud.netflix.zuul.filters.post;
|
||||
|
||||
import com.netflix.util.Pair;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE;
|
||||
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_RESPONSE_FILTER_ORDER;
|
||||
|
||||
/**
|
||||
* {@link ZuulFilter} Responsible for rewriting the Location header to be the Zuul URL
|
||||
*
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
public class LocationRewriteFilter extends ZuulFilter {
|
||||
|
||||
private final UrlPathHelper urlPathHelper = new UrlPathHelper();
|
||||
|
||||
@Autowired
|
||||
private ZuulProperties zuulProperties;
|
||||
|
||||
@Autowired
|
||||
private RouteLocator routeLocator;
|
||||
|
||||
private static final String LOCATION_HEADER = "Location";
|
||||
|
||||
public LocationRewriteFilter() {
|
||||
}
|
||||
|
||||
public LocationRewriteFilter(ZuulProperties zuulProperties,
|
||||
RouteLocator routeLocator) {
|
||||
this.routeLocator = routeLocator;
|
||||
this.zuulProperties = zuulProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return POST_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return SEND_RESPONSE_FILTER_ORDER - 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
int statusCode = ctx.getResponseStatusCode();
|
||||
return HttpStatus.valueOf(statusCode).is3xxRedirection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
Route route = routeLocator.getMatchingRoute(
|
||||
urlPathHelper.getPathWithinApplication(ctx.getRequest()));
|
||||
|
||||
if (route != null) {
|
||||
Pair<String, String> lh = locationHeader(ctx);
|
||||
if (lh != null) {
|
||||
String location = lh.second();
|
||||
URI originalRequestUri = UriComponentsBuilder
|
||||
.fromHttpRequest(new ServletServerHttpRequest(ctx.getRequest()))
|
||||
.build().toUri();
|
||||
|
||||
UriComponentsBuilder redirectedUriBuilder = UriComponentsBuilder
|
||||
.fromUriString(location);
|
||||
|
||||
UriComponents redirectedUriComps = redirectedUriBuilder.build();
|
||||
|
||||
String newPath = getRestoredPath(this.zuulProperties, route,
|
||||
redirectedUriComps);
|
||||
|
||||
String modifiedLocation = redirectedUriBuilder
|
||||
.scheme(originalRequestUri.getScheme())
|
||||
.host(originalRequestUri.getHost())
|
||||
.port(originalRequestUri.getPort()).replacePath(newPath).build()
|
||||
.toUriString();
|
||||
|
||||
lh.setSecond(modifiedLocation);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getRestoredPath(ZuulProperties zuulProperties, Route route,
|
||||
UriComponents redirectedUriComps) {
|
||||
StringBuilder path = new StringBuilder();
|
||||
String redirectedPathWithoutGlobal = downstreamHasGlobalPrefix(zuulProperties)
|
||||
? redirectedUriComps.getPath()
|
||||
.substring(("/" + zuulProperties.getPrefix()).length())
|
||||
: redirectedUriComps.getPath();
|
||||
|
||||
if (downstreamHasGlobalPrefix(zuulProperties)) {
|
||||
path.append("/" + zuulProperties.getPrefix());
|
||||
}
|
||||
else {
|
||||
path.append(zuulHasGlobalPrefix(zuulProperties)
|
||||
? "/" + zuulProperties.getPrefix() : "");
|
||||
}
|
||||
|
||||
path.append(downstreamHasRoutePrefix(route) ? "" : "/" + route.getPrefix())
|
||||
.append(redirectedPathWithoutGlobal);
|
||||
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
private boolean downstreamHasGlobalPrefix(ZuulProperties zuulProperties) {
|
||||
return (!zuulProperties.isStripPrefix()
|
||||
&& StringUtils.hasText(zuulProperties.getPrefix()));
|
||||
}
|
||||
|
||||
private boolean zuulHasGlobalPrefix(ZuulProperties zuulProperties) {
|
||||
return StringUtils.hasText(zuulProperties.getPrefix());
|
||||
}
|
||||
|
||||
private boolean downstreamHasRoutePrefix(Route route) {
|
||||
return (!route.isPrefixStripped() && StringUtils.hasText(route.getPrefix()));
|
||||
}
|
||||
|
||||
private Pair<String, String> locationHeader(RequestContext ctx) {
|
||||
if (ctx.getZuulResponseHeaders() != null) {
|
||||
for (Pair<String, String> pair : ctx.getZuulResponseHeaders()) {
|
||||
if (pair.first().equals(LOCATION_HEADER)) {
|
||||
return pair;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or 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 org.springframework.cloud.netflix.zuul.filters.post;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.embedded.LocalServerPort;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.netflix.ribbon.StaticServerList;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
|
||||
"zuul.routes.aservice.path:/service/**", "zuul.routes.aservice.strip-prefix:true",
|
||||
"eureka.client.enabled:false" })
|
||||
@DirtiesContext
|
||||
public class LocationRewriteFilterIntegrationTests {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
RequestContext context = new RequestContext();
|
||||
RequestContext.testSetCurrentContext(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithRedirectPrefixStripped() {
|
||||
String url = "http://localhost:" + port + "/service/redirectingUri";
|
||||
ResponseEntity<String> response = new TestRestTemplate().getForEntity(url,
|
||||
String.class);
|
||||
List<String> locationHeaders = response.getHeaders().get("Location");
|
||||
|
||||
assertThat(locationHeaders).hasSize(1);
|
||||
String locationHeader = locationHeaders.get(0);
|
||||
assertThat(locationHeader).withFailMessage("Location should have prefix")
|
||||
.isEqualTo(
|
||||
String.format("http://localhost:%d/service/redirectedUri", port));
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@EnableZuulProxy
|
||||
@Controller
|
||||
@RibbonClient(name = "aservice", configuration = RibbonConfig.class)
|
||||
protected static class Config {
|
||||
|
||||
@RequestMapping("/redirectingUri")
|
||||
public String redirect1() {
|
||||
return "redirect:/redirectedUri";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocationRewriteFilter locationRewriteFilter() {
|
||||
return new LocationRewriteFilter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class RibbonConfig {
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or 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 org.springframework.cloud.netflix.zuul.filters.post;
|
||||
|
||||
import com.netflix.util.Pair;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Biju Kunjummen
|
||||
*/
|
||||
|
||||
public class LocationRewriteFilterTests {
|
||||
|
||||
private final String ZUUL_HOST = "myzuul.com";
|
||||
private final String ZUUL_SCHEME = "https";
|
||||
private final int ZUUL_PORT = 8443;
|
||||
private final String ZUUL_BASE_URL = String.format("%s://%s:%d", ZUUL_SCHEME,
|
||||
ZUUL_HOST, ZUUL_PORT);
|
||||
|
||||
private final String SERVER_HOST = "someserver.com";
|
||||
private final String SERVER_SCHEME = "http";
|
||||
private final int SERVER_PORT = 8564;
|
||||
private final String SERVER_BASE_URL = String.format("%s://%s:%d", SERVER_SCHEME,
|
||||
SERVER_HOST, SERVER_PORT);
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
RequestContext context = new RequestContext();
|
||||
RequestContext.testSetCurrentContext(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRewriteLocationHeadersWithRoutePrefix() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
ZuulProperties zuulProperties = new ZuulProperties();
|
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
|
||||
new Route("service1", "/redirectingUri", "service1", "prefix", false,
|
||||
Collections.EMPTY_SET, true),
|
||||
"/prefix/redirectingUri", "/redirectedUri;someparam?param1=abc");
|
||||
filter.run();
|
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String
|
||||
.format("%s/prefix/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldBeUntouchedIfNoRoutesFound() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
ZuulProperties zuulProperties = new ZuulProperties();
|
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties, null,
|
||||
"/prefix/redirectingUri", "/redirectedUri;someparam?param1=abc");
|
||||
filter.run();
|
||||
assertThat(getLocationHeader(context).second()).isEqualTo(
|
||||
String.format("%s/redirectedUri;someparam?param1=abc", SERVER_BASE_URL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRewriteLocationHeadersIfPrefixIsNotStripped() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
ZuulProperties zuulProperties = new ZuulProperties();
|
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
|
||||
new Route("service1", "/something/redirectingUri", "service1", "prefix",
|
||||
false, Collections.EMPTY_SET, false),
|
||||
"/prefix/redirectingUri",
|
||||
"/something/redirectedUri;someparam?param1=abc");
|
||||
filter.run();
|
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
|
||||
"%s/something/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRewriteLocationHeadersIfPrefixIsEmpty() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
ZuulProperties zuulProperties = new ZuulProperties();
|
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
|
||||
new Route("service1", "/something/redirectingUri", "service1", "", false,
|
||||
Collections.EMPTY_SET, true),
|
||||
"/redirectingUri", "/something/redirectedUri;someparam?param1=abc");
|
||||
filter.run();
|
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
|
||||
"%s/something/redirectedUri;someparam?param1=abc", ZUUL_BASE_URL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAddBackGlobalPrefixIfPresent() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
ZuulProperties zuulProperties = new ZuulProperties();
|
||||
zuulProperties.setPrefix("global");
|
||||
zuulProperties.setStripPrefix(true);
|
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
|
||||
new Route("service1", "/something/redirectingUri", "service1", "prefix",
|
||||
false, Collections.EMPTY_SET, true),
|
||||
"/global/prefix/redirectingUri",
|
||||
"/something/redirectedUri;someparam?param1=abc");
|
||||
filter.run();
|
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
|
||||
"%s/global/prefix/something/redirectedUri;someparam?param1=abc",
|
||||
ZUUL_BASE_URL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotAddBackGlobalPrefixIfNotStripped() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
ZuulProperties zuulProperties = new ZuulProperties();
|
||||
zuulProperties.setPrefix("global");
|
||||
zuulProperties.setStripPrefix(false);
|
||||
LocationRewriteFilter filter = setFilterUpWith(context, zuulProperties,
|
||||
new Route("service1", "/something/redirectingUri", "service1", "prefix",
|
||||
false, Collections.EMPTY_SET, true),
|
||||
"/global/prefix/redirectingUri",
|
||||
"/global/something/redirectedUri;someparam?param1=abc");
|
||||
filter.run();
|
||||
assertThat(getLocationHeader(context).second()).isEqualTo(String.format(
|
||||
"%s/global/prefix/something/redirectedUri;someparam?param1=abc",
|
||||
ZUUL_BASE_URL));
|
||||
}
|
||||
|
||||
private LocationRewriteFilter setFilterUpWith(RequestContext context,
|
||||
ZuulProperties zuulProperties, Route route, String toZuulRequestUri,
|
||||
String redirectedUri) {
|
||||
MockHttpServletRequest httpServletRequest = new MockHttpServletRequest();
|
||||
httpServletRequest.setRequestURI(toZuulRequestUri);
|
||||
httpServletRequest.setServerName(ZUUL_HOST);
|
||||
httpServletRequest.setScheme(ZUUL_SCHEME);
|
||||
httpServletRequest.setServerPort(ZUUL_PORT);
|
||||
context.setRequest(httpServletRequest);
|
||||
|
||||
MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
|
||||
context.getZuulResponseHeaders().add(new Pair<>("Location",
|
||||
String.format("%s%s", SERVER_BASE_URL, redirectedUri)));
|
||||
context.setResponse(httpServletResponse);
|
||||
|
||||
RouteLocator routeLocator = mock(RouteLocator.class);
|
||||
when(routeLocator.getMatchingRoute(toZuulRequestUri)).thenReturn(route);
|
||||
LocationRewriteFilter filter = new LocationRewriteFilter(zuulProperties,
|
||||
routeLocator);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
private Pair<String, String> getLocationHeader(RequestContext ctx) {
|
||||
if (ctx.getZuulResponseHeaders() != null) {
|
||||
for (Pair<String, String> pair : ctx.getZuulResponseHeaders()) {
|
||||
if (pair.first().equals("Location")) {
|
||||
return pair;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user