Zuul Route Hystrix Fallback
Hystrix fallback support for Zuul routes. Fixes gh-250.
This commit is contained in:
committed by
Spencer Gibb
parent
6e1ca54edc
commit
0235d77932
@@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.endpoint.Endpoint;
|
||||
@@ -52,6 +53,7 @@ import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonComm
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.okhttp.OkHttpRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
|
||||
@@ -94,11 +96,14 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
|
||||
@ConditionalOnRibbonHttpClient
|
||||
protected static class HttpClientRibbonConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
|
||||
return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties);
|
||||
return new HttpClientRibbonCommandFactory(clientFactory, zuulProperties, zuulFallbackProviders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,11 +111,15 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
|
||||
@ConditionalOnRibbonRestClient
|
||||
protected static class RestClientRibbonConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
|
||||
return new RestClientRibbonCommandFactory(clientFactory, zuulProperties);
|
||||
return new RestClientRibbonCommandFactory(clientFactory, zuulProperties,
|
||||
zuulFallbackProviders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,11 +128,15 @@ public class ZuulProxyConfiguration extends ZuulConfiguration {
|
||||
@ConditionalOnClass(name = "okhttp3.OkHttpClient")
|
||||
protected static class OkHttpRibbonConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
|
||||
return new OkHttpRibbonCommandFactory(clientFactory, zuulProperties);
|
||||
return new OkHttpRibbonCommandFactory(clientFactory, zuulProperties,
|
||||
zuulFallbackProviders);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,20 +17,18 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer.Runner.customize;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.support.RibbonRequestCustomizer.Runner.customize;
|
||||
|
||||
/**
|
||||
* Hystrix wrapper around Eureka Ribbon command
|
||||
*
|
||||
@@ -44,6 +42,12 @@ public class RestClientRibbonCommand extends AbstractRibbonCommand<RestClient, H
|
||||
super(commandKey, client, context, zuulProperties);
|
||||
}
|
||||
|
||||
public RestClientRibbonCommand(String commandKey, RestClient client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties,
|
||||
ZuulFallbackProvider zuulFallbackProvider) {
|
||||
super(commandKey, client, context, zuulProperties, zuulFallbackProvider);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public RestClientRibbonCommand(String commandKey, RestClient restClient,
|
||||
HttpRequest.Verb verb, String uri, Boolean retryable,
|
||||
|
||||
@@ -17,27 +17,34 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommandFactory;
|
||||
|
||||
import com.netflix.client.http.HttpRequest;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class RestClientRibbonCommandFactory implements RibbonCommandFactory<RestClientRibbonCommand> {
|
||||
public class RestClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
private ZuulProperties zuulProperties;
|
||||
|
||||
public RestClientRibbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
this(clientFactory, new ZuulProperties());
|
||||
this(clientFactory, new ZuulProperties(), Collections.<ZuulFallbackProvider>emptySet());
|
||||
}
|
||||
|
||||
public RestClientRibbonCommandFactory(SpringClientFactory clientFactory,
|
||||
ZuulProperties zuulProperties) {
|
||||
ZuulProperties zuulProperties,
|
||||
Set<ZuulFallbackProvider> zuulFallbackProviders) {
|
||||
super(zuulFallbackProviders);
|
||||
this.clientFactory = clientFactory;
|
||||
this.zuulProperties = zuulProperties;
|
||||
}
|
||||
@@ -45,10 +52,12 @@ public class RestClientRibbonCommandFactory implements RibbonCommandFactory<Rest
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public RestClientRibbonCommand create(RibbonCommandContext context) {
|
||||
RestClient restClient = this.clientFactory.getClient(context.getServiceId(),
|
||||
String serviceId = context.getServiceId();
|
||||
ZuulFallbackProvider fallbackProvider = getFallbackProvider(serviceId);
|
||||
RestClient restClient = this.clientFactory.getClient(serviceId,
|
||||
RestClient.class);
|
||||
return new RestClientRibbonCommand(context.getServiceId(), restClient, context,
|
||||
this.zuulProperties);
|
||||
this.zuulProperties, fallbackProvider);
|
||||
}
|
||||
|
||||
public SpringClientFactory getClientFactory() {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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.route;
|
||||
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
/**
|
||||
* Provides fallback when a failure occurs on a route.
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public interface ZuulFallbackProvider {
|
||||
|
||||
/**
|
||||
* The route this fallback will be used for.
|
||||
* @return The route the fallback will be used for.
|
||||
*/
|
||||
public String getRoute();
|
||||
|
||||
/**
|
||||
* Provides a fallback response.
|
||||
* @return The fallback response.
|
||||
*/
|
||||
public ClientHttpResponse fallbackResponse();
|
||||
}
|
||||
@@ -22,10 +22,12 @@ import org.springframework.cloud.netflix.ribbon.apache.RibbonApacheHttpResponse;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class HttpClientRibbonCommand extends AbstractRibbonCommand<RibbonLoadBalancingHttpClient, RibbonApacheHttpRequest, RibbonApacheHttpResponse> {
|
||||
|
||||
@@ -36,6 +38,14 @@ public class HttpClientRibbonCommand extends AbstractRibbonCommand<RibbonLoadBal
|
||||
super(commandKey, client, context, zuulProperties);
|
||||
}
|
||||
|
||||
public HttpClientRibbonCommand(final String commandKey,
|
||||
final RibbonLoadBalancingHttpClient client,
|
||||
final RibbonCommandContext context,
|
||||
final ZuulProperties zuulProperties,
|
||||
final ZuulFallbackProvider zuulFallbackProvider) {
|
||||
super(commandKey, client, context, zuulProperties, zuulFallbackProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RibbonApacheHttpRequest createRequest() throws Exception {
|
||||
return new RibbonApacheHttpRequest(this.context);
|
||||
|
||||
@@ -16,33 +16,46 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.apache;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
|
||||
/**
|
||||
* @author Christian Lohmann
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class HttpClientRibbonCommandFactory implements
|
||||
RibbonCommandFactory<HttpClientRibbonCommand> {
|
||||
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
|
||||
private final ZuulProperties zuulProperties;
|
||||
|
||||
public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
|
||||
this(clientFactory, zuulProperties, Collections.<ZuulFallbackProvider>emptySet());
|
||||
}
|
||||
|
||||
public HttpClientRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties,
|
||||
Set<ZuulFallbackProvider> fallbackProviders) {
|
||||
super(fallbackProviders);
|
||||
this.clientFactory = clientFactory;
|
||||
this.zuulProperties = zuulProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
|
||||
ZuulFallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
|
||||
final String serviceId = context.getServiceId();
|
||||
final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(
|
||||
serviceId, RibbonLoadBalancingHttpClient.class);
|
||||
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
|
||||
|
||||
return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties);
|
||||
return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,20 +22,30 @@ import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonRequest;
|
||||
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpRibbonResponse;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class OkHttpRibbonCommand extends AbstractRibbonCommand<OkHttpLoadBalancingClient, OkHttpRibbonRequest, OkHttpRibbonResponse> {
|
||||
|
||||
public OkHttpRibbonCommand(final String commandKey,
|
||||
final OkHttpLoadBalancingClient client,
|
||||
final RibbonCommandContext context,
|
||||
final ZuulProperties zuulProperties) {
|
||||
final OkHttpLoadBalancingClient client,
|
||||
final RibbonCommandContext context,
|
||||
final ZuulProperties zuulProperties) {
|
||||
super(commandKey, client, context, zuulProperties);
|
||||
}
|
||||
|
||||
public OkHttpRibbonCommand(final String commandKey,
|
||||
final OkHttpLoadBalancingClient client,
|
||||
final RibbonCommandContext context,
|
||||
final ZuulProperties zuulProperties,
|
||||
final ZuulFallbackProvider zuulFallbackProvider) {
|
||||
super(commandKey, client, context, zuulProperties, zuulFallbackProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OkHttpRibbonRequest createRequest() throws Exception {
|
||||
return new OkHttpRibbonRequest(this.context);
|
||||
|
||||
@@ -16,33 +16,46 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.okhttp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.okhttp.OkHttpLoadBalancingClient;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommandFactory;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class OkHttpRibbonCommandFactory implements
|
||||
RibbonCommandFactory<OkHttpRibbonCommand> {
|
||||
public class OkHttpRibbonCommandFactory extends AbstractRibbonCommandFactory {
|
||||
|
||||
private final SpringClientFactory clientFactory;
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
private final ZuulProperties zuulProperties;
|
||||
private ZuulProperties zuulProperties;
|
||||
|
||||
public OkHttpRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties) {
|
||||
this(clientFactory, zuulProperties, Collections.<ZuulFallbackProvider>emptySet());
|
||||
}
|
||||
|
||||
public OkHttpRibbonCommandFactory(SpringClientFactory clientFactory, ZuulProperties zuulProperties,
|
||||
Set<ZuulFallbackProvider> zuulFallbackProviders) {
|
||||
super(zuulFallbackProviders);
|
||||
this.clientFactory = clientFactory;
|
||||
this.zuulProperties = zuulProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpRibbonCommand create(final RibbonCommandContext context) {
|
||||
final String serviceId = context.getServiceId();
|
||||
ZuulFallbackProvider fallbackProvider = getFallbackProvider(serviceId);
|
||||
final OkHttpLoadBalancingClient client = this.clientFactory.getClient(
|
||||
serviceId, OkHttpLoadBalancingClient.class);
|
||||
client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));
|
||||
|
||||
return new OkHttpRibbonCommand(serviceId, client, context, zuulProperties);
|
||||
return new OkHttpRibbonCommand(serviceId, client, context, zuulProperties, fallbackProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.ClientRequest;
|
||||
import com.netflix.client.http.HttpResponse;
|
||||
@@ -44,6 +44,7 @@ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwar
|
||||
|
||||
protected final LBC client;
|
||||
protected RibbonCommandContext context;
|
||||
protected ZuulFallbackProvider zuulFallbackProvider;
|
||||
|
||||
public AbstractRibbonCommand(LBC client, RibbonCommandContext context,
|
||||
ZuulProperties zuulProperties) {
|
||||
@@ -52,9 +53,16 @@ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwar
|
||||
|
||||
public AbstractRibbonCommand(String commandKey, LBC client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties) {
|
||||
this(commandKey, client, context, zuulProperties, null);
|
||||
}
|
||||
|
||||
public AbstractRibbonCommand(String commandKey, LBC client,
|
||||
RibbonCommandContext context, ZuulProperties zuulProperties,
|
||||
ZuulFallbackProvider fallbackProvider) {
|
||||
super(getSetter(commandKey, zuulProperties));
|
||||
this.client = client;
|
||||
this.context = context;
|
||||
this.zuulFallbackProvider = fallbackProvider;
|
||||
}
|
||||
|
||||
protected static Setter getSetter(final String commandKey,
|
||||
@@ -101,6 +109,14 @@ public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwar
|
||||
return new RibbonHttpResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientHttpResponse getFallback() {
|
||||
if(zuulFallbackProvider != null) {
|
||||
return zuulFallbackProvider.fallbackResponse();
|
||||
}
|
||||
return super.getFallback();
|
||||
}
|
||||
|
||||
public LBC getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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.route.support;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public abstract class AbstractRibbonCommandFactory implements RibbonCommandFactory {
|
||||
|
||||
private Map<String, ZuulFallbackProvider> fallbackProviderCache;
|
||||
|
||||
public AbstractRibbonCommandFactory(Set<ZuulFallbackProvider> fallbackProviders){
|
||||
this.fallbackProviderCache = new HashMap<String, ZuulFallbackProvider>();
|
||||
for(ZuulFallbackProvider provider : fallbackProviders) {
|
||||
fallbackProviderCache.put(provider.getRoute(), provider);
|
||||
}
|
||||
}
|
||||
|
||||
protected ZuulFallbackProvider getFallbackProvider(String route) {
|
||||
return fallbackProviderCache.get(route);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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.route.apache;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonCommandFallbackTests;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = HttpClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
|
||||
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
|
||||
"ribbon.ReadTimeout: 1"})
|
||||
@DirtiesContext
|
||||
public class HttpClientRibbonCommandFallbackTests extends RibbonCommandFallbackTests {
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
RequestContext.testSetCurrentContext(null);
|
||||
RequestContext.getCurrentContext().unset();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,9 @@ import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.http.HttpHeaders.SET_COOKIE;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -29,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
|
||||
@@ -44,6 +48,7 @@ import org.springframework.cloud.netflix.ribbon.apache.RibbonLoadBalancingHttpCl
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -155,6 +160,9 @@ public class HttpClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
@RibbonClient(name = "singleton", configuration = SingletonRibbonClientConfiguration.class) })
|
||||
static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
|
||||
|
||||
@RequestMapping(value = "/local/{id}", method = RequestMethod.PATCH)
|
||||
public String patch(@PathVariable final String id,
|
||||
@RequestBody final String body) {
|
||||
@@ -176,7 +184,8 @@ public class HttpClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
final SpringClientFactory clientFactory) {
|
||||
return new HttpClientRibbonCommandFactory(clientFactory, new ZuulProperties());
|
||||
return new HttpClientRibbonCommandFactory(clientFactory, new ZuulProperties(),
|
||||
zuulFallbackProviders);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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.route.okhttp;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonCommandFallbackTests;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = OkHttpRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
|
||||
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
|
||||
"ribbon.ReadTimeout: 1"})
|
||||
@DirtiesContext
|
||||
public class OkHttpRibbonCommandFallbackTests extends RibbonCommandFallbackTests {
|
||||
@Before
|
||||
public void init() {
|
||||
RequestContext.testSetCurrentContext(null);
|
||||
RequestContext.getCurrentContext().unset();
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,13 @@ package org.springframework.cloud.netflix.zuul.filters.route.okhttp;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
@@ -34,6 +38,7 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -100,10 +105,14 @@ public class OkHttpRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
@RibbonClient(name = "another", configuration = AnotherRibbonClientConfiguration.class) })
|
||||
static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Set<ZuulFallbackProvider> zuulFallbackProviders = Collections.emptySet();
|
||||
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
final SpringClientFactory clientFactory) {
|
||||
return new OkHttpRibbonCommandFactory(clientFactory, new ZuulProperties());
|
||||
return new OkHttpRibbonCommandFactory(clientFactory, new ZuulProperties(),
|
||||
zuulFallbackProviders);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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.route.restclient;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.RibbonCommandFallbackTests;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, value = {
|
||||
"zuul.routes.simple: /simple/**", "zuul.routes.another: /another/twolevel/**",
|
||||
"ribbon.ReadTimeout: 1"})
|
||||
@DirtiesContext
|
||||
public class RestClientRibbonCommandFallbackTests extends RibbonCommandFallbackTests {
|
||||
@Before
|
||||
public void init() {
|
||||
RequestContext.testSetCurrentContext(null);
|
||||
RequestContext.getCurrentContext().unset();
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,14 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -52,6 +56,7 @@ import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonComm
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RestClientRibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.NoEncodingFormHttpMessageConverter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.support.ZuulProxyTestBase;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -80,8 +85,6 @@ import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.niws.client.http.RestClient;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = RestClientRibbonCommandIntegrationTests.TestConfig.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
|
||||
"zuul.routes.other: /test/**=http://localhost:7777/local",
|
||||
@@ -285,6 +288,9 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
@RibbonClient(name = "another", configuration = ZuulProxyTestBase.AnotherRibbonClientConfiguration.class) })
|
||||
static class TestConfig extends ZuulProxyTestBase.AbstractZuulProxyApplication {
|
||||
|
||||
@Autowired(required = false)
|
||||
private Set<ZuulFallbackProvider> fallbackProviders = Collections.emptySet();
|
||||
|
||||
@RequestMapping("/trailing-slash")
|
||||
public String trailingSlash(HttpServletRequest request) {
|
||||
return request.getRequestURI();
|
||||
@@ -327,7 +333,7 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
@Bean
|
||||
public RibbonCommandFactory<?> ribbonCommandFactory(
|
||||
SpringClientFactory clientFactory) {
|
||||
return new MyRibbonCommandFactory(clientFactory);
|
||||
return new MyRibbonCommandFactory(clientFactory, fallbackProviders);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -350,8 +356,9 @@ public class RestClientRibbonCommandIntegrationTests extends ZuulProxyTestBase {
|
||||
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
public MyRibbonCommandFactory(SpringClientFactory clientFactory) {
|
||||
super(clientFactory, new ZuulProperties());
|
||||
public MyRibbonCommandFactory(SpringClientFactory clientFactory,
|
||||
Set<ZuulFallbackProvider> fallbackProviders) {
|
||||
super(clientFactory, new ZuulProperties(), fallbackProviders);
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2013-2016 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.route.support;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public abstract class RibbonCommandFallbackTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
protected int port;
|
||||
|
||||
@Test
|
||||
public void fallback() {
|
||||
String uri = "/simple/slow";
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + uri, HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
assertEquals(HttpStatus.OK, result.getStatusCode());
|
||||
assertEquals("fallback", result.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noFallback() {
|
||||
String uri = "/another/twolevel/slow";
|
||||
ResponseEntity<String> result = new TestRestTemplate().exchange(
|
||||
"http://localhost:" + this.port + uri, HttpMethod.GET,
|
||||
new HttpEntity<>((Void) null), String.class);
|
||||
System.out.println("no fallback body: " + result.getBody());
|
||||
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, result.getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,9 @@
|
||||
|
||||
package org.springframework.cloud.netflix.zuul.filters.route.support;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -29,9 +27,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -46,13 +42,16 @@ import org.springframework.cloud.netflix.zuul.filters.Route;
|
||||
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
|
||||
import org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
@@ -66,14 +65,19 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import com.netflix.loadbalancer.ServerList;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assume.assumeThat;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public abstract class ZuulProxyTestBase {
|
||||
|
||||
@@ -363,6 +367,22 @@ public abstract class ZuulProxyTestBase {
|
||||
return "Hello space";
|
||||
}
|
||||
|
||||
@RequestMapping("/slow")
|
||||
public String slow() {
|
||||
try {
|
||||
Thread.sleep(80000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "slow";
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFallbackProvider fallbackProvider() {
|
||||
return new FallbackProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ZuulFilter sampleFilter() {
|
||||
return new ZuulFilter() {
|
||||
@@ -393,6 +413,7 @@ public abstract class ZuulProxyTestBase {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -401,6 +422,53 @@ public abstract class ZuulProxyTestBase {
|
||||
mapping.setRemoveSemicolonContent(false);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class FallbackProvider implements ZuulFallbackProvider {
|
||||
|
||||
@Override
|
||||
public String getRoute() {
|
||||
return "simple";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse fallbackResponse() {
|
||||
return new ClientHttpResponse() {
|
||||
@Override
|
||||
public HttpStatus getStatusCode() throws IOException {
|
||||
return HttpStatus.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRawStatusCode() throws IOException {
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStatusText() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getBody() throws IOException {
|
||||
return new ByteArrayInputStream("fallback".getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.TEXT_HTML);
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
Reference in New Issue
Block a user