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. ...@@ -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`. 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; ...@@ -6,9 +6,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.trace.TraceRepository; import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.embedded.ServletRegistrationBean; 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.post.SendResponseFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter; 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.PreDecorationFilter;
import org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter; import org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -62,6 +64,11 @@ public abstract class AbstractZuulConfiguration { ...@@ -62,6 +64,11 @@ public abstract class AbstractZuulConfiguration {
return new PreDecorationFilter(); return new PreDecorationFilter();
} }
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter() {
return new Servlet30WrapperFilter();
}
// route filters // route filters
@Bean @Bean
public RibbonRoutingFilter ribbonRoutingFilter() { public RibbonRoutingFilter ribbonRoutingFilter() {
...@@ -78,4 +85,8 @@ public abstract class AbstractZuulConfiguration { ...@@ -78,4 +85,8 @@ public abstract class AbstractZuulConfiguration {
return new SendResponseFilter(); 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; ...@@ -12,6 +12,7 @@ import org.springframework.cloud.netflix.zuul.Routes;
import org.springframework.cloud.netflix.zuul.ZuulProperties; import org.springframework.cloud.netflix.zuul.ZuulProperties;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
public class PreDecorationFilter extends ZuulFilter { public class PreDecorationFilter extends ZuulFilter {
...@@ -78,7 +79,7 @@ public class PreDecorationFilter extends ZuulFilter { ...@@ -78,7 +79,7 @@ public class PreDecorationFilter extends ZuulFilter {
} }
} else { } else {
LOG.warn("No route found for uri: "+requestURI); LOG.warn("No route found for uri: "+requestURI);
//TODO: 404 ctx.set("error.status_code", HttpServletResponse.SC_NOT_FOUND);
} }
return null; 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; ...@@ -10,6 +10,7 @@ import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
...@@ -89,8 +90,10 @@ public class RibbonRoutingFilter extends SpringFilter { ...@@ -89,8 +90,10 @@ public class RibbonRoutingFilter extends SpringFilter {
return response; return response;
} }
catch (Exception e) { 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, private Map<String, Object> debug(Verb verb, String uri,
......
...@@ -17,7 +17,7 @@ public class ZuulProxyApplication { ...@@ -17,7 +17,7 @@ public class ZuulProxyApplication {
@RequestMapping("/testing123") @RequestMapping("/testing123")
public String testing123() { public String testing123() {
return "testing123"; throw new RuntimeException("myerror");
} }
@RequestMapping("/") @RequestMapping("/")
......
...@@ -6,7 +6,8 @@ spring: ...@@ -6,7 +6,8 @@ spring:
eureka: eureka:
server: server:
enabled: false enabled: false
error:
path: /myerror
zuul: zuul:
proxy: proxy:
mapping: /api mapping: /api
......
server: server:
port: 9876 port: 9876
servletPath: /app
management: management:
port: 9877 port: 9877
spring: spring:
application: application:
name: testzuulserver name: testzuulserver
error:
path: ${server.servletPath}/error
zuul: zuul:
server: 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