Fixes #792 - [HTTP/2] Socket seems to be not closed completely.

Now sending the GOAWAY and then disconnecting from doStop(), ensuring
that the underlying TCP connection is closed even if the server does
not close.
This commit is contained in:
Simone Bordet 2016-08-06 12:31:09 +02:00
parent 92c7b991d8
commit 45695e08aa
2 changed files with 135 additions and 0 deletions

View File

@ -113,6 +113,26 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
super.doStart();
}
@Override
protected void doStop() throws Exception
{
super.doStop();
close(ErrorCode.NO_ERROR.code, "stop", new Callback.NonBlocking()
{
@Override
public void succeeded()
{
disconnect();
}
@Override
public void failed(Throwable x)
{
disconnect();
}
});
}
@ManagedAttribute(value = "The flow control strategy", readonly = true)
public FlowControlStrategy getFlowControlStrategy()
{

View File

@ -19,8 +19,15 @@
package org.eclipse.jetty.http2.client.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@ -33,6 +40,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpFields;
@ -41,6 +49,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.HTTP2Stream;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
@ -51,10 +60,15 @@ import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -390,6 +404,107 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testClientStopsServerDoesNotCloseClientCloses() throws Exception
{
try (ServerSocket server = new ServerSocket(0))
{
List<Session> sessions = new ArrayList<>();
HTTP2Client h2Client = new HTTP2Client();
HttpClient client = new HttpClient(new HttpClientTransportOverHTTP2(h2Client)
{
@Override
protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session)
{
sessions.add(session);
return super.newHttpConnection(destination, session);
}
}, null);
QueuedThreadPool clientExecutor = new QueuedThreadPool();
clientExecutor.setName("client");
client.setExecutor(clientExecutor);
client.start();
CountDownLatch resultLatch = new CountDownLatch(1);
client.newRequest("localhost", server.getLocalPort())
.send(result ->
{
if (result.getResponse().getStatus() == HttpStatus.OK_200)
resultLatch.countDown();
});
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
Generator generator = new Generator(byteBufferPool);
try (Socket socket = server.accept())
{
socket.setSoTimeout(1000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
ServerParser parser = new ServerParser(byteBufferPool, new ServerParser.Listener.Adapter()
{
@Override
public void onHeaders(HeadersFrame request)
{
// Server's preface.
generator.control(lease, new SettingsFrame(new HashMap<>(), false));
// Reply to client's SETTINGS.
generator.control(lease, new SettingsFrame(new HashMap<>(), true));
// Response.
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
HeadersFrame response = new HeadersFrame(request.getStreamId(), metaData, null, true);
generator.control(lease, response);
try
{
// Write the frames.
for (ByteBuffer buffer : lease.getByteBuffers())
output.write(BufferUtil.toArray(buffer));
}
catch (Throwable x)
{
x.printStackTrace();
}
}
}, 4096, 8192);
byte[] bytes = new byte[1024];
while (true)
{
try
{
int read = input.read(bytes);
if (read < 0)
Assert.fail();
parser.parse(ByteBuffer.wrap(bytes, 0, read));
}
catch (SocketTimeoutException x)
{
break;
}
}
Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
// The client will send a GO_AWAY, but the server will not close.
client.stop();
// Give some time to process the stop/close operations.
Thread.sleep(1000);
Assert.assertTrue(h2Client.getBeans(Session.class).isEmpty());
for (Session session : sessions)
{
Assert.assertTrue(session.isClosed());
Assert.assertTrue(((HTTP2Session)session).isDisconnected());
}
}
}
}
@Ignore
@Test
public void testExternalServer() throws Exception