Skip to content

Commit

Permalink
Merge pull request #3740 from eclipse/jetty-10.0.x-3537-bootstrap_web…
Browse files Browse the repository at this point in the history
…socket_http2

Issue #3537 - Bootstrap websocket on HTTP/2
  • Loading branch information
sbordet committed Nov 28, 2019
2 parents e9c3eb0 + dc4ada1 commit 3b81782
Show file tree
Hide file tree
Showing 54 changed files with 1,449 additions and 663 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1101,7 +1101,7 @@ public boolean isDefaultPort(String scheme, int port)
return port == 80;
}

static boolean isSchemeSecure(String scheme)
public static boolean isSchemeSecure(String scheme)
{
return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public class HttpRequest implements Request
private List<RequestListener> requestListeners;
private BiFunction<Request, Request, Response.CompleteListener> pushListener;
private Supplier<HttpFields> trailers;
private String upgradeProtocol;

protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
{
Expand Down Expand Up @@ -635,6 +636,12 @@ public HttpRequest trailers(Supplier<HttpFields> trailers)
return this;
}

public HttpRequest upgradeProtocol(String upgradeProtocol)
{
this.upgradeProtocol = upgradeProtocol;
return this;
}

@Override
public ContentProvider getContent()
{
Expand Down Expand Up @@ -791,6 +798,11 @@ public Supplier<HttpFields> getTrailers()
return trailers;
}

public String getUpgradeProtocol()
{
return upgradeProtocol;
}

@Override
public boolean abort(Throwable cause)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//

package org.eclipse.jetty.client;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.EndPoint;

public interface HttpUpgrader
{
public void prepare(HttpRequest request);

public void upgrade(HttpResponse response, EndPoint endPoint);

public interface Factory
{
public HttpUpgrader newHttpUpgrader(HttpVersion version);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@
import org.eclipse.jetty.alpn.client.ALPNClientConnection;
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.MultiplexConnectionPool;
import org.eclipse.jetty.client.MultiplexHttpDestination;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
Expand Down Expand Up @@ -121,7 +121,7 @@ public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFac
@Override
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
{
boolean ssl = HttpScheme.HTTPS.is(request.getScheme());
boolean ssl = HttpClient.isSchemeSecure(request.getScheme());
String http1 = "http/1.1";
String http2 = ssl ? "h2" : "h2c";
List<String> protocols = List.of();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.HttpResponseException;
import org.eclipse.jetty.client.HttpUpgrader;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
Expand Down Expand Up @@ -98,29 +99,24 @@ public Result exchangeTerminating(HttpExchange exchange, Result result)
return result;

HttpResponse response = exchange.getResponse();

if ((response.getVersion() == HttpVersion.HTTP_1_1) &&
(response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101))
if (response.getVersion() == HttpVersion.HTTP_1_1 && response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
{
String nextConnection = response.getHeaders().get(HttpHeader.CONNECTION);
if ((nextConnection == null) || !nextConnection.toLowerCase(Locale.US).contains("upgrade"))
{
return new Result(result, new HttpResponseException("101 Switching Protocols without Connection: Upgrade not supported", response));
}
String header = response.getHeaders().get(HttpHeader.CONNECTION);
if (header == null || !header.toLowerCase(Locale.US).contains("upgrade"))
return new Result(result, new HttpResponseException("101 response without 'Connection: Upgrade'", response));

// Upgrade Response
HttpRequest request = exchange.getRequest();
HttpConnectionUpgrader upgrader = (HttpConnectionUpgrader)request.getConversation().getAttribute(HttpConnectionUpgrader.class.getName());
if (upgrader != null)
HttpUpgrader upgrader = (HttpUpgrader)request.getConversation().getAttribute(HttpUpgrader.class.getName());
if (upgrader == null)
return new Result(result, new HttpResponseException("101 response without " + HttpUpgrader.class.getSimpleName(), response));

try
{
upgrader.upgrade(response, getHttpConnection().getEndPoint());
}
catch (Throwable x)
{
try
{
upgrader.upgrade(response, getHttpConnection());
}
catch (Throwable x)
{
return new Result(result, x);
}
return new Result(result, new HttpResponseException("Could not upgrade to WebSocket", response, x));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpUpgrader;
import org.eclipse.jetty.client.IConnection;
import org.eclipse.jetty.client.SendFailure;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Promise;
Expand Down Expand Up @@ -258,6 +261,12 @@ protected void normalizeRequest(Request request)
request.timeout(connectTimeout, TimeUnit.MILLISECONDS)
.idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS);
}
if (request instanceof HttpUpgrader.Factory)
{
HttpUpgrader upgrader = ((HttpUpgrader.Factory)request).newHttpUpgrader(HttpVersion.HTTP_1_1);
((HttpRequest)request).getConversation().setAttribute(HttpUpgrader.class.getName(), upgrader);
upgrader.prepare((HttpRequest)request);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,7 @@ public boolean messageComplete()
if (status == HttpStatus.SWITCHING_PROTOCOLS_101)
return true;

if (HttpMethod.CONNECT.is(exchange.getRequest().getMethod()) &&
status == HttpStatus.OK_200)
if (HttpMethod.CONNECT.is(exchange.getRequest().getMethod()) && status == HttpStatus.OK_200)
return true;

return false;
Expand Down
5 changes: 5 additions & 0 deletions jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,11 @@ public static class ConnectRequest extends Request
private String _protocol;

public ConnectRequest(HttpScheme scheme, HostPortHttpField authority, String path, HttpFields fields, String protocol)
{
this(scheme == null ? null : scheme.asString(), authority, path, fields, protocol);
}

public ConnectRequest(String scheme, HostPortHttpField authority, String path, HttpFields fields, String protocol)
{
super(HttpMethod.CONNECT.asString(), scheme, authority, path, HttpVersion.HTTP_2, fields, Long.MIN_VALUE);
_protocol = protocol;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
private int initialSessionRecvWindow;
private int writeThreshold;
private boolean pushEnabled;
private boolean connectProtocolEnabled;
private long idleTime;
private GoAwayFrame closeFrame;

Expand Down Expand Up @@ -370,6 +371,14 @@ public void onSettings(SettingsFrame frame, boolean reply)
generator.setMaxHeaderListSize(value);
break;
}
case SettingsFrame.ENABLE_CONNECT_PROTOCOL:
{
boolean enabled = value == 1;
if (LOG.isDebugEnabled())
LOG.debug("{} CONNECT protocol for {}", enabled ? "Enabling" : "Disabling", this);
connectProtocolEnabled = enabled;
break;
}
default:
{
if (LOG.isDebugEnabled())
Expand Down Expand Up @@ -906,6 +915,17 @@ public boolean isPushEnabled()
return pushEnabled;
}

@ManagedAttribute(value = "Whether CONNECT requests supports a protocol", readonly = true)
public boolean isConnectProtocolEnabled()
{
return connectProtocolEnabled;
}

public void setConnectProtocolEnabled(boolean connectProtocolEnabled)
{
this.connectProtocolEnabled = connectProtocolEnabled;
}

/**
* A typical close by a remote peer involves a GO_AWAY frame followed by TCP FIN.
* This method is invoked when the TCP FIN is received, or when an exception is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,16 +372,18 @@ else if (!dataProcess)
dataProcess = proceed = dataDemand > 0;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} data processing of {} for {}", initial ? "Starting" : proceed ? "Proceeding" : "Stalling", frame, this);
if (initial)
{
if (LOG.isDebugEnabled())
LOG.debug("Starting data processing of {} for {}", frame, this);
notifyBeforeData(this);
try (AutoLock l = lock.lock())
{
dataProcess = proceed = dataDemand > 0;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} data processing of {} for {}", proceed ? "Proceeding" : "Stalling", frame, this);
if (proceed)
processData();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class SettingsFrame extends Frame
public static final int INITIAL_WINDOW_SIZE = 4;
public static final int MAX_FRAME_SIZE = 5;
public static final int MAX_HEADER_LIST_SIZE = 6;
public static final int ENABLE_CONNECT_PROTOCOL = 8;

private final Map<Integer, Integer> settings;
private final boolean reply;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,22 +177,22 @@ else if (value != null)
_contentLength = field.getLongValue();
_fields.add(field);
break;

case TE:
if ("trailers".equalsIgnoreCase(value))
_fields.add(field);
else
streamException("Unsupported TE value '%s'", value);
break;

case CONNECTION:
if ("TE".equalsIgnoreCase(value))
_fields.add(field);
else
streamException("Connection specific field '%s'", header);
break;
break;

default:
default:
if (name.charAt(0) == ':')
streamException("Unknown pseudo header '%s'", name);
else
Expand Down Expand Up @@ -238,7 +238,7 @@ public MetaData build() throws HpackException.StreamException
_streamException.addSuppressed(new Throwable());
throw _streamException;
}

if (_request && _response)
throw new HpackException.StreamException("Request and Response headers");

Expand Down Expand Up @@ -268,7 +268,7 @@ public MetaData build() throws HpackException.StreamException
throw new HpackException.StreamException("No Status");
return new MetaData.Response(HttpVersion.HTTP_2, _status, fields, _contentLength);
}

return new MetaData(HttpVersion.HTTP_2, fields, _contentLength);
}
finally
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpUpgrader;
import org.eclipse.jetty.client.SendFailure;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.IStream;
Expand Down Expand Up @@ -88,6 +90,18 @@ public SendFailure send(HttpExchange exchange)
return send(channel, exchange);
}

@Override
protected void normalizeRequest(Request request)
{
super.normalizeRequest(request);
if (request instanceof HttpUpgrader.Factory)
{
HttpUpgrader upgrader = ((HttpUpgrader.Factory)request).newHttpUpgrader(HttpVersion.HTTP_2);
((HttpRequest)request).getConversation().setAttribute(HttpUpgrader.class.getName(), upgrader);
upgrader.prepare((HttpRequest)request);
}
}

protected HttpChannelOverHTTP2 acquireHttpChannel()
{
HttpChannelOverHTTP2 channel = idleChannels.poll();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import java.util.function.BiFunction;

import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpConversation;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.HttpUpgrader;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpField;
Expand Down Expand Up @@ -110,7 +112,11 @@ void onHeaders(Stream stream, HeadersFrame frame)
if (LOG.isDebugEnabled())
LOG.debug("Successful HTTP2 tunnel on {} via {}", stream, endPoint);
((IStream)stream).setAttachment(endPoint);
httpRequest.getConversation().setAttribute(EndPoint.class.getName(), endPoint);
HttpConversation conversation = httpRequest.getConversation();
conversation.setAttribute(EndPoint.class.getName(), endPoint);
HttpUpgrader upgrader = (HttpUpgrader)conversation.getAttribute(HttpUpgrader.class.getName());
if (upgrader != null)
upgrader.upgrade(httpResponse, endPoint);
}

if (responseHeaders(exchange))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,16 @@ protected void sendHeaders(HttpExchange exchange, final HttpContent content, fin
MetaData.Request metaData;
if (isTunnel)
{
metaData = new MetaData.Request(request.getMethod(), null, new HostPortHttpField(request.getPath()), null, HttpVersion.HTTP_2, request.getHeaders());
String upgradeProtocol = request.getUpgradeProtocol();
if (upgradeProtocol == null)
{
metaData = new MetaData.ConnectRequest((String)null, new HostPortHttpField(request.getPath()), null, request.getHeaders(), null);
}
else
{
HostPortHttpField authority = new HostPortHttpField(request.getHost(), request.getPort());
metaData = new MetaData.ConnectRequest(request.getScheme(), authority, request.getPath(), request.getHeaders(), upgradeProtocol);
}
}
else
{
Expand Down
Loading

0 comments on commit 3b81782

Please sign in to comment.