459845 - Support upgrade from http1 to http2
Moved the sneaky direct upgrade to its own method and added support to detect a standard upgrade. The standard upgrade now creates the h2c connection and decodes the settings header, but it does not yet process the settings nor send a response to the request.
This commit is contained in:
parent
a7cfc3dc2d
commit
0dd58d2efe
|
@ -200,7 +200,7 @@ public class HttpClientCustomProxyTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ public class HttpClientCustomProxyTest
|
|||
getEndPoint().write(new Callback.Adapter(), buffer);
|
||||
|
||||
// We are good, upgrade the connection
|
||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
|
||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint(), null));
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
|
|
@ -109,7 +109,7 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
HttpConnectionFactory httpFactory = new HttpConnectionFactory()
|
||||
{
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint)
|
||||
{
|
||||
|
|
|
@ -42,7 +42,7 @@ public class ServerFCGIConnectionFactory extends AbstractConnectionFactory
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
return new ServerFCGIConnection(connector, endPoint, configuration, sendStatus200);
|
||||
}
|
||||
|
|
|
@ -110,6 +110,7 @@ public enum HttpHeader
|
|||
IDENTITY("identity"),
|
||||
|
||||
X_POWERED_BY("X-Powered-By"),
|
||||
HTTP2_SETTINGS("HTTP2-Settings"),
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** HTTP2 Fields.
|
||||
|
@ -124,7 +125,7 @@ public enum HttpHeader
|
|||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(520);
|
||||
public final static Trie<HttpHeader> CACHE= new ArrayTrie<>(530);
|
||||
static
|
||||
{
|
||||
for (HttpHeader header : HttpHeader.values())
|
||||
|
|
|
@ -88,7 +88,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
ServerSessionListener listener = newSessionListener(connector, endPoint);
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory
|
|||
{
|
||||
public HTTP2CServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration)
|
||||
{
|
||||
super(httpConfiguration,"h2c");
|
||||
super(httpConfiguration,"h2c","h2c-14");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.http2.server;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
import org.eclipse.jetty.http2.HTTP2Cipher;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
|
@ -32,11 +35,13 @@ import org.eclipse.jetty.http2.frames.HeadersFrame;
|
|||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.NegotiatingServerConnection.CipherDiscriminator;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.annotation.Name;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -143,6 +148,31 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
final Session session = stream.getSession();
|
||||
session.close(ErrorCode.PROTOCOL_ERROR.code, reason, Callback.Adapter.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
Connection connection = super.newConnection(connector,endPoint,attachment);
|
||||
|
||||
if (attachment instanceof MetaData.Request)
|
||||
{
|
||||
MetaData.Request request = (MetaData.Request) attachment;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} upgraded {}",this,request.toString()+request.getFields());
|
||||
|
||||
// TODO work out why _ needs replacing?
|
||||
byte[] settings = Base64.getDecoder().decode(request.getFields().getField(HttpHeader.HTTP2_SETTINGS).getValue().replace('_','='));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} settings {}",this,TypeUtil.toHexString(settings));
|
||||
|
||||
// TODO process the settings frame
|
||||
|
||||
// TODO use the metadata to push a response
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
* <li>block waiting for new connections
|
||||
* <li>accept the connection (eg socket accept)
|
||||
* <li>perform any configuration of the connection (eg. socket linger times)
|
||||
* <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
|
||||
* <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint, Object)}
|
||||
* method to create a new Connection instance.
|
||||
* </nl>
|
||||
* The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
|
||||
|
|
|
@ -58,8 +58,10 @@ public interface ConnectionFactory
|
|||
* <p>Creates a new {@link Connection} with the given parameters</p>
|
||||
* @param connector The {@link Connector} creating this connection
|
||||
* @param endPoint the {@link EndPoint} associated with the connection
|
||||
* @param attachment TODO
|
||||
* @param attachment Extra information for the connection
|
||||
* @return a new {@link Connection}
|
||||
*/
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint);
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment);
|
||||
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
|||
private boolean _unknownExpectation = false;
|
||||
private boolean _expect100Continue = false;
|
||||
private boolean _expect102Processing = false;
|
||||
private HttpField _http2Upgrade = null;
|
||||
|
||||
|
||||
public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport)
|
||||
|
@ -79,6 +80,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
|||
_metadata.recycle();
|
||||
_connection=null;
|
||||
_fields.clear();
|
||||
_http2Upgrade=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -167,6 +169,12 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case UPGRADE:
|
||||
if (value.startsWith("h2c"))
|
||||
_http2Upgrade=field;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -293,38 +301,17 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
|||
persistent = HttpMethod.CONNECT.is(_metadata.getMethod());
|
||||
if (!persistent)
|
||||
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
|
||||
|
||||
if (_http2Upgrade!=null && http2Upgrade())
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case HTTP_2:
|
||||
{
|
||||
// Allow sneaky "upgrade" to HTTP_2_0 only if the connector supports h2, but not protocol negotiation
|
||||
ConnectionFactory h2=null;
|
||||
if (!(getConnector().getDefaultConnectionFactory() instanceof NegotiatingServerConnectionFactory))
|
||||
for (ConnectionFactory factory : getConnector().getConnectionFactories())
|
||||
if (factory.getProtocols().contains("h2c"))
|
||||
h2=factory;
|
||||
|
||||
// If now a sneaky "upgrade" then a real upgrade is required
|
||||
if (h2==null ||
|
||||
_metadata.getMethod()!=HttpMethod.PRI.asString() ||
|
||||
!"*".equals(_metadata.getURI().toString()) ||
|
||||
_fields.size()>0)
|
||||
{
|
||||
badMessage(HttpStatus.UPGRADE_REQUIRED_426,null);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do a direct upgrade. Even though this is a HTTP/1 connector, we have seen a
|
||||
// HTTP/2.0 prefix, so let the request through
|
||||
Connection old_connection=getEndPoint().getConnection();
|
||||
Connection new_connection = h2.newConnection(getConnector(),getEndPoint());
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Direct Upgrade from {} to {}", old_connection,new_connection);
|
||||
getResponse().setStatus(101); // This will not get sent
|
||||
getRequest().setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,new_connection);
|
||||
getHttpTransport().onCompleted();
|
||||
return true;
|
||||
return http2Upgrade();
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -345,6 +332,87 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
|||
return !_delayedForContent;
|
||||
}
|
||||
|
||||
private boolean http2Upgrade()
|
||||
{
|
||||
LOG.debug("h2c upgrade {}",this);
|
||||
// Find the h2 factory
|
||||
ConnectionFactory h2=null;
|
||||
if (!(getConnector().getDefaultConnectionFactory() instanceof NegotiatingServerConnectionFactory))
|
||||
{
|
||||
loop: for (ConnectionFactory factory : getConnector().getConnectionFactories())
|
||||
for (String protocol : factory.getProtocols())
|
||||
if (protocol.startsWith("h2c"))
|
||||
{
|
||||
h2=factory;
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
Connection old_connection=getEndPoint().getConnection();
|
||||
Connection new_connection;
|
||||
|
||||
|
||||
if (_http2Upgrade==null)
|
||||
{
|
||||
LOG.debug("h2c preamble upgrade {}",this);
|
||||
// This must be a sneaky upgrade triggered by the http2 preamble!
|
||||
// If we don't have a HTTP factory or the preamble does not look right, then bad message
|
||||
if (h2==null ||
|
||||
_metadata.getMethod()!=HttpMethod.PRI.asString() ||
|
||||
!"*".equals(_metadata.getURI().toString()) ||
|
||||
_fields.size()>0)
|
||||
{
|
||||
badMessage(HttpStatus.UPGRADE_REQUIRED_426,null);
|
||||
return false;
|
||||
}
|
||||
|
||||
getResponse().setStatus(101); // wont be sent
|
||||
new_connection = h2.newConnection(getConnector(),getEndPoint(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a standard upgrade, so failures are not bad message, just a false return
|
||||
if (h2==null)
|
||||
{
|
||||
LOG.debug("No h2c factory for {}",this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!h2.getProtocols().contains(_http2Upgrade.getValue()))
|
||||
{
|
||||
LOG.debug("No h2c version {} for {}",_http2Upgrade.getValue(),this);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_connection==null || !_connection.getValue().contains("Upgrade") || !_connection.getValue().contains("HTTP2-Settings"))
|
||||
{
|
||||
LOG.debug("Bad h2c {} for {}",_connection,this);
|
||||
return false;
|
||||
}
|
||||
|
||||
getResponse().setStatus(101);
|
||||
HttpFields fields = new HttpFields();
|
||||
|
||||
try
|
||||
{
|
||||
sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.SWITCHING_PROTOCOLS_101,fields,0),null,true);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
badMessage(HttpStatus.INTERNAL_SERVER_ERROR_500,null);
|
||||
return false;
|
||||
}
|
||||
|
||||
new_connection = h2.newConnection(getConnector(),getEndPoint(),_metadata);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Upgrade from {} to {}", old_connection,new_connection);
|
||||
getRequest().setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,new_connection);
|
||||
getHttpTransport().onCompleted();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleException(Throwable x)
|
||||
{
|
||||
|
|
|
@ -53,7 +53,7 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ public class LocalConnector extends AbstractConnector
|
|||
endPoint.onOpen();
|
||||
onEndPointOpened(endPoint);
|
||||
|
||||
Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
|
||||
Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint, null);
|
||||
endPoint.setConnection(connection);
|
||||
|
||||
connection.onOpen();
|
||||
|
|
|
@ -128,7 +128,7 @@ public abstract class NegotiatingServerConnection extends AbstractConnection
|
|||
else
|
||||
{
|
||||
EndPoint endPoint = getEndPoint();
|
||||
Connection newConnection = connectionFactory.newConnection(connector, endPoint);
|
||||
Connection newConnection = connectionFactory.newConnection(connector, endPoint, null);
|
||||
endPoint.upgrade(newConnection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.server;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
|
@ -88,7 +89,7 @@ public abstract class NegotiatingServerConnectionFactory extends AbstractConnect
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
List<String> negotiated = this.negotiatedProtocols;
|
||||
if (negotiated.isEmpty())
|
||||
|
|
|
@ -63,7 +63,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endp)
|
||||
public Connection newConnection(Connector connector, EndPoint endp, Object attachment)
|
||||
{
|
||||
String next=_next;
|
||||
if (next==null)
|
||||
|
@ -201,7 +201,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
}
|
||||
|
||||
EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local);
|
||||
Connection newConnection = connectionFactory.newConnection(_connector, endPoint);
|
||||
Connection newConnection = connectionFactory.newConnection(_connector, endPoint, null);
|
||||
endPoint.upgrade(newConnection);
|
||||
}
|
||||
catch (Throwable e)
|
||||
|
|
|
@ -512,7 +512,7 @@ public class ServerConnector extends AbstractNetworkConnector
|
|||
@Override
|
||||
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
|
||||
{
|
||||
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
|
||||
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -72,7 +72,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
|
||||
engine.setUseClientMode(false);
|
||||
|
@ -83,7 +83,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
|||
|
||||
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
|
||||
EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
Connection connection = next.newConnection(connector, decryptedEndPoint);
|
||||
Connection connection = next.newConnection(connector, decryptedEndPoint, null);
|
||||
decryptedEndPoint.setConnection(connection);
|
||||
|
||||
return sslConnection;
|
||||
|
|
|
@ -52,7 +52,7 @@ public class ExtendedServerTest extends HttpServerTestBase
|
|||
startServer(new ServerConnector(_server,new HttpConnectionFactory()
|
||||
{
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.nio.ByteBuffer;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -51,7 +52,7 @@ public class SlowClientWithPipelinedRequestTest
|
|||
connector = new ServerConnector(server,new HttpConnectionFactory()
|
||||
{
|
||||
@Override
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
||||
{
|
||||
return configure(new HttpConnection(new HttpConfiguration(),connector,endPoint)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue