8

I'm making use of a Vert.x web server to serve up a React app as static content. I want this to be served up from the path /, then within the React app it has its own routing using react-router which should decide which page to show.

So far I have the following:

Vertx vertx = Vertx.vertx();
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.route(HttpMethod.POST, "/rest/foo").handler(new FooHandler());
router.route(HttpMethod.GET, "/*").handler(StaticHandler.create()).failureHandler(event -> { // This serves up the React app
    event.response().sendFile("webroot/index.html").end();
});
server.requestHandler(router::accept).listen(12001);

This works as expected if I start by requesting localhost:12001 and it also correctly handles the path changes from that point onwards. However if I try to refresh one of the pages which has a path handled by the react router then I get a bunch of error generated in the server logs (the page does load correctly though).

Does anybody know what the issue is here and how to fix it?

SEVERE: Unexpected exception in route
java.lang.IllegalStateException: Response has already been written
    at io.vertx.core.http.impl.HttpServerResponseImpl.checkWritten(HttpServerResponseImpl.java:561)
    at io.vertx.core.http.impl.HttpServerResponseImpl.end0(HttpServerResponseImpl.java:389)
    at io.vertx.core.http.impl.HttpServerResponseImpl.end(HttpServerResponseImpl.java:328)
    at co.uk.foo.webserver.server.WebServer.lambda$initialiseRoutes$0(WebServer.java:67)
    at co.uk.foo.webserver.server.WebServer$$Lambda$4/1197365356.handle(Unknown Source)
    at io.vertx.ext.web.impl.RouteImpl.handleFailure(RouteImpl.java:227)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:76)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:94)
    at io.vertx.ext.web.impl.RoutingContextImpl.doFail(RoutingContextImpl.java:355)
    at io.vertx.ext.web.impl.RoutingContextImpl.fail(RoutingContextImpl.java:119)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.lambda$sendStatic$2(StaticHandlerImpl.java:198)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl$$Lambda$17/1050258443.handle(Unknown Source)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.wrapInTCCLSwitch(StaticHandlerImpl.java:245)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.getFileProps(StaticHandlerImpl.java:264)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.sendStatic(StaticHandlerImpl.java:184)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.handle(StaticHandlerImpl.java:141)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.handle(StaticHandlerImpl.java:51)
    at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:221)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:78)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:94)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.doEnd(BodyHandlerImpl.java:155)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.end(BodyHandlerImpl.java:141)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl.lambda$handle$34(BodyHandlerImpl.java:61)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl$$Lambda$14/1403708668.handle(Unknown Source)
    at io.vertx.core.http.impl.HttpServerRequestImpl.handleEnd(HttpServerRequestImpl.java:411)
    at io.vertx.core.http.impl.ServerConnection.handleEnd(ServerConnection.java:286)
    at io.vertx.core.http.impl.ServerConnection.processMessage(ServerConnection.java:404)
    at io.vertx.core.http.impl.ServerConnection.handleMessage(ServerConnection.java:134)
    at io.vertx.core.http.impl.HttpServerImpl$ServerHandler.doMessageReceived(HttpServerImpl.java:515)
    at io.vertx.core.http.impl.HttpServerImpl$ServerHandler.doMessageReceived(HttpServerImpl.java:421)
    at io.vertx.core.http.impl.VertxHttpHandler.lambda$channelRead$20(VertxHttpHandler.java:80)
    at io.vertx.core.http.impl.VertxHttpHandler$$Lambda$16/1532360211.run(Unknown Source)
    at io.vertx.core.impl.ContextImpl.lambda$wrapTask$18(ContextImpl.java:333)
    at io.vertx.core.impl.ContextImpl$$Lambda$11/511598695.run(Unknown Source)
    at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:225)
    at io.vertx.core.http.impl.VertxHttpHandler.channelRead(VertxHttpHandler.java:80)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:124)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:276)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:263)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:131)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
    at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
    at java.lang.Thread.run(Thread.java:745)

