Issue #80 (Spin loop in case of HTTP/2 prefaces without H2C).
Fixed by changing the state of the parser before calling the parser handler callbacks, and by closing the parser if the upgrade to HTTP/2 is not successful.
This commit is contained in:
parent
6a9002991a
commit
26b6c848f3
|
@ -641,8 +641,8 @@ public class HttpParser
|
|||
}
|
||||
else if (ch < HttpTokens.SPACE && ch>=0)
|
||||
{
|
||||
handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
|
||||
setState(State.HEADER);
|
||||
handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -714,8 +714,8 @@ public class HttpParser
|
|||
{
|
||||
if (_responseHandler!=null)
|
||||
{
|
||||
handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
|
||||
setState(State.HEADER);
|
||||
handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -761,7 +761,6 @@ public class HttpParser
|
|||
if (ch == HttpTokens.LINE_FEED)
|
||||
{
|
||||
String reason=takeString();
|
||||
|
||||
setState(State.HEADER);
|
||||
handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
|
||||
continue;
|
||||
|
@ -953,8 +952,8 @@ public class HttpParser
|
|||
return handle;
|
||||
|
||||
case NO_CONTENT:
|
||||
handle=_handler.headerComplete()||handle;
|
||||
setState(State.END);
|
||||
handle=_handler.headerComplete()||handle;
|
||||
handle=_handler.messageComplete()||handle;
|
||||
return handle;
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
package org.eclipse.jetty.http2.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
|
@ -26,6 +28,7 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.HashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.http.HostPortHttpField;
|
||||
|
@ -40,11 +43,17 @@ import org.eclipse.jetty.http2.frames.SettingsFrame;
|
|||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.parser.Parser;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpConnection;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
@ -56,27 +65,24 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
public class HTTP2CServerTest extends AbstractServerTest
|
||||
{
|
||||
private HTTP2CServer _server;
|
||||
private int _port;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception
|
||||
{
|
||||
_server = new HTTP2CServer(0);
|
||||
_server.start();
|
||||
_port = ((NetworkConnector)_server.getConnectors()[0]).getLocalPort();
|
||||
server = new HTTP2CServer(0);
|
||||
server.start();
|
||||
connector = (ServerConnector)server.getConnectors()[0];
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception
|
||||
{
|
||||
_server.stop();
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHTTP_1_0_Simple() throws Exception
|
||||
{
|
||||
try (Socket client = new Socket("localhost", _port))
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
client.getOutputStream().flush();
|
||||
|
@ -91,7 +97,7 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
@Test
|
||||
public void testHTTP_1_1_Simple() throws Exception
|
||||
{
|
||||
try (Socket client = new Socket("localhost", _port))
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
client.getOutputStream().write("GET /one HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
client.getOutputStream().write("GET /two HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
@ -109,7 +115,7 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
@Test
|
||||
public void testHTTP_1_1_Upgrade() throws Exception
|
||||
{
|
||||
try (Socket client = new Socket("localhost", _port))
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
OutputStream output = client.getOutputStream();
|
||||
output.write(("" +
|
||||
|
@ -186,7 +192,7 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
|
||||
generator.control(lease, new PrefaceFrame());
|
||||
generator.control(lease, new SettingsFrame(new HashMap<>(), false));
|
||||
MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:" + _port), "/two", HttpVersion.HTTP_2, new HttpFields());
|
||||
MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:" + connector.getLocalPort()), "/two", HttpVersion.HTTP_2, new HttpFields());
|
||||
generator.control(lease, new HeadersFrame(3, metaData, null, true));
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
output.write(BufferUtil.toArray(buffer));
|
||||
|
@ -222,10 +228,10 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
|
||||
generator.control(lease, new PrefaceFrame());
|
||||
generator.control(lease, new SettingsFrame(new HashMap<>(), false));
|
||||
MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:" + _port), "/test", HttpVersion.HTTP_2, new HttpFields());
|
||||
MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:" + connector.getLocalPort()), "/test", HttpVersion.HTTP_2, new HttpFields());
|
||||
generator.control(lease, new HeadersFrame(1, metaData, null, true));
|
||||
|
||||
try (Socket client = new Socket("localhost", _port))
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
OutputStream output = client.getOutputStream();
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
|
@ -276,4 +282,64 @@ public class HTTP2CServerTest extends AbstractServerTest
|
|||
assertThat(s, containsString("uri=/test"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHTTP_2_0_DirectWithoutH2C() throws Exception
|
||||
{
|
||||
AtomicLong fills = new AtomicLong();
|
||||
// Remove "h2c", leaving only "http/1.1".
|
||||
connector.clearConnectionFactories();
|
||||
HttpConnectionFactory connectionFactory = new HttpConnectionFactory()
|
||||
{
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
{
|
||||
HttpConnection connection = new HttpConnection(getHttpConfiguration(), connector, endPoint)
|
||||
{
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
fills.incrementAndGet();
|
||||
super.onFillable();
|
||||
}
|
||||
};
|
||||
return configure(connection, connector, endPoint);
|
||||
}
|
||||
};
|
||||
connector.addConnectionFactory(connectionFactory);
|
||||
connector.setDefaultProtocol(connectionFactory.getProtocol());
|
||||
|
||||
// Now send a HTTP/2 direct request, which
|
||||
// will have the PRI * HTTP/2.0 preface.
|
||||
|
||||
byteBufferPool = new MappedByteBufferPool();
|
||||
generator = new Generator(byteBufferPool);
|
||||
|
||||
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
|
||||
generator.control(lease, new PrefaceFrame());
|
||||
|
||||
try (Socket client = new Socket("localhost", connector.getLocalPort()))
|
||||
{
|
||||
OutputStream output = client.getOutputStream();
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
output.write(BufferUtil.toArray(buffer));
|
||||
|
||||
// We sent a HTTP/2 preface, but the server has no "h2c" connection
|
||||
// factory so it does not know how to handle this request.
|
||||
|
||||
InputStream input = client.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
|
||||
String responseLine = reader.readLine();
|
||||
Assert.assertThat(responseLine, Matchers.containsString(" 426 "));
|
||||
while (true)
|
||||
{
|
||||
if (reader.read() < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we did not spin.
|
||||
Thread.sleep(1000);
|
||||
Assert.assertThat(fills.get(), Matchers.lessThan(5L));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -331,6 +331,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
|||
return true;
|
||||
|
||||
badMessage(HttpStatus.UPGRADE_REQUIRED_426,null);
|
||||
_httpConnection.getParser().close();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -217,19 +217,20 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
HttpConnection last=setCurrentConnection(this);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
while (getEndPoint().isOpen())
|
||||
{
|
||||
// Fill the request buffer (if needed)
|
||||
// Fill the request buffer (if needed).
|
||||
int filled = fillRequestBuffer();
|
||||
|
||||
// Parse the request buffer
|
||||
// Parse the request buffer.
|
||||
boolean handle = parseRequestBuffer();
|
||||
|
||||
// If there was a connection upgrade, the other
|
||||
// connection took over, nothing more to do here.
|
||||
if (getEndPoint().getConnection()!=this)
|
||||
break;
|
||||
|
||||
// Handle close parser
|
||||
// Handle closed parser.
|
||||
if (_parser.isClose() || _parser.isClosed())
|
||||
{
|
||||
close();
|
||||
|
@ -245,13 +246,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
if (suspended || getEndPoint().getConnection() != this)
|
||||
break;
|
||||
}
|
||||
|
||||
// Continue or break?
|
||||
else if (filled<=0)
|
||||
else
|
||||
{
|
||||
if (filled==0)
|
||||
fillInterested();
|
||||
break;
|
||||
if (filled <= 0)
|
||||
{
|
||||
if (filled == 0)
|
||||
fillInterested();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue