[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:
Sarath Chandra 2022-02-07 23:00:56 +05:30 committed by GitHub
parent c94cbd9ed5
commit 289b9c0fa8
5 changed files with 203 additions and 1 deletions

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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;
}
}
}

View File

@ -1,5 +1,8 @@
zuul:
SendErrorFilter:
post:
disable: true
routes:
foos:
path: /foos/**
url: http://localhost:8081/spring-zuul-foos-resource/foos
url: http://localhost:8081/spring-zuul-foos-resource/foos

View File

@ -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());
}
}