459845 - Support upgrade from http1 to http2
Generalised Upgrade Connection Factories
This commit is contained in:
parent
0bcf1fade3
commit
3f795da26c
|
@ -200,7 +200,7 @@ public class HttpClientCustomProxyTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public org.eclipse.jetty.io.Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
|
return new CAFEBABEServerConnection(connector, endPoint, connectionFactory);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ public class HttpClientCustomProxyTest
|
||||||
getEndPoint().write(new Callback.Adapter(), buffer);
|
getEndPoint().write(new Callback.Adapter(), buffer);
|
||||||
|
|
||||||
// We are good, upgrade the connection
|
// We are good, upgrade the connection
|
||||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint(), null));
|
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
|
|
|
@ -109,7 +109,7 @@ public class SslBytesServerTest extends SslBytesTest
|
||||||
HttpConnectionFactory httpFactory = new HttpConnectionFactory()
|
HttpConnectionFactory httpFactory = new HttpConnectionFactory()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint)
|
return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint)
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class ServerFCGIConnectionFactory extends AbstractConnectionFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
return new ServerFCGIConnection(connector, endPoint, configuration, sendStatus200);
|
return new ServerFCGIConnection(connector, endPoint, configuration, sendStatus200);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import java.util.concurrent.Executor;
|
||||||
import org.eclipse.jetty.http2.parser.Parser;
|
import org.eclipse.jetty.http2.parser.Parser;
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.Connection;
|
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.ConcurrentArrayQueue;
|
import org.eclipse.jetty.util.ConcurrentArrayQueue;
|
||||||
|
@ -34,7 +33,7 @@ import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.ExecutionStrategy;
|
import org.eclipse.jetty.util.thread.ExecutionStrategy;
|
||||||
|
|
||||||
public class HTTP2Connection extends AbstractConnection implements Connection.UpgradeTo
|
public class HTTP2Connection extends AbstractConnection
|
||||||
{
|
{
|
||||||
protected static final Logger LOG = Log.getLogger(HTTP2Connection.class);
|
protected static final Logger LOG = Log.getLogger(HTTP2Connection.class);
|
||||||
|
|
||||||
|
@ -61,11 +60,15 @@ public class HTTP2Connection extends AbstractConnection implements Connection.Up
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUpgradeTo(ByteBuffer prefilled)
|
|
||||||
|
protected Parser getParser()
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
return parser;
|
||||||
LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(prefilled));
|
}
|
||||||
producer.buffer = prefilled;
|
|
||||||
|
protected void prefill(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
producer.buffer=buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -124,7 +127,7 @@ public class HTTP2Connection extends AbstractConnection implements Connection.Up
|
||||||
executionStrategy.dispatch();
|
executionStrategy.dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HTTP2Producer implements ExecutionStrategy.Producer
|
protected class HTTP2Producer implements ExecutionStrategy.Producer
|
||||||
{
|
{
|
||||||
private ByteBuffer buffer;
|
private ByteBuffer buffer;
|
||||||
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
ServerSessionListener listener = newSessionListener(connector, endPoint);
|
ServerSessionListener listener = newSessionListener(connector, endPoint);
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,18 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http2.server;
|
package org.eclipse.jetty.http2.server;
|
||||||
|
|
||||||
import org.eclipse.jetty.http2.parser.ServerParser;
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.MetaData.Request;
|
||||||
|
import org.eclipse.jetty.io.Connection;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.util.annotation.Name;
|
import org.eclipse.jetty.util.annotation.Name;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -38,8 +45,10 @@ import org.eclipse.jetty.util.annotation.Name;
|
||||||
* allows a single port to accept either HTTP/1 or HTTP/2 direct
|
* allows a single port to accept either HTTP/1 or HTTP/2 direct
|
||||||
* connections.
|
* connections.
|
||||||
*/
|
*/
|
||||||
public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory
|
public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory implements ConnectionFactory.Upgrading
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(HTTP2CServerConnectionFactory.class);
|
||||||
|
|
||||||
public HTTP2CServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration)
|
public HTTP2CServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration)
|
||||||
{
|
{
|
||||||
super(httpConfiguration,"h2c","h2c-14");
|
super(httpConfiguration,"h2c","h2c-14");
|
||||||
|
@ -52,17 +61,21 @@ public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ServerParser newServerParser(Connector connector, ServerParser.Listener listener)
|
@Override
|
||||||
|
public Connection upgradeConnection(Connector connector, EndPoint endPoint, Request request, HttpFields response101) throws BadMessageException
|
||||||
{
|
{
|
||||||
ServerParser parser = super.newServerParser(connector,listener);
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} upgraded {}",this,request.toString()+request.getFields());
|
||||||
|
|
||||||
|
if (request.getContentLength()>0)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (connector.getDefaultConnectionFactory() instanceof HttpConnectionFactory)
|
HTTP2ServerConnection connection = (HTTP2ServerConnection)newConnection(connector,endPoint);
|
||||||
{
|
|
||||||
// This must be a sneaky upgrade from HTTP/1
|
if (connection.upgrade(request,response101))
|
||||||
// So advance the parsers pointer until after the PRI * HTTP/2.0 request.
|
return connection;
|
||||||
parser.directUpgrade();
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
return parser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,23 +18,37 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http2.server;
|
package org.eclipse.jetty.http2.server;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
|
import org.eclipse.jetty.http.MetaData.Request;
|
||||||
import org.eclipse.jetty.http2.HTTP2Connection;
|
import org.eclipse.jetty.http2.HTTP2Connection;
|
||||||
|
import org.eclipse.jetty.http2.HTTP2Session;
|
||||||
import org.eclipse.jetty.http2.ISession;
|
import org.eclipse.jetty.http2.ISession;
|
||||||
import org.eclipse.jetty.http2.IStream;
|
import org.eclipse.jetty.http2.IStream;
|
||||||
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
|
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||||
import org.eclipse.jetty.http2.parser.Parser;
|
import org.eclipse.jetty.http2.parser.Parser;
|
||||||
|
import org.eclipse.jetty.http2.parser.ServerParser;
|
||||||
|
import org.eclipse.jetty.http2.parser.SettingsBodyParser;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.util.B64Code;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.ConcurrentArrayQueue;
|
import org.eclipse.jetty.util.ConcurrentArrayQueue;
|
||||||
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
|
||||||
public class HTTP2ServerConnection extends HTTP2Connection
|
public class HTTP2ServerConnection extends HTTP2Connection implements Connection.UpgradeTo
|
||||||
{
|
{
|
||||||
private final Queue<HttpChannelOverHTTP2> channels = new ConcurrentArrayQueue<>();
|
private final Queue<HttpChannelOverHTTP2> channels = new ConcurrentArrayQueue<>();
|
||||||
private final ServerSessionListener listener;
|
private final ServerSessionListener listener;
|
||||||
|
@ -47,6 +61,14 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
||||||
this.httpConfig = httpConfig;
|
this.httpConfig = httpConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpgradeTo(ByteBuffer prefilled)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(prefilled));
|
||||||
|
prefill(prefilled);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOpen()
|
public void onOpen()
|
||||||
{
|
{
|
||||||
|
@ -105,6 +127,42 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean upgrade(Request request, HttpFields response101)
|
||||||
|
{
|
||||||
|
if (HttpMethod.PRI.is(request.getMethod()))
|
||||||
|
{
|
||||||
|
((ServerParser)getParser()).directUpgrade();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String value = request.getFields().getField(HttpHeader.HTTP2_SETTINGS).getValue();
|
||||||
|
final byte[] settings = B64Code.decodeRFC4648URL(value);
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} settings {}",this,TypeUtil.toHexString(settings));
|
||||||
|
|
||||||
|
SettingsFrame frame = SettingsBodyParser.parseBody(BufferUtil.toBuffer(settings));
|
||||||
|
if (frame == null)
|
||||||
|
{
|
||||||
|
LOG.warn("Invalid {} header value: {}", HttpHeader.HTTP2_SETTINGS, value);
|
||||||
|
throw new BadMessageException();
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTP2Session session = (HTTP2Session)getSession();
|
||||||
|
// SPEC: the required reply to this SETTINGS frame is the 101 response.
|
||||||
|
session.onSettings(frame, false);
|
||||||
|
|
||||||
|
|
||||||
|
// TODO use the metadata a response
|
||||||
|
// Create stream 1
|
||||||
|
// half close it
|
||||||
|
// arrange for the request meta data to be handled AFTER the subsequesnt #onOpen call
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2
|
private class ServerHttpChannelOverHTTP2 extends HttpChannelOverHTTP2
|
||||||
{
|
{
|
||||||
public ServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport)
|
public ServerHttpChannelOverHTTP2(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransportOverHTTP2 transport)
|
||||||
|
@ -120,4 +178,5 @@ public class HTTP2ServerConnection extends HTTP2Connection
|
||||||
channels.offer(this);
|
channels.offer(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,38 +76,6 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
||||||
return !HTTP2Cipher.isBlackListProtocol(tlsProtocol) || !HTTP2Cipher.isBlackListCipher(tlsCipher);
|
return !HTTP2Cipher.isBlackListProtocol(tlsProtocol) || !HTTP2Cipher.isBlackListCipher(tlsCipher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
|
||||||
{
|
|
||||||
HTTP2ServerConnection connection = (HTTP2ServerConnection)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());
|
|
||||||
|
|
||||||
String value = request.getFields().getField(HttpHeader.HTTP2_SETTINGS).getValue();
|
|
||||||
final byte[] settings = B64Code.decodeRFC4648URL(value);
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("{} settings {}",this,TypeUtil.toHexString(settings));
|
|
||||||
|
|
||||||
SettingsFrame frame = SettingsBodyParser.parseBody(BufferUtil.toBuffer(settings));
|
|
||||||
if (frame == null)
|
|
||||||
throw new IllegalArgumentException(String.format("Invalid %s header value: %s", HttpHeader.HTTP2_SETTINGS, value));
|
|
||||||
|
|
||||||
HTTP2Session session = (HTTP2Session)connection.getSession();
|
|
||||||
// SPEC: the required reply to this SETTINGS frame is the 101 response.
|
|
||||||
session.onSettings(frame, false);
|
|
||||||
|
|
||||||
// TODO use the metadata to push a response
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class HTTPServerSessionListener extends ServerSessionListener.Adapter implements Stream.Listener
|
private class HTTPServerSessionListener extends ServerSessionListener.Adapter implements Stream.Listener
|
||||||
{
|
{
|
||||||
private final Connector connector;
|
private final Connector connector;
|
||||||
|
|
|
@ -123,7 +123,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
* <li>block waiting for new connections
|
* <li>block waiting for new connections
|
||||||
* <li>accept the connection (eg socket accept)
|
* <li>accept the connection (eg socket accept)
|
||||||
* <li>perform any configuration of the connection (eg. socket linger times)
|
* <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, Object)}
|
* <li>call the {@link #getDefaultConnectionFactory()} {@link ConnectionFactory#newConnection(Connector, org.eclipse.jetty.io.EndPoint)}
|
||||||
* method to create a new Connection instance.
|
* method to create a new Connection instance.
|
||||||
* </nl>
|
* </nl>
|
||||||
* The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
|
* The default number of acceptor tasks is the minimum of 1 and half the number of available CPUs. Having more acceptors may reduce
|
||||||
|
|
|
@ -21,6 +21,9 @@ package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
|
||||||
|
@ -58,10 +61,27 @@ public interface ConnectionFactory
|
||||||
* <p>Creates a new {@link Connection} with the given parameters</p>
|
* <p>Creates a new {@link Connection} with the given parameters</p>
|
||||||
* @param connector The {@link Connector} creating this connection
|
* @param connector The {@link Connector} creating this connection
|
||||||
* @param endPoint the {@link EndPoint} associated with the 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}
|
* @return a new {@link Connection}
|
||||||
*/
|
*/
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment);
|
public Connection newConnection(Connector connector, EndPoint endPoint);
|
||||||
|
|
||||||
|
|
||||||
|
public interface Upgrading extends ConnectionFactory
|
||||||
|
{
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Create a connection for an upgrade request.
|
||||||
|
* <p>This is a variation of {@link #newConnection(Connector, EndPoint)} that can create (and/or customise)
|
||||||
|
* a connection for an upgrade request. Implementations may call {@link #newConnection(Connector, EndPoint)} or
|
||||||
|
* may construct the connection instance themselves.</p>
|
||||||
|
*
|
||||||
|
* @param connector The connector to upgrade for.
|
||||||
|
* @param endPoint The endpoint of the connection.
|
||||||
|
* @param upgradeRequest The meta data of the upgrade request.
|
||||||
|
* @param reponseFields The fields to be sent with the 101 response
|
||||||
|
* @return Null to indicate that request processing should continue normally without upgrading. A new connection instance to
|
||||||
|
* indicate that the upgrade should proceed.
|
||||||
|
* @throws BadMessageException Thrown to indicate the upgrade attempt was illegal and that a bad message response should be sent.
|
||||||
|
*/
|
||||||
|
public Connection upgradeConnection(Connector connector, EndPoint endPoint, MetaData.Request upgradeRequest,HttpFields responseFields) throws BadMessageException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package org.eclipse.jetty.server;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HostPortHttpField;
|
import org.eclipse.jetty.http.HostPortHttpField;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
@ -45,16 +46,17 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler
|
class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class);
|
private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class);
|
||||||
|
private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE,"h2c");
|
||||||
|
|
||||||
private final HttpFields _fields = new HttpFields();
|
private final HttpFields _fields = new HttpFields();
|
||||||
private final MetaData.Request _metadata = new MetaData.Request(_fields);
|
private final MetaData.Request _metadata = new MetaData.Request(_fields);
|
||||||
private final HttpConnection _httpConnection;
|
private final HttpConnection _httpConnection;
|
||||||
private HttpField _connection;
|
private HttpField _connection;
|
||||||
|
private HttpField _upgrade = null;
|
||||||
private boolean _delayedForContent;
|
private boolean _delayedForContent;
|
||||||
private boolean _unknownExpectation = false;
|
private boolean _unknownExpectation = false;
|
||||||
private boolean _expect100Continue = false;
|
private boolean _expect100Continue = false;
|
||||||
private boolean _expect102Processing = false;
|
private boolean _expect102Processing = false;
|
||||||
private HttpField _http2Upgrade = null;
|
|
||||||
|
|
||||||
|
|
||||||
public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport)
|
public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport)
|
||||||
|
@ -80,7 +82,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
||||||
_metadata.recycle();
|
_metadata.recycle();
|
||||||
_connection=null;
|
_connection=null;
|
||||||
_fields.clear();
|
_fields.clear();
|
||||||
_http2Upgrade=null;
|
_upgrade=null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -171,8 +173,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPGRADE:
|
case UPGRADE:
|
||||||
if (value.startsWith("h2c"))
|
_upgrade=field;
|
||||||
_http2Upgrade=field;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -302,7 +303,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
||||||
if (!persistent)
|
if (!persistent)
|
||||||
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
|
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
|
||||||
|
|
||||||
if (_http2Upgrade!=null && http2Upgrade())
|
if (_upgrade!=null && upgrade())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -310,8 +311,17 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
||||||
|
|
||||||
case HTTP_2:
|
case HTTP_2:
|
||||||
{
|
{
|
||||||
// Allow sneaky "upgrade" to HTTP_2_0 only if the connector supports h2, but not protocol negotiation
|
// Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2, but not protocol negotiation
|
||||||
return http2Upgrade();
|
_upgrade=PREAMBLE_UPGRADE_H2C;
|
||||||
|
|
||||||
|
if (HttpMethod.PRI.is(_metadata.getMethod()) &&
|
||||||
|
"*".equals(_metadata.getURI().toString()) &&
|
||||||
|
_fields.size()==0 &&
|
||||||
|
upgrade())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
badMessage(HttpStatus.UPGRADE_REQUIRED_426,null);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -332,83 +342,68 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
|
||||||
return !_delayedForContent;
|
return !_delayedForContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean http2Upgrade()
|
|
||||||
|
/**
|
||||||
|
* Attempt to upgrade using a {@link ConnectionFactory.Upgrading} from the connector.
|
||||||
|
* The upgrade may succeed, be ignored (which can allow a later handler to implement)
|
||||||
|
* or fail with a {@link BadMessageException}.
|
||||||
|
* @return
|
||||||
|
* @throws BadMessageException
|
||||||
|
*/
|
||||||
|
private boolean upgrade() throws BadMessageException
|
||||||
{
|
{
|
||||||
LOG.debug("h2c upgrade {}",this);
|
if (LOG.isDebugEnabled())
|
||||||
// Find the h2 factory
|
LOG.debug("upgrade {} {}",this,_upgrade);
|
||||||
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 (_upgrade!=PREAMBLE_UPGRADE_H2C && (_connection==null || !_connection.getValue().contains("Upgrade")))
|
||||||
|
throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
|
||||||
|
|
||||||
if (_http2Upgrade==null)
|
// Find the upgrade factory
|
||||||
|
ConnectionFactory.Upgrading factory=null;
|
||||||
|
loop: for (ConnectionFactory f : getConnector().getConnectionFactories())
|
||||||
{
|
{
|
||||||
LOG.debug("h2c preamble upgrade {}",this);
|
if (f instanceof ConnectionFactory.Upgrading)
|
||||||
// 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);
|
if (f.getProtocols().contains(_upgrade.getValue()))
|
||||||
return false;
|
{
|
||||||
}
|
factory=(ConnectionFactory.Upgrading)f;
|
||||||
|
break loop;
|
||||||
getResponse().setStatus(101); // wont be sent
|
}
|
||||||
new_connection = h2.newConnection(getConnector(),getEndPoint(), null);
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (factory==null)
|
||||||
{
|
{
|
||||||
// This is a standard upgrade, so failures are not bad message, just a false return
|
if (LOG.isDebugEnabled())
|
||||||
if (h2==null)
|
LOG.debug("No factory for {} in {}",_upgrade,getConnector());
|
||||||
{
|
return false;
|
||||||
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);
|
// Create new connection
|
||||||
HttpFields fields = new HttpFields();
|
HttpFields response101 = new HttpFields();
|
||||||
|
Connection upgrade_connection = factory.upgradeConnection(getConnector(),getEndPoint(),_metadata,response101);
|
||||||
try
|
if (upgrade_connection==null)
|
||||||
{
|
{
|
||||||
sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.SWITCHING_PROTOCOLS_101,fields,0),null,true);
|
if (LOG.isDebugEnabled())
|
||||||
}
|
LOG.debug("Upgrade ignored for {} by {}",_upgrade,factory);
|
||||||
catch(IOException e)
|
return false;
|
||||||
{
|
}
|
||||||
LOG.warn(e);
|
|
||||||
badMessage(HttpStatus.INTERNAL_SERVER_ERROR_500,null);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
new_connection = h2.newConnection(getConnector(),getEndPoint(),_metadata);
|
// Send 101 if needed
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_upgrade!=PREAMBLE_UPGRADE_H2C)
|
||||||
|
sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.SWITCHING_PROTOCOLS_101,response101,0),null,true);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500,null,e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Upgrade from {} to {}", old_connection,new_connection);
|
LOG.debug("Upgrade from {} to {}", getEndPoint().getConnection(),upgrade_connection);
|
||||||
getRequest().setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,new_connection);
|
getRequest().setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE,upgrade_connection);
|
||||||
|
getResponse().setStatus(101);
|
||||||
getHttpTransport().onCompleted();
|
getHttpTransport().onCompleted();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
|
return configure(new HttpConnection(_config, connector, endPoint), connector, endPoint);
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ public class LocalConnector extends AbstractConnector
|
||||||
endPoint.onOpen();
|
endPoint.onOpen();
|
||||||
onEndPointOpened(endPoint);
|
onEndPointOpened(endPoint);
|
||||||
|
|
||||||
Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint, null);
|
Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
|
||||||
endPoint.setConnection(connection);
|
endPoint.setConnection(connection);
|
||||||
|
|
||||||
connection.onOpen();
|
connection.onOpen();
|
||||||
|
|
|
@ -128,7 +128,7 @@ public abstract class NegotiatingServerConnection extends AbstractConnection
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
EndPoint endPoint = getEndPoint();
|
EndPoint endPoint = getEndPoint();
|
||||||
Connection newConnection = connectionFactory.newConnection(connector, endPoint, null);
|
Connection newConnection = connectionFactory.newConnection(connector, endPoint);
|
||||||
endPoint.upgrade(newConnection);
|
endPoint.upgrade(newConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ public abstract class NegotiatingServerConnectionFactory extends AbstractConnect
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
List<String> negotiated = this.negotiatedProtocols;
|
List<String> negotiated = this.negotiatedProtocols;
|
||||||
if (negotiated.isEmpty())
|
if (negotiated.isEmpty())
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endp, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endp)
|
||||||
{
|
{
|
||||||
String next=_next;
|
String next=_next;
|
||||||
if (next==null)
|
if (next==null)
|
||||||
|
@ -201,7 +201,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local);
|
EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local);
|
||||||
Connection newConnection = connectionFactory.newConnection(_connector, endPoint, null);
|
Connection newConnection = connectionFactory.newConnection(_connector, endPoint);
|
||||||
endPoint.upgrade(newConnection);
|
endPoint.upgrade(newConnection);
|
||||||
}
|
}
|
||||||
catch (Throwable e)
|
catch (Throwable e)
|
||||||
|
|
|
@ -512,7 +512,7 @@ public class ServerConnector extends AbstractNetworkConnector
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
|
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
|
||||||
{
|
{
|
||||||
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint, null);
|
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
|
SSLEngine engine = _sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
|
||||||
engine.setUseClientMode(false);
|
engine.setUseClientMode(false);
|
||||||
|
@ -83,7 +83,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
||||||
|
|
||||||
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
|
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
|
||||||
EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
|
EndPoint decryptedEndPoint = sslConnection.getDecryptedEndPoint();
|
||||||
Connection connection = next.newConnection(connector, decryptedEndPoint, null);
|
Connection connection = next.newConnection(connector, decryptedEndPoint);
|
||||||
decryptedEndPoint.setConnection(connection);
|
decryptedEndPoint.setConnection(connection);
|
||||||
|
|
||||||
return sslConnection;
|
return sslConnection;
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class ExtendedServerTest extends HttpServerTestBase
|
||||||
startServer(new ServerConnector(_server,new HttpConnectionFactory()
|
startServer(new ServerConnector(_server,new HttpConnectionFactory()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint);
|
return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class SlowClientWithPipelinedRequestTest
|
||||||
connector = new ServerConnector(server,new HttpConnectionFactory()
|
connector = new ServerConnector(server,new HttpConnectionFactory()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public Connection newConnection(Connector connector, EndPoint endPoint, Object attachment)
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
{
|
{
|
||||||
return configure(new HttpConnection(new HttpConfiguration(),connector,endPoint)
|
return configure(new HttpConnection(new HttpConfiguration(),connector,endPoint)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue