disconnect = serverConnection.disconnect();
+ if(disconnect != null) {
+ disconnect.addListener(
+ new GenericFutureListener() {
+ @Override
+ public void operationComplete(Future future)
+ throws Exception {
+ checkForRetry(lastStateBeforeFailure, cause);
}
- }
- });
+ });
+ } else {
+ checkForRetry(lastStateBeforeFailure, cause);
+ }
+ }
+
+ private void checkForRetry(ConnectionState lastStateBeforeFailure, Throwable cause) {
+ synchronized (connectLock) {
+ if (!clientConnection.serverConnectionFailed(
+ serverConnection,
+ lastStateBeforeFailure,
+ cause)) {
+ // the connection to the server failed and we are not retrying, so transition to the
+ // DISCONNECTED state
+ serverConnection.become(ConnectionState.DISCONNECTED);
+
+ // We are not retrying our connection, let anyone waiting for a connection know that we're done
+ notifyThreadsWaitingForConnection();
+ }
+ }
}
/**
diff --git a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java
index 1891532e4..3367ecbee 100644
--- a/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java
+++ b/src/main/java/org/littleshoot/proxy/impl/DefaultHttpProxyServer.java
@@ -117,6 +117,7 @@ public class DefaultHttpProxyServer implements HttpProxyServer {
private final int maxHeaderSize;
private final int maxChunkSize;
private final boolean allowRequestsToOriginServer;
+ private final boolean mimicServerHandshake;
/**
* The alias or pseudonym for this proxy, used when adding the Via header.
@@ -252,7 +253,8 @@ private DefaultHttpProxyServer(ServerGroup serverGroup,
int maxInitialLineLength,
int maxHeaderSize,
int maxChunkSize,
- boolean allowRequestsToOriginServer) {
+ boolean allowRequestsToOriginServer,
+ boolean mimicServerHandshake) {
this.serverGroup = serverGroup;
this.transportProtocol = transportProtocol;
this.requestedAddress = requestedAddress;
@@ -291,6 +293,7 @@ private DefaultHttpProxyServer(ServerGroup serverGroup,
this.maxHeaderSize = maxHeaderSize;
this.maxChunkSize = maxChunkSize;
this.allowRequestsToOriginServer = allowRequestsToOriginServer;
+ this.mimicServerHandshake = mimicServerHandshake;
}
/**
@@ -384,6 +387,10 @@ public boolean isAllowRequestsToOriginServer() {
return allowRequestsToOriginServer;
}
+ public boolean isMimicServerHandshake(){
+ return mimicServerHandshake;
+ }
+
@Override
public HttpProxyServerBootstrap clone() {
return new DefaultHttpProxyServerBootstrap(serverGroup,
@@ -408,7 +415,8 @@ public HttpProxyServerBootstrap clone() {
maxInitialLineLength,
maxHeaderSize,
maxChunkSize,
- allowRequestsToOriginServer);
+ allowRequestsToOriginServer,
+ mimicServerHandshake);
}
@Override
@@ -624,6 +632,7 @@ private static class DefaultHttpProxyServerBootstrap implements HttpProxyServerB
private int maxHeaderSize = MAX_HEADER_SIZE_DEFAULT;
private int maxChunkSize = MAX_CHUNK_SIZE_DEFAULT;
private boolean allowRequestToOriginServer = false;
+ private boolean mimicServerHandshake = false;
private DefaultHttpProxyServerBootstrap() {
}
@@ -648,7 +657,8 @@ private DefaultHttpProxyServerBootstrap(
int maxInitialLineLength,
int maxHeaderSize,
int maxChunkSize,
- boolean allowRequestToOriginServer) {
+ boolean allowRequestToOriginServer,
+ boolean mimicServerHandshake) {
this.serverGroup = serverGroup;
this.transportProtocol = transportProtocol;
this.requestedAddress = requestedAddress;
@@ -674,6 +684,7 @@ private DefaultHttpProxyServerBootstrap(
this.maxHeaderSize = maxHeaderSize;
this.maxChunkSize = maxChunkSize;
this.allowRequestToOriginServer = allowRequestToOriginServer;
+ this.mimicServerHandshake = mimicServerHandshake;
}
private DefaultHttpProxyServerBootstrap(Properties props) {
@@ -873,6 +884,12 @@ public HttpProxyServerBootstrap withAllowRequestToOriginServer(boolean allowRequ
return this;
}
+ @Override
+ public HttpProxyServerBootstrap withMimicServerHandshake(boolean mimicServerHandshake) {
+ this.mimicServerHandshake = mimicServerHandshake;
+ return this;
+ }
+
@Override
public HttpProxyServer start() {
return build().start();
@@ -904,7 +921,7 @@ transportProtocol, determineListenAddress(),
idleConnectionTimeout, activityTrackers, connectTimeout,
serverResolver, readThrottleBytesPerSecond, writeThrottleBytesPerSecond,
localAddress, proxyAlias, maxInitialLineLength, maxHeaderSize, maxChunkSize,
- allowRequestToOriginServer);
+ allowRequestToOriginServer, mimicServerHandshake);
}
private InetSocketAddress determineListenAddress() {
diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java
index 58c3eb240..d7c208eef 100644
--- a/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java
+++ b/src/main/java/org/littleshoot/proxy/impl/ProxyConnection.java
@@ -549,7 +549,8 @@ public SSLEngine getSslEngine() {
*/
protected void stopReading() {
LOG.debug("Stopped reading");
- this.channel.config().setAutoRead(false);
+ if(this.channel != null)
+ this.channel.config().setAutoRead(false);
}
/**
@@ -557,7 +558,8 @@ protected void stopReading() {
*/
protected void resumeReading() {
LOG.debug("Resumed reading");
- this.channel.config().setAutoRead(true);
+ if(this.channel != null)
+ this.channel.config().setAutoRead(true);
}
/**
diff --git a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java
index 2c9cece42..fe2cf9741 100644
--- a/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java
+++ b/src/main/java/org/littleshoot/proxy/impl/ProxyToServerConnection.java
@@ -541,9 +541,18 @@ private void connectAndWrite(HttpRequest initialRequest) {
*/
private void initializeConnectionFlow() {
this.connectionFlow = new ConnectionFlow(clientConnection, this,
- connectLock)
- .then(ConnectChannel);
+ connectLock);
+
+ //If it's a CONNECT request and mimic server handshake is enabled, don't make remote
+ //server connection and use MITM server ssl engine to do the handshake
+ if(ProxyUtils.isCONNECT(initialRequest) && proxyServer.isMimicServerHandshake()){
+ connectionFlow
+ .then(clientConnection.RespondCONNECTSuccessful)
+ .then(serverConnection.MitmMimicHSEncryptClientChannel);
+ return;
+ }
+ connectionFlow.then(ConnectChannel);
if (chainedProxy != null && chainedProxy.requiresEncryption()) {
connectionFlow.then(serverConnection.EncryptChannel(chainedProxy
.newSslEngine()));
@@ -736,6 +745,47 @@ public void operationComplete(
}
};
+ /**
+ *
+ * Encrypts the client channel based on MITM SSL Engine {@link SSLSession}.
+ *
+ *
+ *
+ * This does not wait for the handshake to finish so that we can go on and
+ * respond to the CONNECT request.
+ *
+ */
+ private ConnectionFlowStep MitmMimicHSEncryptClientChannel = new ConnectionFlowStep(
+ this, HANDSHAKING) {
+ @Override
+ boolean shouldExecuteOnEventLoop() {
+ return false;
+ }
+
+ @Override
+ boolean shouldSuppressInitialRequest() {
+ return true;
+ }
+
+ @Override
+ protected Future> execute() {
+ return clientConnection
+ .encrypt(proxyServer.getMitmManager()
+ .clientSslEngineFor(initialRequest, proxyServer.getMitmManager().serverSslEngine().getSession()), false)
+ .addListener(
+ new GenericFutureListener>() {
+ @Override
+ public void operationComplete(
+ Future super Channel> future)
+ throws Exception {
+ if (future.isSuccess()) {
+ clientConnection.setMitming(true);
+ }
+ }
+ });
+ }
+ };
+
/**
* Called when the connection to the server or upstream chained proxy fails. This method may return true to indicate
* that the connection should be retried. If returning true, this method must set up the connection itself.