1 /* |
|
2 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
24 */ |
|
25 |
|
26 package jdk.incubator.http; |
|
27 |
|
28 import java.lang.System.Logger.Level; |
|
29 import java.net.InetSocketAddress; |
|
30 import java.net.URI; |
|
31 import java.util.Base64; |
|
32 import java.util.Collections; |
|
33 import java.util.HashSet; |
|
34 import java.util.HashMap; |
|
35 import java.util.Map; |
|
36 import java.util.Set; |
|
37 import java.util.concurrent.ConcurrentHashMap; |
|
38 import java.util.concurrent.CompletableFuture; |
|
39 |
|
40 import jdk.incubator.http.internal.common.Log; |
|
41 import jdk.incubator.http.internal.common.MinimalFuture; |
|
42 import jdk.incubator.http.internal.common.Utils; |
|
43 import jdk.incubator.http.internal.frame.SettingsFrame; |
|
44 import static jdk.incubator.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE; |
|
45 import static jdk.incubator.http.internal.frame.SettingsFrame.ENABLE_PUSH; |
|
46 import static jdk.incubator.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE; |
|
47 import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS; |
|
48 import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_FRAME_SIZE; |
|
49 |
|
50 /** |
|
51 * Http2 specific aspects of HttpClientImpl |
|
52 */ |
|
53 class Http2ClientImpl { |
|
54 |
|
55 static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag. |
|
56 final static System.Logger debug = |
|
57 Utils.getDebugLogger("Http2ClientImpl"::toString, DEBUG); |
|
58 |
|
59 private final HttpClientImpl client; |
|
60 |
|
61 Http2ClientImpl(HttpClientImpl client) { |
|
62 this.client = client; |
|
63 } |
|
64 |
|
65 /* Map key is "scheme:host:port" */ |
|
66 private final Map<String,Http2Connection> connections = new ConcurrentHashMap<>(); |
|
67 |
|
68 private final Set<String> failures = Collections.synchronizedSet(new HashSet<>()); |
|
69 |
|
70 /** |
|
71 * When HTTP/2 requested only. The following describes the aggregate behavior including the |
|
72 * calling code. In all cases, the HTTP2 connection cache |
|
73 * is checked first for a suitable connection and that is returned if available. |
|
74 * If not, a new connection is opened, except in https case when a previous negotiate failed. |
|
75 * In that case, we want to continue using http/1.1. When a connection is to be opened and |
|
76 * if multiple requests are sent in parallel then each will open a new connection. |
|
77 * |
|
78 * If negotiation/upgrade succeeds then |
|
79 * one connection will be put in the cache and the others will be closed |
|
80 * after the initial request completes (not strictly necessary for h2, only for h2c) |
|
81 * |
|
82 * If negotiate/upgrade fails, then any opened connections remain open (as http/1.1) |
|
83 * and will be used and cached in the http/1 cache. Note, this method handles the |
|
84 * https failure case only (by completing the CF with an ALPN exception, handled externally) |
|
85 * The h2c upgrade is handled externally also. |
|
86 * |
|
87 * Specific CF behavior of this method. |
|
88 * 1. completes with ALPN exception: h2 negotiate failed for first time. failure recorded. |
|
89 * 2. completes with other exception: failure not recorded. Caller must handle |
|
90 * 3. completes normally with null: no connection in cache for h2c or h2 failed previously |
|
91 * 4. completes normally with connection: h2 or h2c connection in cache. Use it. |
|
92 */ |
|
93 CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) { |
|
94 URI uri = req.uri(); |
|
95 InetSocketAddress proxy = req.proxy(); |
|
96 String key = Http2Connection.keyFor(uri, proxy); |
|
97 |
|
98 synchronized (this) { |
|
99 Http2Connection connection = connections.get(key); |
|
100 if (connection != null) { // fast path if connection already exists |
|
101 return MinimalFuture.completedFuture(connection); |
|
102 } |
|
103 |
|
104 if (!req.secure() || failures.contains(key)) { |
|
105 // secure: negotiate failed before. Use http/1.1 |
|
106 // !secure: no connection available in cache. Attempt upgrade |
|
107 return MinimalFuture.completedFuture(null); |
|
108 } |
|
109 } |
|
110 return Http2Connection |
|
111 .createAsync(req, this) |
|
112 .whenComplete((conn, t) -> { |
|
113 synchronized (Http2ClientImpl.this) { |
|
114 if (conn != null) { |
|
115 offerConnection(conn); |
|
116 } else { |
|
117 Throwable cause = Utils.getCompletionCause(t); |
|
118 if (cause instanceof Http2Connection.ALPNException) |
|
119 failures.add(key); |
|
120 } |
|
121 } |
|
122 }); |
|
123 } |
|
124 |
|
125 /* |
|
126 * Cache the given connection, if no connection to the same |
|
127 * destination exists. If one exists, then we let the initial stream |
|
128 * complete but allow it to close itself upon completion. |
|
129 * This situation should not arise with https because the request |
|
130 * has not been sent as part of the initial alpn negotiation |
|
131 */ |
|
132 boolean offerConnection(Http2Connection c) { |
|
133 String key = c.key(); |
|
134 Http2Connection c1 = connections.putIfAbsent(key, c); |
|
135 if (c1 != null) { |
|
136 c.setSingleStream(true); |
|
137 return false; |
|
138 } |
|
139 return true; |
|
140 } |
|
141 |
|
142 void deleteConnection(Http2Connection c) { |
|
143 connections.remove(c.key()); |
|
144 } |
|
145 |
|
146 void stop() { |
|
147 debug.log(Level.DEBUG, "stopping"); |
|
148 connections.values().forEach(this::close); |
|
149 connections.clear(); |
|
150 } |
|
151 |
|
152 private void close(Http2Connection h2c) { |
|
153 try { h2c.close(); } catch (Throwable t) {} |
|
154 } |
|
155 |
|
156 HttpClientImpl client() { |
|
157 return client; |
|
158 } |
|
159 |
|
160 /** Returns the client settings as a base64 (url) encoded string */ |
|
161 String getSettingsString() { |
|
162 SettingsFrame sf = getClientSettings(); |
|
163 byte[] settings = sf.toByteArray(); // without the header |
|
164 Base64.Encoder encoder = Base64.getUrlEncoder() |
|
165 .withoutPadding(); |
|
166 return encoder.encodeToString(settings); |
|
167 } |
|
168 |
|
169 private static final int K = 1024; |
|
170 |
|
171 private static int getParameter(String property, int min, int max, int defaultValue) { |
|
172 int value = Utils.getIntegerNetProperty(property, defaultValue); |
|
173 // use default value if misconfigured |
|
174 if (value < min || value > max) { |
|
175 Log.logError("Property value for {0}={1} not in [{2}..{3}]: " + |
|
176 "using default={4}", property, value, min, max, defaultValue); |
|
177 value = defaultValue; |
|
178 } |
|
179 return value; |
|
180 } |
|
181 |
|
182 // used for the connection window, to have a connection window size |
|
183 // bigger than the initial stream window size. |
|
184 int getConnectionWindowSize(SettingsFrame clientSettings) { |
|
185 // Maximum size is 2^31-1. Don't allow window size to be less |
|
186 // than the stream window size. HTTP/2 specify a default of 64 * K -1, |
|
187 // but we use 2^26 by default for better performance. |
|
188 int streamWindow = clientSettings.getParameter(INITIAL_WINDOW_SIZE); |
|
189 |
|
190 // The default is the max between the stream window size |
|
191 // and the connection window size. |
|
192 int defaultValue = Math.min(Integer.MAX_VALUE, |
|
193 Math.max(streamWindow, K*K*32)); |
|
194 |
|
195 return getParameter( |
|
196 "jdk.httpclient.connectionWindowSize", |
|
197 streamWindow, Integer.MAX_VALUE, defaultValue); |
|
198 } |
|
199 |
|
200 SettingsFrame getClientSettings() { |
|
201 SettingsFrame frame = new SettingsFrame(); |
|
202 // default defined for HTTP/2 is 4 K, we use 16 K. |
|
203 frame.setParameter(HEADER_TABLE_SIZE, getParameter( |
|
204 "jdk.httpclient.hpack.maxheadertablesize", |
|
205 0, Integer.MAX_VALUE, 16 * K)); |
|
206 // O: does not accept push streams. 1: accepts push streams. |
|
207 frame.setParameter(ENABLE_PUSH, getParameter( |
|
208 "jdk.httpclient.enablepush", |
|
209 0, 1, 1)); |
|
210 // HTTP/2 recommends to set the number of concurrent streams |
|
211 // no lower than 100. We use 100. 0 means no stream would be |
|
212 // accepted. That would render the client to be non functional, |
|
213 // so we won't let 0 be configured for our Http2ClientImpl. |
|
214 frame.setParameter(MAX_CONCURRENT_STREAMS, getParameter( |
|
215 "jdk.httpclient.maxstreams", |
|
216 1, Integer.MAX_VALUE, 100)); |
|
217 // Maximum size is 2^31-1. Don't allow window size to be less |
|
218 // than the minimum frame size as this is likely to be a |
|
219 // configuration error. HTTP/2 specify a default of 64 * K -1, |
|
220 // but we use 16 M for better performance. |
|
221 frame.setParameter(INITIAL_WINDOW_SIZE, getParameter( |
|
222 "jdk.httpclient.windowsize", |
|
223 16 * K, Integer.MAX_VALUE, 16*K*K)); |
|
224 // HTTP/2 specify a minimum size of 16 K, a maximum size of 2^24-1, |
|
225 // and a default of 16 K. We use 16 K as default. |
|
226 frame.setParameter(MAX_FRAME_SIZE, getParameter( |
|
227 "jdk.httpclient.maxframesize", |
|
228 16 * K, 16 * K * K -1, 16 * K)); |
|
229 return frame; |
|
230 } |
|
231 } |
|