View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.http2.client.http;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
27  import org.eclipse.jetty.client.HttpClient;
28  import org.eclipse.jetty.client.HttpClientTransport;
29  import org.eclipse.jetty.client.HttpDestination;
30  import org.eclipse.jetty.client.Origin;
31  import org.eclipse.jetty.client.ProxyConfiguration;
32  import org.eclipse.jetty.client.api.Connection;
33  import org.eclipse.jetty.http.HttpScheme;
34  import org.eclipse.jetty.http2.HTTP2Session;
35  import org.eclipse.jetty.http2.api.Session;
36  import org.eclipse.jetty.http2.client.HTTP2Client;
37  import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory;
38  import org.eclipse.jetty.http2.frames.GoAwayFrame;
39  import org.eclipse.jetty.http2.frames.SettingsFrame;
40  import org.eclipse.jetty.io.ClientConnectionFactory;
41  import org.eclipse.jetty.io.EndPoint;
42  import org.eclipse.jetty.util.Promise;
43  import org.eclipse.jetty.util.annotation.ManagedAttribute;
44  import org.eclipse.jetty.util.annotation.ManagedObject;
45  import org.eclipse.jetty.util.component.ContainerLifeCycle;
46  import org.eclipse.jetty.util.ssl.SslContextFactory;
47  
48  @ManagedObject("The HTTP/2 client transport")
49  public class HttpClientTransportOverHTTP2 extends ContainerLifeCycle implements HttpClientTransport
50  {
51      private final HTTP2Client client;
52      private ClientConnectionFactory connectionFactory;
53      private HttpClient httpClient;
54      private boolean useALPN = true;
55  
56      public HttpClientTransportOverHTTP2(HTTP2Client client)
57      {
58          this.client = client;
59      }
60  
61      @ManagedAttribute(value = "The number of selectors", readonly = true)
62      public int getSelectors()
63      {
64          return client.getSelectors();
65      }
66  
67      public boolean isUseALPN()
68      {
69          return useALPN;
70      }
71  
72      public void setUseALPN(boolean useALPN)
73      {
74          this.useALPN = useALPN;
75      }
76  
77      @Override
78      protected void doStart() throws Exception
79      {
80          if (!client.isStarted())
81          {
82              client.setExecutor(httpClient.getExecutor());
83              client.setScheduler(httpClient.getScheduler());
84              client.setByteBufferPool(httpClient.getByteBufferPool());
85              client.setConnectTimeout(httpClient.getConnectTimeout());
86              client.setIdleTimeout(httpClient.getIdleTimeout());
87              client.setInputBufferSize(httpClient.getResponseBufferSize());
88          }
89          addBean(client);
90          super.doStart();
91  
92          this.connectionFactory = new HTTP2ClientConnectionFactory();
93          client.setClientConnectionFactory((endPoint, context) ->
94          {
95              HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
96              return destination.getClientConnectionFactory().newConnection(endPoint, context);
97          });
98      }
99  
100     @Override
101     protected void doStop() throws Exception
102     {
103         super.doStop();
104         removeBean(client);
105     }
106 
107     protected HttpClient getHttpClient()
108     {
109         return httpClient;
110     }
111 
112     @Override
113     public void setHttpClient(HttpClient client)
114     {
115         httpClient = client;
116     }
117 
118     @Override
119     public HttpDestination newHttpDestination(Origin origin)
120     {
121         return new HttpDestinationOverHTTP2(httpClient, origin);
122     }
123 
124     @Override
125     public void connect(InetSocketAddress address, Map<String, Object> context)
126     {
127         client.setConnectTimeout(httpClient.getConnectTimeout());
128 
129         SessionListenerPromise listenerPromise = new SessionListenerPromise(context);
130 
131         HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
132         SslContextFactory sslContextFactory = null;
133         if (HttpScheme.HTTPS.is(destination.getScheme()))
134             sslContextFactory = httpClient.getSslContextFactory();
135 
136         client.connect(sslContextFactory, address, listenerPromise, listenerPromise, context);
137     }
138 
139     @Override
140     public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
141     {
142         endPoint.setIdleTimeout(httpClient.getIdleTimeout());
143 
144         ClientConnectionFactory factory = connectionFactory;
145         HttpDestinationOverHTTP2 destination = (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
146         ProxyConfiguration.Proxy proxy = destination.getProxy();
147         boolean ssl = proxy == null ? HttpScheme.HTTPS.is(destination.getScheme()) : proxy.isSecure();
148         if (ssl && isUseALPN())
149             factory = new ALPNClientConnectionFactory(client.getExecutor(), factory, client.getProtocols());
150         return factory.newConnection(endPoint, context);
151     }
152 
153     protected HttpConnectionOverHTTP2 newHttpConnection(HttpDestination destination, Session session)
154     {
155         return new HttpConnectionOverHTTP2(destination, session);
156     }
157 
158     protected void onClose(HttpConnectionOverHTTP2 connection, GoAwayFrame frame)
159     {
160         connection.close();
161     }
162 
163     private class SessionListenerPromise extends Session.Listener.Adapter implements Promise<Session>
164     {
165         private final Map<String, Object> context;
166         private HttpConnectionOverHTTP2 connection;
167 
168         private SessionListenerPromise(Map<String, Object> context)
169         {
170             this.context = context;
171         }
172 
173         @Override
174         public void succeeded(Session session)
175         {
176             connection = newHttpConnection(destination(), session);
177             promise().succeeded(connection);
178         }
179 
180         @Override
181         public void failed(Throwable failure)
182         {
183             promise().failed(failure);
184         }
185 
186         private HttpDestinationOverHTTP2 destination()
187         {
188             return (HttpDestinationOverHTTP2)context.get(HTTP_DESTINATION_CONTEXT_KEY);
189         }
190 
191         @SuppressWarnings("unchecked")
192         private Promise<Connection> promise()
193         {
194             return (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
195         }
196 
197         @Override
198         public Map<Integer, Integer> onPreface(Session session)
199         {
200             Map<Integer, Integer> settings = new HashMap<>();
201             settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, client.getInitialStreamRecvWindow());
202             return settings;
203         }
204 
205         @Override
206         public void onSettings(Session session, SettingsFrame frame)
207         {
208             Map<Integer, Integer> settings = frame.getSettings();
209             if (settings.containsKey(SettingsFrame.MAX_CONCURRENT_STREAMS))
210                 destination().setMaxRequestsPerConnection(settings.get(SettingsFrame.MAX_CONCURRENT_STREAMS));
211         }
212 
213         @Override
214         public void onClose(Session session, GoAwayFrame frame)
215         {
216             HttpClientTransportOverHTTP2.this.onClose(connection, frame);
217         }
218 
219         @Override
220         public boolean onIdleTimeout(Session session)
221         {
222             return connection.onIdleTimeout(((HTTP2Session)session).getEndPoint().getIdleTimeout());
223         }
224 
225         @Override
226         public void onFailure(Session session, Throwable failure)
227         {
228             HttpConnectionOverHTTP2 c = connection;
229             if (c != null)
230                 c.close(failure);
231         }
232     }
233 }