1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 }