Spring-cloud-netflix: Zuul pre-filter: rejecting request logs a NPE

Created on 1 Jun 2017  路  10Comments  路  Source: spring-cloud/spring-cloud-netflix

Hi!

I'm trying to reject a request in a pre-filter (for example an authentication error).
For that, i'm doing the following in the run method:

if(!auth) {
    ctx.unset();
    ctx.setResponseStatusCode(401);
    ctx.setResponseBody("example");
}

Doing this, zull log a nullpointer exception:

[ERROR] 2017-06-01 12:10:15.295 [qtp380274260-29] FilterProcessor - Filter threw Exception
com.netflix.zuul.exception.ZuulException: Filter threw Exception
        at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:227) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.FilterProcessor.error(FilterProcessor.java:105) [zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.ZuulRunner.error(ZuulRunner.java:112) [zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.http.ZuulServlet.error(ZuulServlet.java:145) [zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:90) [zuul-core-1.3.0.jar!/:1.3.0]
        at org.springframework.web.servlet.mvc.ServletWrappingController.handleRequestInternal(ServletWrappingController.java:157) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
        at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:44) [spring-cloud-netflix-core-1.3.1.RELEASE.jar!/:1.3.1.RELEASE]
        at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:50) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]

I have more pre-filters, post-filters, etc...

As a result:
The response status code is ok, but the response body isn't sent to the client and the nullpointerexception trace is logged...

I saw some examples, people saying that ctx.unset() method needs to be called in order to stop the request being routed, is this the correct way?

I tried to replace the ctx.unset() by ctx.setSendZuulResponse(false) and the effect is the same....

What is the right approach to implement this kind of situations where we want to stop the request and return a error code to the client with a body?

spring-cloud-starter-zuul: 1.3.1.RELEASE
spring-boot-starter-parent; 1.5.3.RELEASE
spring-cloud-dependencies: Dalston.SR1
question

Most helpful comment

Yes, these log messages are expected, they are logged by the error filter SendErrorFilter#run().

If you don't want to log the exception, you could add a property: logging.level.org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter=error.

Usually, the post-filters in sc-zuul will not be executed. If you customize a post-filter, you should check RequestContext.getCurrentContext().getThrowable() == null in the method shouldFilter(), just like: SendResponseFilter#shouldFilter()

All 10 comments

I think you could do it like this in your auth-filter:

if (!auth) {
    throw new ZuulRuntimeException(new ZuulException(
        "not auth", HttpStatus.UNAUTHORIZED.value(), "not auth"));
}

And the response will be:

HTTP/1.1 401 Unauthorized
Content-Type: application/json;charset=utf-8
Transfer-Encoding: chunked

{"timestamp":1496314171649,"status":401,"error":"Unauthorized","exception":"com.netflix.zuul.exception.ZuulException","message":"not auth"}

just one solution to reject a request, hope helpful for you.

Hi lowzj,

Thank you.
I tried already your solution and is working more or less as expected.
Anyway, i continue seeing the exception being logged the post-filters are executed.

Here's the exception log:

[WARN ] 2017-06-01 12:57:51.442 [qtp380274260-34] SendErrorFilter - Error during filtering
com.netflix.zuul.exception.ZuulException: auth bad credentials
        at com.pack.pre.AuthFilter.throwError(RateLimitFilter.java:115) ~[gateway-api-1.0.0-SNAPSHOT.jar!/:1.0.0-SNAPSHOT]
        at com.pack.pre.AuthFilter.run(RateLimitFilter.java:81) ~[gateway-api-1.0.0-SNAPSHOT.jar!/:1.0.0-SNAPSHOT]
        at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.FilterProcessor.preRoute(FilterProcessor.java:133) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.ZuulRunner.preRoute(ZuulRunner.java:105) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.http.ZuulServlet.preRoute(ZuulServlet.java:125) ~[zuul-core-1.3.0.jar!/:1.3.0]
        at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:74) ~[zuul-core-1.3.0.jar!/:1.3.0]

Do you have the same behaviour? is this normal?
Is there a way to avoid post-filters being executed? exception log not being logged out?

Thanks in advance!

Yes, these log messages are expected, they are logged by the error filter SendErrorFilter#run().

If you don't want to log the exception, you could add a property: logging.level.org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter=error.

Usually, the post-filters in sc-zuul will not be executed. If you customize a post-filter, you should check RequestContext.getCurrentContext().getThrowable() == null in the method shouldFilter(), just like: SendResponseFilter#shouldFilter()

Thank you Lowzj!
That solves the problem!

Send error filter is usually used for exceptions (failures) during. Turning the error logging on the filter is a bad idea, then you won't know when real errors happen. I'd say run you pre filter after the PreDecorationFilter and remove the serviceId attribute (not unset());

I don't see a null pointer exception in your report either.

Hi @spencergibb ,

Thank you for your reply.

Here's the full stacktrace of the NPE (at the bottom):

