Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move SSL handshake logic from listener thread to connection thread #7764

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,26 @@
package io.helidon.webserver;

import java.io.UncheckedIOException;
import java.net.Socket;
import java.util.HexFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;

import javax.net.ssl.SSLSocket;

import io.helidon.common.buffers.BufferData;
import io.helidon.common.buffers.DataReader;
import io.helidon.common.buffers.DataWriter;
import io.helidon.common.socket.HelidonSocket;
import io.helidon.common.socket.PeerInfo;
import io.helidon.common.socket.PlainSocket;
import io.helidon.common.socket.SocketWriter;
import io.helidon.common.socket.TlsSocket;
import io.helidon.common.task.InterruptableTask;
import io.helidon.common.tls.Tls;
import io.helidon.http.HttpException;
import io.helidon.http.RequestException;
import io.helidon.webserver.spi.ServerConnection;
Expand All @@ -53,30 +60,35 @@ class ConnectionHandler implements InterruptableTask<Void>, ConnectionContext {
private final ConnectionProviders connectionProviders;
private final List<ServerConnectionSelector> providerCandidates;
private final Map<String, ServerConnection> activeConnections;
private final HelidonSocket socket;
private final Socket socket;
private final String serverChannelId;
private final Router router;
private final SocketWriter writer;
private final DataReader reader;
private final Tls tls;

private ServerConnection connection;
private HelidonSocket helidonSocket;
private DataReader reader;
private SocketWriter writer;

ConnectionHandler(ListenerContext listenerContext,
Semaphore connectionSemaphore,
Semaphore requestSemaphore,
ConnectionProviders connectionProviders,
Map<String, ServerConnection> activeConnections,
HelidonSocket socket,
Router router) {
Socket socket,
String serverChannelId,
Router router,
Tls tls) {
this.listenerContext = listenerContext;
this.connectionSemaphore = connectionSemaphore;
this.requestSemaphore = requestSemaphore;
this.connectionProviders = connectionProviders;
this.providerCandidates = connectionProviders.providerCandidates();
this.activeConnections = activeConnections;
this.socket = socket;
this.serverChannelId = serverChannelId;
this.router = router;
this.writer = SocketWriter.create(listenerContext.executor(), socket, listenerContext.config().writeQueueLength());
this.reader = new DataReader(socket);
this.tls = tls;
}

@Override
Expand All @@ -86,19 +98,49 @@ public boolean canInterrupt() {

@Override
public final void run() {
String socketsId = socket.socketId() + " " + socket.childSocketId();
String channelId = "0x" + HexFormat.of().toHexDigits(System.identityHashCode(socket));

// handle SSL and init helidonSocket, reader and writer
try {
if (tls.enabled()) {
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.setHandshakeApplicationProtocolSelector(
(sslEngine, list) -> {
for (String protocolId : list) {
if (connectionProviders.supportedApplicationProtocols()
.contains(protocolId)) {
return protocolId;
}
}
return null;
});
sslSocket.startHandshake();
helidonSocket = TlsSocket.server(sslSocket, channelId, serverChannelId);
} else {
helidonSocket = PlainSocket.server(socket, channelId, serverChannelId);
}

reader = new DataReader(helidonSocket);
writer = SocketWriter.create(listenerContext.executor(), helidonSocket,
listenerContext.config().writeQueueLength());
} catch (Exception e) {
throw e instanceof RuntimeException re ? re : new RuntimeException(e); // see ServerListener
}

// connection handling
String socketsId = helidonSocket.socketId() + " " + helidonSocket.childSocketId();
Thread.currentThread().setName("[" + socketsId + "] WebServer socket");
if (LOGGER.isLoggable(DEBUG)) {
socket.log(LOGGER,
helidonSocket.log(LOGGER,
DEBUG,
"accepted socket from %s:%d",
socket.remotePeer().host(),
socket.remotePeer().port());
helidonSocket.remotePeer().host(),
helidonSocket.remotePeer().port());
}

try {
if (socket.protocolNegotiated()) {
this.connection = connectionProviders.byApplicationProtocol(socket.protocol())
if (helidonSocket.protocolNegotiated()) {
this.connection = connectionProviders.byApplicationProtocol(helidonSocket.protocol())
.connection(this);
}

Expand All @@ -112,17 +154,17 @@ public final void run() {
activeConnections.put(socketsId, connection);
connection.handle(requestSemaphore);
} catch (RequestException e) {
socket.log(LOGGER, WARNING, "escaped Request exception", e);
helidonSocket.log(LOGGER, WARNING, "escaped Request exception", e);
} catch (HttpException e) {
socket.log(LOGGER, WARNING, "escaped HTTP exception", e);
helidonSocket.log(LOGGER, WARNING, "escaped HTTP exception", e);
} catch (CloseConnectionException e) {
// end of request stream - safe to close the connection, as it was requested by our client
socket.log(LOGGER, TRACE, "connection close requested", e);
helidonSocket.log(LOGGER, TRACE, "connection close requested", e);
} catch (UncheckedIOException e) {
// socket exception - the socket failed, probably killed by OS, proxy or client
socket.log(LOGGER, TRACE, "received I/O exception", e);
helidonSocket.log(LOGGER, TRACE, "received I/O exception", e);
} catch (Exception e) {
socket.log(LOGGER, WARNING, "unexpected exception", e);
helidonSocket.log(LOGGER, WARNING, "unexpected exception", e);
} finally {
// connection has finished the loop of handling, release the semaphore
connectionSemaphore.release();
Expand All @@ -131,32 +173,32 @@ public final void run() {
closeChannel();
}

socket.log(LOGGER, DEBUG, "socket closed");
helidonSocket.log(LOGGER, DEBUG, "socket closed");
}

@Override
public PeerInfo remotePeer() {
return socket.remotePeer();
return helidonSocket.remotePeer();
}

@Override
public PeerInfo localPeer() {
return socket.localPeer();
return helidonSocket.localPeer();
}

@Override
public boolean isSecure() {
return socket.isSecure();
return helidonSocket.isSecure();
}

@Override
public String socketId() {
return socket.socketId();
return helidonSocket.socketId();
}

@Override
public String childSocketId() {
return socket.childSocketId();
return helidonSocket.childSocketId();
}

@Override
Expand Down Expand Up @@ -198,7 +240,7 @@ private ServerConnection identifyConnection() {
while (true) {
Iterator<ServerConnectionSelector> iterator = providerCandidates.iterator();
if (!iterator.hasNext()) {
socket.log(LOGGER, DEBUG, "Could not find a suitable connection provider. "
helidonSocket.log(LOGGER, DEBUG, "Could not find a suitable connection provider. "
+ "initial connection buffer (may be empty if no providers exist):\n%s",
currentBuffer.debugDataHex(false));
return null;
Expand Down Expand Up @@ -240,7 +282,7 @@ private ServerConnection identifyConnection() {
// we may have removed all candidates, we must re-check
// we must return before requesting more data (as more data may not be available)
if (providerCandidates.isEmpty()) {
socket.log(LOGGER,
helidonSocket.log(LOGGER,
DEBUG,
"Could not find a suitable connection provider. "
+ "initial connection buffer (may be empty if no providers exist):\n%s",
Expand All @@ -256,9 +298,9 @@ private ServerConnection identifyConnection() {

private void closeChannel() {
try {
socket.close();
helidonSocket.close();
} catch (Throwable e) {
socket.log(LOGGER, TRACE, "Failed to close socket on connection close", e);
helidonSocket.log(LOGGER, TRACE, "Failed to close socket on connection close", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,11 @@

import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.LazyValue;
import io.helidon.common.context.Context;
import io.helidon.common.socket.HelidonSocket;
import io.helidon.common.socket.PlainSocket;
import io.helidon.common.socket.SocketOptions;
import io.helidon.common.socket.TlsSocket;
import io.helidon.common.task.HelidonTaskExecutor;
import io.helidon.common.tls.Tls;
import io.helidon.http.encoding.ContentEncodingContext;
Expand All @@ -71,10 +67,8 @@ class ServerListener implements ListenerContext {
private static final System.Logger LOGGER = System.getLogger(ServerListener.class.getName());

@SuppressWarnings("rawtypes")
private static final LazyValue<List<ServerConnectionSelectorProvider>> SELECTOR_PROVIDERS = LazyValue.create(() -> {
return HelidonServiceLoader.create(ServiceLoader.load(ServerConnectionSelectorProvider.class))
.asList();
});
private static final LazyValue<List<ServerConnectionSelectorProvider>> SELECTOR_PROVIDERS = LazyValue.create(() ->
HelidonServiceLoader.create(ServiceLoader.load(ServerConnectionSelectorProvider.class)).asList());

private final ConnectionProviders connectionProviders;
private final String socketName;
Expand Down Expand Up @@ -359,37 +353,16 @@ private void listen() {
Socket socket = serverSocket.accept();

try {
ConnectionHandler handler;
String channelId = "0x" + HexFormat.of().toHexDigits(System.identityHashCode(socket));
connectionOptions.configureSocket(socket);

HelidonSocket helidonSocket;
if (tls.enabled()) {
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.setHandshakeApplicationProtocolSelector(
(sslEngine, list) -> {
for (String protocolId : list) {
if (connectionProviders.supportedApplicationProtocols()
.contains(protocolId)) {
return protocolId;
}
}
return null;
});
sslSocket.startHandshake();
helidonSocket = TlsSocket.server(sslSocket, channelId, serverChannelId);
} else {
helidonSocket = PlainSocket.server(socket, channelId, serverChannelId);
}

handler = new ConnectionHandler(this,
ConnectionHandler handler = new ConnectionHandler(this,
connectionSemaphore,
requestSemaphore,
connectionProviders,
activeConnections,
helidonSocket,
router);

socket,
serverChannelId,
router,
tls);
readerExecutor.execute(handler);
} catch (RejectedExecutionException e) {
LOGGER.log(ERROR, "Executor rejected handler for new connection");
Expand Down