Skip to content

Commit

Permalink
Fixes #9183 - ConnectHandler may close the connection instead of send…
Browse files Browse the repository at this point in the history
…ing 200 OK.

Delaying the call to UpstreamConnection.fillInterested() until the 200 OK response has been sent.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed Jan 17, 2023
1 parent 7c7a7f3 commit 142b108
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -562,7 +564,7 @@ public EndPoint getEndPoint()

public class UpstreamConnection extends ProxyConnection
{
private ConnectContext connectContext;
private final ConnectContext connectContext;

public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectContext connectContext)
{
Expand All @@ -574,8 +576,9 @@ public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool b
public void onOpen()
{
super.onOpen();
// Delay fillInterested() until the 200 OK response has been sent.
connectContext.asyncContext.addListener(new OnCompleteListener(this::fillInterested));
onConnectSuccess(connectContext, UpstreamConnection.this);
fillInterested();
}

@Override
Expand All @@ -589,6 +592,38 @@ protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
{
ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}

private class OnCompleteListener implements AsyncListener
{
private final Runnable onComplete;

private OnCompleteListener(Runnable onComplete)
{
this.onComplete = onComplete;
}

@Override
public void onComplete(AsyncEvent event)
{
onComplete.run();
}

@Override
public void onTimeout(AsyncEvent event)
{
}

@Override
public void onError(AsyncEvent event)
{
close(event.getThrowable());
}

@Override
public void onStartAsync(AsyncEvent event)
{
}
}
}

public class DownstreamConnection extends ProxyConnection implements Connection.UpgradeTo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -47,6 +48,7 @@

import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

Expand Down Expand Up @@ -84,6 +86,59 @@ public void testCONNECT() throws Exception
}
}

@Test
public void testCONNECTAndClose() throws Exception
{
disposeProxy();
connectHandler = new ConnectHandler()
{
@Override
protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
{
try
{
super.handleConnect(baseRequest, request, response, serverAddress);
// Delay the return of this method to trigger the race
// with the server closing the connection immediately.
Thread.sleep(500);
}
catch (InterruptedException x)
{
throw new RuntimeException(x);
}
}
};
proxy.setHandler(connectHandler);
proxy.start();

try (ServerSocket server = new ServerSocket(0))
{
String hostPort = "localhost:" + server.getLocalPort();
String request =
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();

Socket serverSocket = server.accept();
// Close immediately to trigger the race with
// the return from ConnectHandler.handle().
serverSocket.close();

// Expect 200 OK from the CONNECT request
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(socket.getInputStream()));
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
// Expect the connection to be closed.
assertEquals(-1, socket.getInputStream().read());
}
}
}

@Test
public void testCONNECTwithIPv6() throws Exception
{
Expand Down

0 comments on commit 142b108

Please sign in to comment.