Made HTTP2Client support SSL so that it can be used to test websites that serve HTTP2.
This commit is contained in:
parent
7004d71996
commit
51e4885911
|
@ -49,6 +49,11 @@
|
|||
<artifactId>http2-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
|
|
|
@ -20,31 +20,30 @@ package org.eclipse.jetty.http2.client;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
|
||||
import org.eclipse.jetty.http2.ErrorCodes;
|
||||
import org.eclipse.jetty.http2.HTTP2Connection;
|
||||
import org.eclipse.jetty.http2.HTTP2FlowControl;
|
||||
import org.eclipse.jetty.http2.HTTP2Session;
|
||||
import org.eclipse.jetty.http2.ISession;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.parser.Parser;
|
||||
import org.eclipse.jetty.http2.parser.PrefaceParser;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
|
@ -80,6 +79,11 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
}
|
||||
|
||||
public void connect(InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
connect(null, address, listener, promise);
|
||||
}
|
||||
|
||||
public void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -87,7 +91,17 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
channel.socket().setTcpNoDelay(true);
|
||||
channel.configureBlocking(false);
|
||||
channel.connect(address);
|
||||
selector.connect(channel, new Context(listener, promise));
|
||||
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put(HTTP2ClientConnectionFactory.CLIENT_CONTEXT_KEY, this);
|
||||
context.put(HTTP2ClientConnectionFactory.SESSION_LISTENER_CONTEXT_KEY, listener);
|
||||
context.put(HTTP2ClientConnectionFactory.SESSION_PROMISE_CONTEXT_KEY, promise);
|
||||
if (sslContextFactory != null)
|
||||
context.put(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY, sslContextFactory);
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, address.getHostString());
|
||||
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, address.getPort());
|
||||
|
||||
selector.connect(channel, context);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -112,6 +126,16 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public boolean addSession(ISession session)
|
||||
{
|
||||
return sessions.offer(session);
|
||||
}
|
||||
|
||||
public boolean removeSession(ISession session)
|
||||
{
|
||||
return sessions.remove(session);
|
||||
}
|
||||
|
||||
private class ClientSelectorManager extends SelectorManager
|
||||
{
|
||||
private ClientSelectorManager(Executor executor, Scheduler scheduler)
|
||||
|
@ -128,71 +152,22 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
@Override
|
||||
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
|
||||
{
|
||||
Context context = (Context)attachment;
|
||||
Generator generator = new Generator(byteBufferPool, 4096);
|
||||
HTTP2Session session = new HTTP2ClientSession(getScheduler(), endpoint, generator, context.listener, new HTTP2FlowControl(65535));
|
||||
Parser parser = new Parser(byteBufferPool, session, 4096, 8192);
|
||||
return new HTTP2ClientConnection(byteBufferPool, getExecutor(), endpoint, parser, session, 8192, context.promise);
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||
context.put(HTTP2ClientConnectionFactory.BYTE_BUFFER_POOL_CONTEXT_KEY, byteBufferPool);
|
||||
context.put(HTTP2ClientConnectionFactory.EXECUTOR_CONTEXT_KEY, getExecutor());
|
||||
context.put(HTTP2ClientConnectionFactory.SCHEDULER_CONTEXT_KEY, getScheduler());
|
||||
|
||||
private class HTTP2ClientConnection extends HTTP2Connection implements Callback
|
||||
{
|
||||
private final Promise<Session> promise;
|
||||
ClientConnectionFactory factory = new HTTP2ClientConnectionFactory();
|
||||
|
||||
public HTTP2ClientConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise<Session> promise)
|
||||
{
|
||||
super(byteBufferPool, executor, endpoint, parser, session, bufferSize);
|
||||
this.promise = promise;
|
||||
}
|
||||
SslContextFactory sslContextFactory = (SslContextFactory)context.get(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY);
|
||||
if (sslContextFactory != null)
|
||||
{
|
||||
ALPNClientConnectionFactory alpn = new ALPNClientConnectionFactory(getExecutor(), factory, "h2-14");
|
||||
factory = new SslClientConnectionFactory(sslContextFactory, byteBufferPool, getExecutor(), alpn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
getEndPoint().write(this, ByteBuffer.wrap(PrefaceParser.PREFACE_BYTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
super.onClose();
|
||||
sessions.remove(getSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onReadTimeout()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Idle timeout {}ms expired on {}", getEndPoint().getIdleTimeout(), this);
|
||||
getSession().close(ErrorCodes.NO_ERROR, "idle_timeout", closeCallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
sessions.offer(getSession());
|
||||
promise.succeeded(getSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
close();
|
||||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private class Context
|
||||
{
|
||||
private final Session.Listener listener;
|
||||
private final Promise<Session> promise;
|
||||
|
||||
private Context(Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
this.listener = listener;
|
||||
this.promise = promise;
|
||||
return factory.newConnection(endpoint, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.http2.ErrorCodes;
|
||||
import org.eclipse.jetty.http2.HTTP2Connection;
|
||||
import org.eclipse.jetty.http2.HTTP2FlowControl;
|
||||
import org.eclipse.jetty.http2.HTTP2Session;
|
||||
import org.eclipse.jetty.http2.ISession;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.generator.Generator;
|
||||
import org.eclipse.jetty.http2.parser.Parser;
|
||||
import org.eclipse.jetty.http2.parser.PrefaceParser;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
|
||||
public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
public static final String CLIENT_CONTEXT_KEY = "http2.client";
|
||||
public static final String BYTE_BUFFER_POOL_CONTEXT_KEY = "http2.client.byteBufferPool";
|
||||
public static final String EXECUTOR_CONTEXT_KEY = "http2.client.executor";
|
||||
public static final String SCHEDULER_CONTEXT_KEY = "http2.client.scheduler";
|
||||
public static final String SESSION_LISTENER_CONTEXT_KEY = "http2.client.sessionListener";
|
||||
public static final String SESSION_PROMISE_CONTEXT_KEY = "http2.client.sessionPromise";
|
||||
|
||||
@Override
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
HTTP2Client client = (HTTP2Client)context.get(CLIENT_CONTEXT_KEY);
|
||||
ByteBufferPool byteBufferPool = (ByteBufferPool)context.get(BYTE_BUFFER_POOL_CONTEXT_KEY);
|
||||
Executor executor = (Executor)context.get(EXECUTOR_CONTEXT_KEY);
|
||||
Scheduler scheduler = (Scheduler)context.get(SCHEDULER_CONTEXT_KEY);
|
||||
Session.Listener listener = (Session.Listener)context.get(SESSION_LISTENER_CONTEXT_KEY);
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Session> promise = (Promise<Session>)context.get(SESSION_PROMISE_CONTEXT_KEY);
|
||||
|
||||
Generator generator = new Generator(byteBufferPool, 4096);
|
||||
HTTP2Session session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, new HTTP2FlowControl(65535));
|
||||
Parser parser = new Parser(byteBufferPool, session, 4096, 8192);
|
||||
return new HTTP2ClientConnection(client, byteBufferPool, executor, endPoint, parser, session, 8192, promise);
|
||||
}
|
||||
|
||||
private class HTTP2ClientConnection extends HTTP2Connection implements Callback
|
||||
{
|
||||
private final HTTP2Client client;
|
||||
private final Promise<Session> promise;
|
||||
|
||||
public HTTP2ClientConnection(HTTP2Client client, ByteBufferPool byteBufferPool, Executor executor, EndPoint endpoint, Parser parser, ISession session, int bufferSize, Promise<Session> promise)
|
||||
{
|
||||
super(byteBufferPool, executor, endpoint, parser, session, bufferSize);
|
||||
this.client = client;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
getEndPoint().write(this, ByteBuffer.wrap(PrefaceParser.PREFACE_BYTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose()
|
||||
{
|
||||
super.onClose();
|
||||
client.removeSession(getSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onReadTimeout()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Idle timeout {}ms expired on {}", getEndPoint().getIdleTimeout(), this);
|
||||
getSession().close(ErrorCodes.NO_ERROR, "idle_timeout", closeCallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
client.addSession(getSession());
|
||||
promise.succeeded(getSession());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
close();
|
||||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2014 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.net.InetSocketAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
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.HeadersFrame;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.Jetty;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public class Client
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
HTTP2Client client = new HTTP2Client();
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.1", "TLSv1");
|
||||
client.addBean(sslContextFactory);
|
||||
client.start();
|
||||
|
||||
FuturePromise<Session> sessionPromise = new FuturePromise<>();
|
||||
client.connect(sslContextFactory, new InetSocketAddress("testsite.webtide.com", 443), new ServerSessionListener.Adapter(), sessionPromise);
|
||||
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
HttpFields requestFields = new HttpFields();
|
||||
requestFields.put("User-Agent", client.getClass().getName() + "/" + Jetty.VERSION);
|
||||
MetaData.Request metaData = new MetaData.Request("GET", new HttpURI("https://testsite.webtide.com/"), HttpVersion.HTTP_2, requestFields);
|
||||
HeadersFrame headersFrame = new HeadersFrame(0, metaData, null, true);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
session.newStream(headersFrame, new Promise.Adapter<Stream>(), new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
System.err.println(frame.getMetaData());
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
latch.await(5, TimeUnit.SECONDS);
|
||||
|
||||
client.stop();
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@ package org.eclipse.jetty.io.ssl;
|
|||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
|
@ -31,6 +30,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
|
|||
|
||||
public class SslClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
public static final String SSL_CONTEXT_FACTORY_CONTEXT_KEY = "ssl.context.factory";
|
||||
public static final String SSL_PEER_HOST_CONTEXT_KEY = "ssl.peer.host";
|
||||
public static final String SSL_PEER_PORT_CONTEXT_KEY = "ssl.peer.port";
|
||||
public static final String SSL_ENGINE_CONTEXT_KEY = "ssl.engine";
|
||||
|
|
Loading…
Reference in New Issue