1 /* 2 * Copyright (c) 2015, 2017, 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 import jdk.incubator.http.internal.common.MinimalFuture; 40 import jdk.incubator.http.internal.common.Utils; 41 import jdk.incubator.http.internal.frame.SettingsFrame; 42 import static jdk.incubator.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE; 43 import static jdk.incubator.http.internal.frame.SettingsFrame.ENABLE_PUSH; 44 import static jdk.incubator.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE; 45 import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS; 46 import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_FRAME_SIZE; 47 48 /** 49 * Http2 specific aspects of HttpClientImpl 50 */ 51 class Http2ClientImpl { 52 53 static final boolean DEBUG = Utils.DEBUG; // Revisit: temporary dev flag. 54 final static System.Logger debug = 55 Utils.getDebugLogger("Http2ClientImpl"::toString, DEBUG); 56 57 private final HttpClientImpl client; 58 59 Http2ClientImpl(HttpClientImpl client) { 60 this.client = client; 61 } 62 63 /* Map key is "scheme:host:port" */ 64 private final Map<String,Http2Connection> connections = new ConcurrentHashMap<>(); 65 66 private final Set<String> opening = Collections.synchronizedSet(new HashSet<>()); 67 private final Map<String,Set<CompletableFuture<Http2Connection>>> waiting = 68 Collections.synchronizedMap(new HashMap<>()); 69 70 private void addToWaiting(String key, CompletableFuture<Http2Connection> cf) { 71 synchronized (waiting) { 72 Set<CompletableFuture<Http2Connection>> waiters = waiting.get(key); 73 if (waiters == null) { 74 waiters = new HashSet<>(); 75 waiting.put(key, waiters); 76 } 77 waiters.add(cf); 78 } 79 } 80 81 // boolean haveConnectionFor(URI uri, InetSocketAddress proxy) { 82 // return connections.containsKey(Http2Connection.keyFor(uri,proxy)); 83 // } 84 85 /** 86 * If a https request then async waits until a connection is opened. 87 * Returns null if the request is 'http' as a different (upgrade) 88 * mechanism is used. 89 * 90 * Only one connection per destination is created. Blocks when opening 91 * connection, or when waiting for connection to be opened. 92 * First thread opens the connection and notifies the others when done. 93 * 94 * If the request is secure (https) then we open the connection here. 95 * If not, then the more complicated upgrade from 1.1 to 2 happens (not here) 96 * In latter case, when the Http2Connection is connected, putConnection() must 97 * be called to store it. 98 */ 99 CompletableFuture<Http2Connection> getConnectionFor(HttpRequestImpl req) { 100 URI uri = req.uri(); 101 InetSocketAddress proxy = req.proxy(); 102 String key = Http2Connection.keyFor(uri, proxy); 103 104 synchronized (opening) { 105 Http2Connection connection = connections.get(key); 106 if (connection != null) { // fast path if connection already exists 107 return CompletableFuture.completedFuture(connection); 108 } 109 110 if (!req.secure()) { 111 return MinimalFuture.completedFuture(null); 112 } 113 114 if (!opening.contains(key)) { 115 debug.log(Level.DEBUG, "Opening: %s", key); 116 opening.add(key); 117 } else { 118 CompletableFuture<Http2Connection> cf = new MinimalFuture<>(); 119 addToWaiting(key, cf); 120 return cf; 121 } 122 } 123 return Http2Connection 124 .createAsync(req, this) 125 .whenComplete((conn, t) -> { 126 debug.log(Level.DEBUG, 127 "waking up dependents with created connection"); 128 synchronized (opening) { 129 Set<CompletableFuture<Http2Connection>> waiters = waiting.remove(key); 130 debug.log(Level.DEBUG, "Opening completed: %s", key); 131 opening.remove(key); 132 final Throwable cause = Utils.getCompletionCause(t); 133 if (waiters == null) { 134 debug.log(Level.DEBUG, "no dependent to wake up"); 135 return; 136 } else if (cause instanceof Http2Connection.ALPNException) { 137 waiters.forEach((cf1) -> cf1.completeAsync(() -> null, 138 client.theExecutor())); 139 } else if (cause != null) { 140 debug.log(Level.DEBUG, 141 () -> "waking up dependants: failed: " + cause); 142 waiters.forEach((cf1) -> cf1.completeExceptionally(cause)); 143 } else { 144 debug.log(Level.DEBUG, "waking up dependants: succeeded"); 145 waiters.forEach((cf1) -> cf1.completeAsync(() -> conn, 146 client.theExecutor())); 147 } 148 } 149 }); 150 } 151 152 /* 153 * TODO: If there isn't a connection to the same destination, then 154 * store it. If there is already a connection, then close it 155 */ 156 void putConnection(Http2Connection c) { 157 connections.put(c.key(), c); 158 } 159 160 void deleteConnection(Http2Connection c) { 161 connections.remove(c.key()); 162 } 163 164 void stop() { 165 debug.log(Level.DEBUG, "stopping"); 166 connections.values().forEach(this::close); 167 connections.clear(); 168 } 169 170 private void close(Http2Connection h2c) { 171 try { h2c.close(); } catch (Throwable t) {} 172 } 173 174 HttpClientImpl client() { 175 return client; 176 } 177 178 /** Returns the client settings as a base64 (url) encoded string */ 179 String getSettingsString() { 180 SettingsFrame sf = getClientSettings(); 181 byte[] settings = sf.toByteArray(); // without the header 182 Base64.Encoder encoder = Base64.getUrlEncoder() 183 .withoutPadding(); 184 return encoder.encodeToString(settings); 185 } 186 187 private static final int K = 1024; 188 189 SettingsFrame getClientSettings() { 190 SettingsFrame frame = new SettingsFrame(); 191 frame.setParameter(HEADER_TABLE_SIZE, Utils.getIntegerNetProperty( 192 "jdk.httpclient.hpack.maxheadertablesize", 16 * K)); 193 frame.setParameter(ENABLE_PUSH, Utils.getIntegerNetProperty( 194 "jdk.httpclient.enablepush", 1)); 195 frame.setParameter(MAX_CONCURRENT_STREAMS, Utils.getIntegerNetProperty( 196 "jdk.httpclient.maxstreams", 16)); 197 frame.setParameter(INITIAL_WINDOW_SIZE, Utils.getIntegerNetProperty( 198 "jdk.httpclient.windowsize", 64 * K - 1)); 199 frame.setParameter(MAX_FRAME_SIZE, Utils.getIntegerNetProperty( 200 "jdk.httpclient.maxframesize", 16 * K)); 201 return frame; 202 } 203 }