From cdd154ead387bc9e76985b48a0895b6a1f8b1de6 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 9 Apr 2019 13:21:12 +0200 Subject: [PATCH] Issue #250 - Implement HTTP CONNECT for HTTP/2. Modified HTTP/2 implementation to support the CONNECT method. Signed-off-by: Simone Bordet --- .../java/org/eclipse/jetty/http/HttpURI.java | 4 +- .../jetty/http2/client/ConnectTunnelTest.java | 98 +++++++++++++++++++ .../jetty/http2/hpack/HpackEncoder.java | 8 +- .../jetty/http2/hpack/MetaDataBuilder.java | 12 ++- 4 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConnectTunnelTest.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java index 3eead93f09e7..99399a33e0c8 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java @@ -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); } /* ------------------------------------------------------------ */ diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConnectTunnelTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConnectTunnelTest.java new file mode 100644 index 000000000000..42184ad2b9f6 --- /dev/null +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/ConnectTunnelTest.java @@ -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 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)); + } +} diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java index 69739ff989a9..4e0af53a5de8 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/HpackEncoder.java @@ -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; @@ -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()) { diff --git a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java index c06bb884a715..5f0e680e606a 100644 --- a/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java +++ b/jetty-http2/http2-hpack/src/main/java/org/eclipse/jetty/http2/hpack/MetaDataBuilder.java @@ -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; @@ -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)