Don't swallow execeptions with zuul by reusing the spring boot error facilities.
fixes gh-40
This commit is contained in:
@@ -484,3 +484,14 @@ This means that http calls to /users get forwarded to the users service.
|
||||
|
||||
Since zuul, by default, intercepts all requests (`/*`), to enable actuator, you should set the `management.port`.
|
||||
|
||||
To use the Spring Boot error facilities while using the standalone Zuul server, please set the following properties
|
||||
|
||||
----
|
||||
# moves the Spring Dispatch Servlet to a path below `/`
|
||||
server:
|
||||
servletPath: /app
|
||||
# sets the error path to use the Dispatch Servlet to resolve the error view
|
||||
error:
|
||||
path: ${server.servletPath}/error
|
||||
----
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.trace.TraceRepository;
|
||||
import org.springframework.boot.context.embedded.FilterRegistrationBean;
|
||||
import org.springframework.boot.context.embedded.ServletRegistrationBean;
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
|
||||
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -62,6 +64,11 @@ public abstract class AbstractZuulConfiguration {
|
||||
return new PreDecorationFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Servlet30WrapperFilter servlet30WrapperFilter() {
|
||||
return new Servlet30WrapperFilter();
|
||||
}
|
||||
|
||||
// route filters
|
||||
@Bean
|
||||
public RibbonRoutingFilter ribbonRoutingFilter() {
|
||||
@@ -78,4 +85,8 @@ public abstract class AbstractZuulConfiguration {
|
||||
return new SendResponseFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SendErrorFilter sendErrorFilter() {
|
||||
return new SendErrorFilter();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.post;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Slf4j
|
||||
public class SendErrorFilter extends ZuulFilter {
|
||||
|
||||
@Value("${error.path:/error}")
|
||||
private String errorPath;
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "post";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
return ctx.containsKey("error.status_code");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
try {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
int statusCode = (Integer)ctx.get("error.status_code");
|
||||
if (ctx.containsKey("error.exception")) {
|
||||
Object e = ctx.get("error.exception");
|
||||
log.warn("Error during filtering", Throwable.class.cast(e));
|
||||
ctx.getRequest().setAttribute("javax.servlet.error.exception", e);
|
||||
}
|
||||
ctx.getRequest().setAttribute("javax.servlet.error.status_code", statusCode);
|
||||
ctx.getRequest().getRequestDispatcher(errorPath).forward(ctx.getRequest(), ctx.getResponse());
|
||||
} catch (Exception e) {
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import org.springframework.cloud.netflix.zuul.Routes;
|
||||
import org.springframework.cloud.netflix.zuul.ZuulProperties;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class PreDecorationFilter extends ZuulFilter {
|
||||
@@ -78,7 +79,7 @@ public class PreDecorationFilter extends ZuulFilter {
|
||||
}
|
||||
} else {
|
||||
LOG.warn("No route found for uri: "+requestURI);
|
||||
//TODO: 404
|
||||
ctx.set("error.status_code", HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package org.springframework.cloud.netflix.zuul.filters.pre;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.http.HttpServletRequestWrapper;
|
||||
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.Part;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class Servlet30WrapperFilter extends ZuulFilter {
|
||||
protected Field requestField = null;
|
||||
|
||||
public Servlet30WrapperFilter() {
|
||||
try {
|
||||
requestField = HttpServletRequestWrapper.class.getDeclaredField("req");
|
||||
requestField.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filterType() {
|
||||
return "pre";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true; //TODO: only if in servlet 3.0 env
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() {
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
HttpServletRequest request = ctx.getRequest();
|
||||
if (request instanceof HttpServletRequestWrapper) {
|
||||
try {
|
||||
request = (HttpServletRequest) requestField.get(request);
|
||||
} catch (IllegalAccessException e) {
|
||||
Throwables.propagate(e);
|
||||
}
|
||||
}
|
||||
ctx.setRequest(new Servlet30RequestWrapper(request));
|
||||
//ctx.setResponse(new HttpServletResponseWrapper(ctx.getResponse()));
|
||||
return null;
|
||||
}
|
||||
|
||||
class Servlet30RequestWrapper extends HttpServletRequestWrapper {
|
||||
private HttpServletRequest request;
|
||||
|
||||
Servlet30RequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
|
||||
return request.authenticate(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void login(String username, String password) throws ServletException {
|
||||
request.login(username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout() throws ServletException {
|
||||
request.logout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException {
|
||||
return request.getParts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Part getPart(String name) throws IOException, IllegalStateException, ServletException {
|
||||
return request.getPart(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getServletContext() {
|
||||
return request.getServletContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync() {
|
||||
return request.startAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) {
|
||||
return request.startAsync(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncStarted() {
|
||||
return request.isAsyncStarted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
return request.isAsyncSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext getAsyncContext() {
|
||||
return request.getAsyncContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatcherType getDispatcherType() {
|
||||
return request.getDispatcherType();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@@ -89,8 +90,10 @@ public class RibbonRoutingFilter extends SpringFilter {
|
||||
return response;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
context.set("error.exception", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, Object> debug(Verb verb, String uri,
|
||||
|
||||
@@ -17,7 +17,7 @@ public class ZuulProxyApplication {
|
||||
|
||||
@RequestMapping("/testing123")
|
||||
public String testing123() {
|
||||
return "testing123";
|
||||
throw new RuntimeException("myerror");
|
||||
}
|
||||
|
||||
@RequestMapping("/")
|
||||
|
||||
@@ -6,7 +6,8 @@ spring:
|
||||
eureka:
|
||||
server:
|
||||
enabled: false
|
||||
|
||||
error:
|
||||
path: /myerror
|
||||
zuul:
|
||||
proxy:
|
||||
mapping: /api
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
server:
|
||||
port: 9876
|
||||
servletPath: /app
|
||||
management:
|
||||
port: 9877
|
||||
spring:
|
||||
application:
|
||||
name: testzuulserver
|
||||
error:
|
||||
path: ${server.servletPath}/error
|
||||
|
||||
zuul:
|
||||
server:
|
||||
|
||||
Reference in New Issue
Block a user