Skip to content

Commit

Permalink
Issue #250 - Implement HTTP CONNECT for HTTP/2.
Browse files Browse the repository at this point in the history
Implemented semantic defined by RFC 8441.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed May 3, 2019
1 parent cc54ca1 commit 8e2da6b
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public enum HttpHeader
C_AUTHORITY(":authority"),
C_PATH(":path"),
C_STATUS(":status"),
C_PROTOCOL(":protocol"),

UNKNOWN("::UNKNOWN::");

Expand Down
43 changes: 25 additions & 18 deletions jetty-http/src/main/java/org/eclipse/jetty/http/MetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public static class Request extends MetaData
{
private String _method;
private HttpURI _uri;
private String _protocol;

public Request(HttpFields fields)
{
Expand All @@ -153,31 +154,26 @@ public Request(String method, HttpURI uri, HttpVersion version, HttpFields field

public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
{
this(method, new HttpURI(scheme == null ? null : scheme.asString(),
hostPort==null?null:hostPort.getHost(),
hostPort==null?-1:hostPort.getPort(),
uri), version, fields);
this(method, scheme, hostPort, uri, version, fields, Long.MIN_VALUE);
}

public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(method, new HttpURI(scheme==null?null:scheme.asString(),
hostPort==null?null:hostPort.getHost(),
hostPort==null?-1:hostPort.getPort(),
uri), version, fields, contentLength);
this(method, scheme == null ? null : scheme.asString(), hostPort, uri, version, fields, contentLength);
}

public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
{
this(method, new HttpURI(scheme,
hostPort==null?null:hostPort.getHost(),
hostPort==null?-1:hostPort.getPort(),
uri), version, fields, contentLength);
hostPort == null ? null : hostPort.getHost(),
hostPort == null ? -1 : hostPort.getPort(),
uri), version, fields, contentLength);
}

public Request(Request request)
{
this(request.getMethod(),new HttpURI(request.getURI()), request.getHttpVersion(), new HttpFields(request.getFields()), request.getContentLength());
setProtocol(request.getProtocol());
}

@Override
Expand All @@ -187,6 +183,7 @@ public void recycle()
_method = null;
if (_uri != null)
_uri.clear();
_protocol = null;
}

@Override
Expand Down Expand Up @@ -219,6 +216,14 @@ public HttpURI getURI()
return _uri;
}

/**
* @param uri the HTTP URI to set
*/
public void setURI(HttpURI uri)
{
_uri = uri;
}

/**
* @return the HTTP URI in string form
*/
Expand All @@ -227,20 +232,22 @@ public String getURIString()
return _uri == null ? null : _uri.toString();
}

/**
* @param uri the HTTP URI to set
*/
public void setURI(HttpURI uri)
public String getProtocol()
{
_uri = uri;
return _protocol;
}

public void setProtocol(String protocol)
{
_protocol = protocol;
}

@Override
public String toString()
{
HttpFields fields = getFields();
return String.format("%s{u=%s,%s,h=%d,cl=%d}",
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength());
return String.format("%s{u=%s,%s,h=%d,cl=%d,p=%s}",
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength(), getProtocol());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
Expand Down Expand Up @@ -95,4 +96,57 @@ public void onData(Stream stream, DataFrame frame, Callback callback)

assertTrue(latch.await(5, TimeUnit.SECONDS));
}

@Test
public void testCONNECTWithProtocol() throws Exception
{
start(new ServerSessionListener.Adapter()
{
@Override
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
{
// Verifies that the CONNECT request is well formed.
MetaData.Request request = (MetaData.Request)frame.getMetaData();
assertEquals(HttpMethod.CONNECT.asString(), request.getMethod());
HttpURI uri = request.getURI();
assertNotNull(uri.getScheme());
assertNotNull(uri.getPath());
assertNotNull(uri.getAuthority());
assertNotNull(request.getProtocol());
return new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
stream.data(frame, callback);
}
};
}
});

Session client = newClient(new Session.Listener.Adapter());

CountDownLatch latch = new CountDownLatch(1);
byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8);
String host = "localhost";
int port = connector.getLocalPort();
String authority = host + ":" + port;
MetaData.Request request = new MetaData.Request(HttpMethod.CONNECT.asString(), HttpScheme.HTTP, new HostPortHttpField(authority), "/", HttpVersion.HTTP_2, new HttpFields());
request.setProtocol("websocket");
FuturePromise<Stream> streamPromise = new FuturePromise<>();
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
{
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
if (frame.isEndStream())
latch.countDown();
}
});
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
ByteBuffer data = ByteBuffer.wrap(bytes);
stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP);

assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,14 @@ public void encode(ByteBuffer buffer, MetaData metadata)
String scheme=request.getURI().getScheme();
encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
if (!HttpMethod.CONNECT.is(request.getMethod()))
boolean isConnect = HttpMethod.CONNECT.is(request.getMethod());
String protocol = request.getProtocol();
if (!isConnect || protocol != null)
{
encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
if (protocol != null)
encode(buffer,new HttpField(HttpHeader.C_PROTOCOL,protocol));
}
}
else if (metadata.isResponse())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class MetaDataBuilder
private HttpScheme _scheme;
private HostPortHttpField _authority;
private String _path;
private String _protocol;
private long _contentLength=Long.MIN_VALUE;
private HttpFields _fields = new HttpFields(10);
private HpackException.StreamException _streamException;
Expand Down Expand Up @@ -139,6 +140,23 @@ else if (value != null)
_request = true;
break;

case C_PATH:
if(checkPseudoHeader(header, _path))
{
if (value!=null && value.length()>0)
_path = value;
else
streamException("No Path");
}
_request = true;
break;

case C_PROTOCOL:
if (checkPseudoHeader(header, _protocol))
_protocol = value;
_request = true;
break;

case HOST:
// :authority fields must come first. If we have one, ignore the host header as far as authority goes.
if (_authority==null)
Expand All @@ -151,17 +169,6 @@ else if (value != null)
_fields.add(field);
break;

case C_PATH:
if(checkPseudoHeader(header, _path))
{
if (value!=null && value.length()>0)
_path = value;
else
streamException("No Path");
}
_request = true;
break;

case CONTENT_LENGTH:
_contentLength = field.getLongValue();
_fields.add(field);
Expand Down Expand Up @@ -239,14 +246,17 @@ public MetaData build() throws HpackException.StreamException
{
if (_method==null)
throw new HpackException.StreamException("No Method");
if (!HttpMethod.CONNECT.is(_method))
boolean isConnect = HttpMethod.CONNECT.is(_method);
if (!isConnect || _protocol != null)
{
if (_scheme==null)
throw new HpackException.StreamException("No Scheme");
if (_path==null)
throw new HpackException.StreamException("No Path");
}
return new MetaData.Request(_method,_scheme,_authority,_path,HttpVersion.HTTP_2,fields,_contentLength);
MetaData.Request request = new MetaData.Request(_method, _scheme, _authority, _path, HttpVersion.HTTP_2, fields, _contentLength);
request.setProtocol(_protocol);
return request;
}
if (_response)
{
Expand All @@ -267,6 +277,7 @@ public MetaData build() throws HpackException.StreamException
_scheme = null;
_authority = null;
_path = null;
_protocol = null;
_size = 0;
_contentLength = Long.MIN_VALUE;
}
Expand Down

0 comments on commit 8e2da6b

Please sign in to comment.