[BAEL-5261] Customizing Zuul Exceptions (#11736)
* [BAEL-5261] Customizing Zuul Exceptions * [BAEL-5261] Customizing Zuul Exceptions * [BAEL-5261] Code sanitizing
This commit is contained in:
parent
c94cbd9ed5
commit
289b9c0fa8
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.spring.cloud.zuul.filter;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
|
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
|
||||||
|
|
||||||
|
@EnableZuulProxy
|
||||||
|
@SpringBootApplication
|
||||||
|
public class CustomZuulErrorApplication extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(CustomZuulErrorApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.baeldung.spring.cloud.zuul.filter;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.netflix.zuul.ZuulFilter;
|
||||||
|
import com.netflix.zuul.context.RequestContext;
|
||||||
|
import com.netflix.zuul.exception.ZuulException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class CustomZuulErrorFilter extends ZuulFilter {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CustomZuulErrorFilter.class);
|
||||||
|
|
||||||
|
private static final String RESPONSE_BODY = "{\n" + " \"timestamp\": " + "\"" + Instant.now()
|
||||||
|
.toString() + "\"" + ",\n" + " \"status\": 503,\n" + " \"error\": \"Service Unavailable\"\n" + "}";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object run() {
|
||||||
|
RequestContext context = RequestContext.getCurrentContext();
|
||||||
|
Throwable throwable = context.getThrowable();
|
||||||
|
|
||||||
|
if (throwable instanceof ZuulException) {
|
||||||
|
final ZuulException zuulException = (ZuulException) throwable;
|
||||||
|
log.error("Zuul exception: " + zuulException.getMessage());
|
||||||
|
|
||||||
|
if (throwable.getCause().getCause().getCause() instanceof ConnectException) {
|
||||||
|
|
||||||
|
// reset throwable to prevent further error handling in follow up filters
|
||||||
|
context.remove("throwable");
|
||||||
|
|
||||||
|
// set custom response attributes
|
||||||
|
context.setResponseBody(RESPONSE_BODY);
|
||||||
|
context.getResponse()
|
||||||
|
.setContentType("application/json");
|
||||||
|
context.setResponseStatusCode(503);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldFilter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int filterOrder() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String filterType() {
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.baeldung.spring.cloud.zuul.filter;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||||
|
import org.springframework.cglib.proxy.Callback;
|
||||||
|
import org.springframework.cglib.proxy.CallbackFilter;
|
||||||
|
import org.springframework.cglib.proxy.Enhancer;
|
||||||
|
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||||
|
import org.springframework.cglib.proxy.MethodProxy;
|
||||||
|
import org.springframework.cglib.proxy.NoOp;
|
||||||
|
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
|
||||||
|
import org.springframework.cloud.netflix.zuul.web.ZuulController;
|
||||||
|
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix for Zuul configuration with Spring Boot 2.5.x + Zuul - "NoSuchMethodError: ErrorController.getErrorPath()":
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class ZuulConfiguration {
|
||||||
|
/**
|
||||||
|
* The path returned by ErrorController.getErrorPath() with Spring Boot < 2.5
|
||||||
|
* (and no longer available on Spring Boot >= 2.5).
|
||||||
|
*/
|
||||||
|
private static final String ERROR_PATH = "/error";
|
||||||
|
private static final String METHOD = "lookupHandler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new bean post-processor for Zuul.
|
||||||
|
*
|
||||||
|
* @param routeLocator the route locator.
|
||||||
|
* @param zuulController the Zuul controller.
|
||||||
|
* @param errorController the error controller.
|
||||||
|
* @return the new bean post-processor.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ZuulPostProcessor zuulPostProcessor(@Autowired RouteLocator routeLocator, @Autowired ZuulController zuulController, @Autowired(required = false) ErrorController errorController) {
|
||||||
|
return new ZuulPostProcessor(routeLocator, zuulController, errorController);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum LookupHandlerCallbackFilter implements CallbackFilter {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int accept(Method method) {
|
||||||
|
if (METHOD.equals(method.getName())) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum LookupHandlerMethodInterceptor implements MethodInterceptor {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
|
||||||
|
if (ERROR_PATH.equals(args[0])) {
|
||||||
|
// by entering this branch we avoid the ZuulHandlerMapping.lookupHandler method to trigger the
|
||||||
|
// NoSuchMethodError
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return methodProxy.invokeSuper(target, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ZuulPostProcessor implements BeanPostProcessor {
|
||||||
|
|
||||||
|
private final RouteLocator routeLocator;
|
||||||
|
private final ZuulController zuulController;
|
||||||
|
private final boolean hasErrorController;
|
||||||
|
|
||||||
|
ZuulPostProcessor(RouteLocator routeLocator, ZuulController zuulController, ErrorController errorController) {
|
||||||
|
this.routeLocator = routeLocator;
|
||||||
|
this.zuulController = zuulController;
|
||||||
|
this.hasErrorController = (errorController != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
|
if (hasErrorController && (bean instanceof ZuulHandlerMapping)) {
|
||||||
|
Enhancer enhancer = new Enhancer();
|
||||||
|
enhancer.setSuperclass(ZuulHandlerMapping.class);
|
||||||
|
enhancer.setCallbackFilter(LookupHandlerCallbackFilter.INSTANCE); // only for lookupHandler
|
||||||
|
enhancer.setCallbacks(new Callback[] { LookupHandlerMethodInterceptor.INSTANCE, NoOp.INSTANCE });
|
||||||
|
Constructor<?> ctor = ZuulHandlerMapping.class.getConstructors()[0];
|
||||||
|
return enhancer.create(ctor.getParameterTypes(), new Object[] { routeLocator, zuulController });
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
zuul:
|
zuul:
|
||||||
|
SendErrorFilter:
|
||||||
|
post:
|
||||||
|
disable: true
|
||||||
routes:
|
routes:
|
||||||
foos:
|
foos:
|
||||||
path: /foos/**
|
path: /foos/**
|
||||||
url: http://localhost:8081/spring-zuul-foos-resource/foos
|
url: http://localhost:8081/spring-zuul-foos-resource/foos
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.baeldung.spring.cloud.zuul.filter;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import io.restassured.RestAssured;
|
||||||
|
import io.restassured.response.Response;
|
||||||
|
|
||||||
|
public class CustomZuulErrorFilterLiveTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSendRequestWithoutService_thenCustomError() {
|
||||||
|
final Response response = RestAssured.get("http://localhost:8080/foos/1");
|
||||||
|
assertEquals(503, response.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenSendRequestWithoutCustomErrorFilter_thenError() {
|
||||||
|
final Response response = RestAssured.get("http://localhost:8080/foos/1");
|
||||||
|
assertEquals(500, response.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue