blob: 81f96481bf6c0678f525df0834166f426535ad62 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 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.http;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.ProxyConfiguration;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http2.HTTP2Session;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ManagedObject("The HTTP/2 client transport")
public class HttpClientTransportOverHTTP2 extends ContainerLifeCycle implements HttpClientTransport
{
private final HTTP2Client client;
private ClientConnectionFactory connectionFactory;
private HttpClient httpClient;
private boolean useALPN = true;
public HttpClientTransportOverHTTP2(HTTP2Client client)
{
this.client = client;
}
@ManagedAttribute(value = "The number of selectors", readonly = true)
public int getSelectors()
{
return client.getSelectors();
}
public boolean isUseALPN()
{
return useALPN;
}
public void setUseALPN(boolean useALPN)
{
this.useALPN = useALPN;
}
@Override
protected void doStart() throws Exception
{
if (!client.isStarted())
{
client.setExecutor(httpClient.getExecutor());
client.setScheduler(httpClient.getScheduler());
client.setByteBufferPool(httpClient.getByteBufferPool());
client.setConnectTimeout(httpClient.getConnectTimeout());
client.setIdleTimeout(httpClient.getIdleTimeout());
client.setInputBufferSize(httpClient.getResponseBufferSize());
}
addBean(client);
super.doStart();
this.connectionFactory = new HTTP2ClientConnectionFactory();
client.setClientConnectionFactory((endPoint, context) ->
{
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
return destination.getClientConnectionFactory().newConnection(endPoint, context);
});
}
@Override
protected void doStop() throws Exception
{
super.doStop();
removeBean(client);
}
protected HttpClient getHttpClient()
{
return httpClient;
}
@Override
public void setHttpClient(HttpClient client)
{
httpClient = client;
}
@Override
public HttpDestination newHttpDestination(Origin origin)
{
return new HttpDestinationOverHTTP2(httpClient, origin);
}
@Override
public void connect(InetSocketAddress address, Map<String, Object> context)
{
client.setConnectTimeout(httpClient.getConnectTimeout());
SessionListenerPromise listenerPromise = new SessionListenerPromise(context);
HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
SslContextFactory sslContextFactory = null;
if (HttpScheme.HTTPS.is(destination.getScheme()))
sslContextFactory = httpClient.getSslContextFactory();
client.connect(sslContextFactory, address, listenerPromise, listenerPromise, context);
}
@Override
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
{
endPoint.setIdleTimeout(httpClient.getIdleTimeout());
ClientConnectionFactory factory = connectionFactory;
HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
ProxyConfiguration.Proxy proxy = destination.getProxy();
boolean ssl = proxy == null ? HttpScheme.HTTPS.is(destination.getScheme()) : proxy.isSecure();
if (ssl && isUseALPN())
factory = new ALPNClientConnectionFactory(client.getExecutor(), factory, client.getProtocols());
return factory.newConnection(endPoint, context);
}
protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session)
{
return new HttpConnectionOverHTTP2(destination, session);
}
protected void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame)
{
connection.close();
}
private class SessionListenerPromise extends Session.Listener.Adapter implements Promise<Session>
{
private final Map<String, Object> context;
private HttpConnectionOverHTTP2 connection;
private SessionListenerPromise(Map<String, Object> context)
{
this.context = context;
}
@Override
public void succeeded(Session session)
{
connection = newHttpConnection(destination(), session);
promise().succeeded(connection);
}
@Override
public void failed(Throwable failure)
{
promise().failed(failure);
}
private HttpDestinationOverHTTP2 destination()
{
return (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
}
@SuppressWarnings("unchecked")
private Promise<Connection> promise()
{
return (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
}
@Override
public Map<Integer, Integer> onPreface(Session session)
{
Map<Integer, Integer> settings = new HashMap<>();
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, client.getInitialStreamRecvWindow());
return settings;
}
@Override
public void onSettings(Session session, SettingsFrame frame)
{
Map<Integer, Integer> settings = frame.getSettings();
if (settings.containsKey(SettingsFrame.MAX_CONCURRENT_STREAMS))
destination().setMaxRequestsPerConnection(settings.get(SettingsFrame.MAX_CONCURRENT_STREAMS));
}
@Override
public void onClose(Session session, GoAwayFrame frame)
{
HttpClientTransportOverHTTP2.this.onClose(connection, frame);
}
@Override
public boolean onIdleTimeout(Session session)
{
return connection.onIdleTimeout(((HTTP2Session)session).getEndPoint().getIdleTimeout());
}
@Override
public void onFailure(Session session, Throwable failure)
{
HttpConnectionOverHTTP2 c = connection;
if (c != null)
c.close(failure);
}
}
}