user843337
user843337

Reputation:

Serving up a single page rect app using java vert.x web server

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)

Upvotes: 8

Views: 2694

Answers (2)

user843337
user843337

Reputation:

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);
        });
    }
}

Upvotes: 4

Paulo Lopes
Paulo Lopes

Reputation: 5801

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");
});

Upvotes: 2

Related Questions