Jun 26, 2016 4:22:08 PM io.vertx.ext.web.impl.RoutingContextImplBase
SEVERE: Unexpected exception in route
java.lang.IllegalStateException: Head already written
    at io.vertx.core.http.impl.HttpServerResponseImpl.doSendFile(HttpServerResponseImpl.java:434)
    at io.vertx.core.http.impl.HttpServerResponseImpl.sendFile(HttpServerResponseImpl.java:334)
    at io.vertx.core.http.impl.HttpServerResponseImpl.sendFile(HttpServerResponseImpl.java:52)
    at io.vertx.core.http.HttpServerResponse.sendFile(HttpServerResponse.java:275)
    at io.vertx.core.http.HttpServerResponse.sendFile(HttpServerResponse.java:262)
    at co.uk.foo.webserver.server.WebServer.lambda$initialiseRoutes$0(WebServer.java:67)
    at co.uk.foo.webserver.server.WebServer$$Lambda$4/1197365356.handle(Unknown Source)
    at io.vertx.ext.web.impl.RouteImpl.handleFailure(RouteImpl.java:227)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:76)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:94)
    at io.vertx.ext.web.impl.RoutingContextImpl.doFail(RoutingContextImpl.java:355)
    at io.vertx.ext.web.impl.RoutingContextImpl.fail(RoutingContextImpl.java:119)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.lambda$sendStatic$2(StaticHandlerImpl.java:189)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl$$Lambda$17/1050258443.handle(Unknown Source)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.getFileProps(StaticHandlerImpl.java:284)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.sendStatic(StaticHandlerImpl.java:184)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.handle(StaticHandlerImpl.java:141)
    at io.vertx.ext.web.handler.impl.StaticHandlerImpl.handle(StaticHandlerImpl.java:51)
    at io.vertx.ext.web.impl.RouteImpl.handleContext(RouteImpl.java:221)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:78)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:94)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.doEnd(BodyHandlerImpl.java:155)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl$BHandler.end(BodyHandlerImpl.java:141)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl.lambda$handle$34(BodyHandlerImpl.java:61)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl$$Lambda$14/1403708668.handle(Unknown Source)
    at io.vertx.core.http.impl.HttpServerRequestImpl.handleEnd(HttpServerRequestImpl.java:411)
    at io.vertx.core.http.impl.ServerConnection.handleEnd(ServerConnection.java:286)
    at io.vertx.core.http.impl.ServerConnection.processMessage(ServerConnection.java:404)
    at io.vertx.core.http.impl.ServerConnection.handleMessage(ServerConnection.java:134)
    at io.vertx.core.http.impl.HttpServerImpl$ServerHandler.doMessageReceived(HttpServerImpl.java:515)
    at io.vertx.core.http.impl.HttpServerImpl$ServerHandler.doMessageReceived(HttpServerImpl.java:421)
    at io.vertx.core.http.impl.VertxHttpHandler.lambda$channelRead$20(VertxHttpHandler.java:80)
    at io.vertx.core.http.impl.VertxHttpHandler$$Lambda$16/1532360211.run(Unknown Source)
    at io.vertx.core.impl.ContextImpl.lambda$wrapTask$18(ContextImpl.java:333)
    at io.vertx.core.impl.ContextImpl$$Lambda$11/511598695.run(Unknown Source)
    at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:225)
    at io.vertx.core.http.impl.VertxHttpHandler.channelRead(VertxHttpHandler.java:80)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:124)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:276)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:263)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:131)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
    at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
    at java.lang.Thread.run(Thread.java:745)
  • I think that react is generating new URLs that change the path so when you refresh the browser tries to fetch that path which does not exist on the vert.x backend. I think you need to write some rule to map these custom paths to your html file. Maybe you should post the generated paths so we could help translating then to your html file. – Paulo Lopes Jun 20 '16 at 09:10
  • Do you see what is going on in Chrome Console, when you refresh the page. There you should see, whitch reqeusts are failing. Could you then please attach the picture of the failing requests. – haschibaschi Jun 27 '16 at 06:53
  • Which version of Vert.x are you using? – Vadeg Jun 28 '16 at 09:33
  • @Vadeg I'm using the latest available version of vert.x –  Jun 28 '16 at 16:12
  • @haschibaschi I did check the console in chrome and thee are no failed requests. I get a 200 on all of them and the content is valid. The issue only seems to show up in the logs of my vert.x server. –  Jun 28 '16 at 16:13
  • Can you provide a github repo, where we can reproduce the problem? – haschibaschi Jun 29 '16 at 15:31

2 Answers2

4

I tried numerous different approaches with this issue and got nowhere. Instead I decided to write my own handler which behaves the way I want and I don't get any errors shown in my original question.

import io.vertx.core.Handler;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ReactAppHandler implements Handler<RoutingContext> {

    private static final Logger LOGGER = LogManager.getLogger(ReactAppHandler.class);

    private static final String WEB_ROOT_DIR = "webroot";
    private static final String INDEX_HTML = "/index.html";

    @Override
    public void handle(RoutingContext event) {

        HttpServerRequest request = event.request();
        String path = event.normalisedPath();

        LOGGER.info("Received a request for [" + path + "].");

        String requestedFilepath = path;

        if ("/".equals(requestedFilepath)) {
            LOGGER.info("Requested file is root path. Remapping to return the index page.");
            requestedFilepath = INDEX_HTML;
        }

        final String fileToCheck = WEB_ROOT_DIR + requestedFilepath;
        LOGGER.info("Checking if file exists at [" + fileToCheck + "].");

        event.vertx().fileSystem().exists(fileToCheck, fileExistsCheck -> {

            String fileToSend = WEB_ROOT_DIR + INDEX_HTML;

            if (fileExistsCheck.succeeded() && fileExistsCheck.result()) {
                LOGGER.info("File exists at path.");
                fileToSend = fileToCheck;
            } else {
                LOGGER.info("Could not find requested file, the index page will be returned instead.");
            }

            LOGGER.info("Returning file [" + fileToSend + "].");
            request.response().sendFile(fileToSend);
        });
    }
}
  • The behavior you are describing in your handler is not what you were asking. What you should have done is a simple handler that checks if the file exists and when it doesn't reroutes to index.html. This way you have all the features of static handler with cache support, etc. – Paulo Lopes Jul 02 '16 at 20:14
2

When returning files you shouldn't call the end method, here:

router.route(HttpMethod.GET, "/*").handler(StaticHandler.create()).failureHandler(event -> { // This serves up the React app
  event.response().sendFile("webroot/index.html").end();
});

The reason is that sending the file is an asynchronous call which will pump the file to the response in an optimized way and when you call end you're saying close the response NOW!

What it seems to be happening is that your file is quite small so the OS can pump it right away in one go (so your browser still receives it correctly) but you try to close the connection when the OS has already done it for you.

What you should have is:

router.route(HttpMethod.GET, "/*").handler(StaticHandler.create()).failureHandler(event -> { // This serves up the React app
  event.response().sendFile("webroot/index.html");
});
Paulo Lopes
  • 5,845
  • 22
  • 31
  • Thanks for this. I tried it out and it looks like this resolves one of the exceptions, however I still seem to get the 'java.lang.IllegalStateException: Head already written' error. –  Jul 01 '16 at 19:29