javaee / grizzly

Writing scalable server applications in the Java™ programming language has always been difficult. Before the advent of the Java New I/O API (NIO), thread management issues made it impossible for a server to scale to thousands of users. The Grizzly NIO framework has been designed to help developers to take advantage of the Java™ NIO API.
https://javaee.github.io/grizzly/
Other
222 stars 60 forks source link

Invalid URLs make Grizzly fail with an unhandled error #1838

Closed glassfishrobot closed 8 years ago

glassfishrobot commented 8 years ago

Hi team, we are using Grizzly server in combination with Jersey to expose an API for heavy-traffic application. Unfortunately, some of our customers issue some HTTP request with a bogus URL. More specifically, the query string contains a '%' symbol followed by a non-numeric character after URL decoding. In this case the request is automatically rejected before reaching the Jersey layer. Unfortunately this behavior has two main flaws: 1) it attaches the stacktrace of the exception to the response, which is a major violation of enterprise-level privacy regulation; 2) By throwing an exception for every request, it jeopardize the stability of the machine under heavy load.

The stacktrace we see as HTTP response (but it is not logged by our logger) is:

java.net.URISyntaxException: Malformed escape pair at index 29: http://localhost:8080/test?a=%%

 1: org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.getRequestUri(GrizzlyHttpContainer.java:491)
2: org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:366)
3: org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:224)
4: org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:591)
5: org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:571)
6: java.lang.Thread.run(Thread.java:745)
Root Cause: java.net.URISyntaxException: Malformed escape pair at index 29: http://localhost:8080/test?a=%%
 1: java.net.URI$Parser.fail(URI.java:2848)
2: java.net.URI$Parser.scanEscape(URI.java:2978)
3: java.net.URI$Parser.scan(URI.java:3001)
4: java.net.URI$Parser.checkChars(URI.java:3019)
5: java.net.URI$Parser.parseHierarchical(URI.java:3111)
6: java.net.URI$Parser.parse(URI.java:3053)
7: java.net.URI.(URI.java:588)
8: org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.getRequestUri(GrizzlyHttpContainer.java:489)
9: org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:366)
10: org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:224)
... 3 more
Please see the log for more detail.

To reproduce the bug you might reproduce the issue by 1) downloading a maven package from https://goo.gl/CM7SQi, 2) launch the Main, 3) target the server with a bogus URL, for example, by using curl: "curl http://localhost:8080/test?a=%%".

We are aware that it is a corner case due to a non-standard use of URLs. Thus, should you be able to propose a way to cope with the two problems above, we would be more than happy.

Thanks in advance for you help, Marco Funaro

Affected Versions

[2.3.22]

glassfishrobot commented 8 years ago

Reported by marco.funaro

glassfishrobot commented 8 years ago

@rlubke said: You should be able to override the default unhandled exception processing by calling NetworkListener.setDefaultErrorPageGenerator(ErrorPageGenerator [1]). NetworkListener instances can be obtained from the HttpServer instance you're using with Jersey.

[1] https://grizzly.java.net/docs/2.3/apidocs/org/glassfish/grizzly/http/server/ErrorPageGenerator.html

glassfishrobot commented 8 years ago

marco.funaro said: Hi Ryan, thanks for your help. I was able to use the feature you suggested to fix the first point, but grizzly would still throw an exception for every call. I tried fixing it by using and HttpServerFilter and reimplementing the handleRead method to just reject the request if it matches a regex but I was not able to properly inject it in the FilterChain. I tried returning a StopAction but then the HTTP request would hang on the client side. I also tried by returning a an InvokeAction but it would (obviously) throw the Exception anyway. Btw I noticed the link to my maven project is broken, here it is a working one: https://s3-eu-west-1.amazonaws.com/static.cuebiq.com/public/misc/grizzly-reproducer.zip

glassfishrobot commented 8 years ago

@rlubke said: Can you share how you tried to inject the filter?

glassfishrobot commented 8 years ago

marco.funaro said: Here it is: FilterChain chain = networkListener.getFilterChain(); networkListener.getFilterChain().add(chain.size() - 2, new PixelHttpServerFilter());

I injected the filter in the penultimate position so that it already has access to the HTTP payload and just before it is forwarded to the Jersey handle. The class PixelHttpFilter extends HttpBaseFilter and overrides handleRead. I tried returning both a StopAction and InvokeAction with the effects mentioned above.

glassfishrobot commented 8 years ago

@rlubke said: This worked for me:

**URLFilter.java**public class URLFilter extends BaseFilter {

    @Override
    public NextAction handleRead(FilterChainContext ctx) throws IOException {
        final Object msg = ctx.getMessage();
        if (HttpPacket.isHttp(msg)) {
            HttpContent content = (HttpContent) msg;
            if (content.getHttpHeader().isRequest()) {
HttpRequestPacket request = (HttpRequestPacket) content.getHttpHeader();
final DataChunk uri = request.getRequestURIRef().getOriginalRequestURIBC();
boolean failed = false;
if (uri.indexOf('%', 0) != -1) {
    failed = true;
}
final DataChunk query = request.getQueryStringDC();
if (query.indexOf('%', 0) != -1) {
    failed = true;
}
if (failed) {
    final HttpResponsePacket response = request.getResponse();
    response.setStatus(HttpStatus.BAD_REQUEST_400);
    HttpContent responseContent = response.httpContentBuilder().
            last(true).build();
    ctx.write(responseContent);
    ctx.write(request.getResponse());

}
            }
        }
        return ctx.getInvokeAction();

    }
}

You'll want to do a better job of dealing with URI's and query strings than I do here, but you get the gist.

Then here's the server code:

**Server.java**public class Server {

    public static void main(String[] args) {
        HttpServer server = new HttpServer();
        server.getServerConfiguration().addHttpHandler(new HttpHandler() {
            public void service(Request request, Response response) throws Exception {
response.getWriter().write(new Date().toString());
            }
        }, "/handler");
        final NetworkListener listener =
new NetworkListener("grizzly",
        "localhost",
        8080);
        server.addListener(listener);

        try {
            server.start();

            final FilterChain chain = listener.getFilterChain();
            final int idx = chain.indexOfType(org.glassfish.grizzly.http.HttpServerFilter.class);
            chain.add(idx + 1, new URLFilter());

            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            server.stop();
        }

    }
}

Seems to me that we should probably think about something similar to Servlet Filters for our http-server implementation for those implementations that don't already use our Servlet provider. If you agree, would you mind opening a feature request?

glassfishrobot commented 8 years ago

@rlubke said: Will go ahead and close this out. Might be worth logging a feature request against Jersey to log an error for the case where a URL can't be created.

Please re-open if you feel there's an underlying issue in Grizzly.

glassfishrobot commented 8 years ago

marco.funaro said: Hi Ryan, thank you for your help. The solution worked perfectly, the only change I did is returning a ctx.getStopAction() in case of malformed URL to stop Grizzly from further processing. Thanks again, Marco

glassfishrobot commented 7 years ago

This issue was imported from java.net JIRA GRIZZLY-1838

glassfishrobot commented 8 years ago

Marked as works as designed on Thursday, June 30th 2016, 2:08:56 pm