Reputation:
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
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
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