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.internal.net.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.internal.net.http.common.Log; 41 import jdk.internal.net.http.common.MinimalFuture; 42 import jdk.internal.net.http.common.Utils; 43 import jdk.internal.net.http.frame.SettingsFrame; 44 import static jdk.internal.net.http.frame.SettingsFrame.INITIAL_WINDOW_SIZE; 45 import static jdk.internal.net.http.frame.SettingsFrame.ENABLE_PUSH; 46 import static jdk.internal.net.http.frame.SettingsFrame.HEADER_TABLE_SIZE; 47 import static jdk.internal.net.http.frame.SettingsFrame.MAX_CONCURRENT_STREAMS; 48 import static jdk.internal.net.http.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 }