```[ERROR] 2017-06-02 16:02:08.214 [qtp1375394559-61] FilterProcessor - Filter threw Exception
com.netflix.zuul.exception.ZuulException: Filter threw Exception
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:227) ~[zuul-core-1.3.0.jar!/:1.3.0]
at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) ~[zuul-core-1.3.0.jar!/:1.3.0]
at com.netflix.zuul.FilterProcessor.error(FilterProcessor.java:105) [zuul-core-1.3.0.jar!/:1.3.0]
at com.netflix.zuul.ZuulRunner.error(ZuulRunner.java:112) [zuul-core-1.3.0.jar!/:1.3.0]
at com.netflix.zuul.http.ZuulServlet.error(ZuulServlet.java:145) [zuul-core-1.3.0.jar!/:1.3.0]
at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:90) [zuul-core-1.3.0.jar!/:1.3.0]
at org.springframework.web.servlet.mvc.ServletWrappingController.handleRequestInternal(ServletWrappingController.java:157) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:44) [spring-cloud-netflix-core-1.3.1.RELEASE.jar!/:1.3.1.RELEASE]
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:50) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687) [javax.servlet-api-3.1.0.jar!/:3.1.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) [spring-webmvc-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [javax.servlet-api-3.1.0.jar!/:3.1.0]
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:841) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1634) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.boot.web.filter.ApplicationContextHeaderFilter.doFilterInternal(ApplicationContextHeaderFilter.java:55) [spring-boot-1.5.3.RELEASE.jar!/:1.5.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110) [spring-boot-actuator-1.5.3.RELEASE.jar!/:1.5.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:106) [spring-boot-actuator-1.5.3.RELEASE.jar!/:1.5.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.3.8.RELEASE.jar!/:4.3.8.RELEASE]
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1621) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:541) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:548) [jetty-security-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:190) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1595) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:188) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1253) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:168) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:481) [jetty-servlet-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1564) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:166) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1155) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.Server.handle(Server.java:564) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:317) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:251) [jetty-server-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:279) [jetty-io-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:110) [jetty-io-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:124) [jetty-io-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.util.thread.Invocable.invokePreferred(Invocable.java:128) [jetty-util-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.util.thread.Invocable$InvocableExecutor.invoke(Invocable.java:222) [jetty-util-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:294) [jetty-util-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:199) [jetty-util-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:672) [jetty-util-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:590) [jetty-util-9.4.4.v20170414.jar!/:9.4.4.v20170414]
at java.lang.Thread.run(Unknown Source) [?:1.8.0_131]
Caused by: java.lang.NullPointerException
at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.run(SendErrorFilter.java:76) ~[spring-cloud-netflix-core-1.3.1.RELEASE.jar!/:1.3.1.RELEASE]
at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112) ~[zuul-core-1.3.0.jar!/:1.3.0]
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) ~[zuul-core-1.3.0.jar!/:1.3.0]
... 66 more

I already check and my filter is executed after the PreDecoratorFilter.
But i don't have a serviceId to remove, i have this configuration:



zuul.routes.arb:
stripPrefix: false
url: http://localhost:8100
path: /appserver/**
```

And i want to stop all the filters, not continuing executing some of them.

So, what's the correct approach to stop all the filter execution and return an error code back to the client?

What does your exception class look like?

You need an Error Filter and an Error Controller.

@Component
public class AuthenticationFilter extends ZuulFilter {

   @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 7;
    }

    @Override
    public boolean shouldFilter() {
       //some logic
       return true;
    }

   @Override
    public Object run() {
       //some logic 
       if (!auth) {
          RequestContext ctx = RequestContext.getCurrentContext();
          ctx.set("error.status_code", HttpStatus.UNAUTHORIZED.value());
          throw new RuntimeException("Not Authorized");
     }
   }
}
@Component
public class ErrorFilterFilter extends ZuulFilter {

   private static final String ERROR_STATUS_CODE = "error.status_code";
   @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        return !context.containsKey(ERROR_STATUS_CODE) && context.getResponseStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value();
    }

   @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        context.put(ERROR_STATUS_CODE, HttpStatus.INTERNAL_SERVER_ERROR.value());
        return null;
   }
}
@Controller
@Slf4j
public class CustomErrorController implements ErrorController {

    @Value("${error.path:/error}")
    private String errorPath;

    @Override
    public String getErrorPath() {
        return errorPath;
    }

    public void setPublish(boolean publish) {
        this.publish = publish;
    }

    @RequestMapping(value = "${error.path:/error}", produces = "application/json")
    public
    @ResponseBody
    ResponseEntity<ErrorInfo> error(HttpServletRequest request,
            HttpServletResponse response) {
        // just a POJO
        ErrorInfo info = new ErrorInfo();

        Integer status = (Integer)request.getAttribute("javax.servlet.error.status_code");
        status != null ? status : HttpStatus.INTERNAL_SERVER_ERROR.value();
        info.setCode(status);

       Throwable exc = (Throwable) request.getAttribute("javax.servlet.error.exception");
        String msg = exc != null ? exc.getMessage() : "Unexpected error occurred";
        info.setMessage(msg);

        return ResponseEntity.status(info.getCode()).body(info);
    }
}

Closing due to age of the question. If you would like us to look at this issue, please comment and we will look at re-opening the issue.

Was this page helpful?
0 / 5 - 0 ratings