Skip to content

Commit

Permalink
Issue #250 - Implement HTTP CONNECT for HTTP/2.
Browse files Browse the repository at this point in the history
Modified HTTP/2 implementation to support the CONNECT method.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed Apr 30, 2019
1 parent d84a4ef commit e6f7cd3
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 8 deletions.
4 changes: 2 additions & 2 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
Expand Up @@ -164,8 +164,8 @@ public HttpURI(String scheme, String host, int port, String pathQuery)
_host=host;
_port=port;

parse(State.PATH,pathQuery);

if (pathQuery != null)
parse(State.PATH,pathQuery);
}

/* ------------------------------------------------------------ */
Expand Down
@@ -0,0 +1,98 @@
//
// ========================================================================
// 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.http2.client;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FuturePromise;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ConnectTunnelTest extends AbstractTest
{
@Test
public void testCONNECT() 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();
assertNull(uri.getScheme());
assertNull(uri.getPath());
assertNotNull(uri.getAuthority());
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(), null, new HostPortHttpField(authority), null, HttpVersion.HTTP_2, new HttpFields());
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));
}
}
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
Expand Down Expand Up @@ -164,10 +165,13 @@ public void encode(ByteBuffer buffer, MetaData metadata)

// TODO optimise these to avoid HttpField creation
String scheme=request.getURI().getScheme();
encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
if (!HttpMethod.CONNECT.is(request.getMethod()))
{
encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme==null?HttpScheme.HTTP.asString():scheme));
encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
}
}
else if (metadata.isResponse())
{
Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
Expand Down Expand Up @@ -238,10 +239,13 @@ public MetaData build() throws HpackException.StreamException
{
if (_method==null)
throw new HpackException.StreamException("No Method");
if (_scheme==null)
throw new HpackException.StreamException("No Scheme");
if (_path==null)
throw new HpackException.StreamException("No Path");
if (!HttpMethod.CONNECT.is(_method))
{
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);
}
if (_response)
Expand Down

0 comments on commit e6f7cd3

Please sign in to comment.