diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpConnection.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpConnection.java index 769b8a61..3ad879b0 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpConnection.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpConnection.java @@ -6,18 +6,28 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.channels.Channel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; public class HttpConnection implements SelectionConsumer { private final HttpRequestHandler requestHandler; + private final Executor responseHandlerExecutor; private HttpRequest request; + private CompletableFuture futureResponse; private HttpResponse response; public HttpConnection(HttpRequestHandler requestHandler) { + this(requestHandler, Runnable::run); //run synchronously + } + + public HttpConnection(HttpRequestHandler requestHandler, Executor responseHandlerExecutor) { this.requestHandler = requestHandler; + this.responseHandlerExecutor = responseHandlerExecutor; } @Override @@ -48,10 +58,22 @@ public class HttpConnection implements SelectionConsumer { } // process request - if (response == null) { - this.response = requestHandler.handle(request); + if (futureResponse == null) { + futureResponse = CompletableFuture.supplyAsync( + () -> requestHandler.handle(request), + responseHandlerExecutor + ); + futureResponse.thenAccept(response -> { + try { + response.read(channel); // do an initial read to trigger response sending intent + this.response = response; + } catch (IOException e) { + handleIOException(channel, e); + } + }); } + if (response == null) return; if (!selectionKey.isValid()) return; // send response @@ -63,16 +85,24 @@ public class HttpConnection implements SelectionConsumer { // reset to accept new request request.clear(); response.close(); + futureResponse = null; response = null; selectionKey.interestOps(SelectionKey.OP_READ); } catch (IOException e) { - Logger.global.logDebug("Failed to process selection: " + e); - try { - channel.close(); - } catch (IOException e2) { - Logger.global.logWarning("Failed to close channel" + e2); - } + handleIOException(channel, e); + } + } + + private void handleIOException(Channel channel, IOException e) { + request.clear(); + response = null; + + Logger.global.logDebug("Failed to process selection: " + e); + try { + channel.close(); + } catch (IOException e2) { + Logger.global.logWarning("Failed to close channel" + e2); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpResponse.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpResponse.java index 3303e738..8e4894c6 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpResponse.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpResponse.java @@ -57,7 +57,7 @@ public class HttpResponse implements Closeable { this.headers = new HashMap<>(); } - public boolean read(WritableByteChannel channel) throws IOException { + public synchronized boolean read(WritableByteChannel channel) throws IOException { if (complete) return true; // send headers diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java index 9456f3ec..d6b1c115 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpServer.java @@ -13,6 +13,10 @@ public class HttpServer extends Server { @Override public SelectionConsumer createConnectionHandler() { return new HttpConnection(requestHandler); + + // Enable async request handling ... + // TODO: maybe find a better/separate executor than using bluemap's common thread-pool + //return new HttpConnection(requestHandler, BlueMap.THREAD_POOL); } }