BlueMap/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/web/http/HttpConnection.java

110 lines
3.5 KiB
Java

package de.bluecolored.bluemap.common.web.http;
import de.bluecolored.bluemap.core.logger.Logger;
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<HttpResponse> 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
public void accept(SelectionKey selectionKey) {
if (!selectionKey.isValid()) return;
SelectableChannel selChannel = selectionKey.channel();
if (!(selChannel instanceof SocketChannel)) return;
SocketChannel channel = (SocketChannel) selChannel;
try {
if (request == null) {
SocketAddress remote = channel.getRemoteAddress();
InetAddress remoteInet = null;
if (remote instanceof InetSocketAddress)
remoteInet = ((InetSocketAddress) remote).getAddress();
request = new HttpRequest(remoteInet);
}
// receive request
if (!request.write(channel)) {
if (!selectionKey.isValid()) return;
selectionKey.interestOps(SelectionKey.OP_READ);
return;
}
// process 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
if (!response.read(channel)){
selectionKey.interestOps(SelectionKey.OP_WRITE);
return;
}
// reset to accept new request
request.clear();
response.close();
futureResponse = null;
response = null;
selectionKey.interestOps(SelectionKey.OP_READ);
} catch (IOException e) {
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);
}
}
}