Commit 1cd06e65 by Spencer Gibb

Don't swallow execeptions with zuul by reusing the spring boot error facilities.

fixes gh-40
parent f215c52f
......@@ -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();
}
}
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;
}
......
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
......
server:
port: 9876
servletPath: /app
management:
port: 9877
spring:
application:
name: testzuulserver
error:
path: ${server.servletPath}/error
zuul:
server:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment