1 /* 2 * Copyright (c) 2015, 2016, 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.io.IOException; 29 import java.net.InetSocketAddress; 30 import java.net.URI; 31 import java.util.Base64; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.Map; 36 import java.util.Set; 37 import java.util.concurrent.ConcurrentHashMap; 38 39 import jdk.incubator.http.internal.common.Utils; 40 import jdk.incubator.http.internal.frame.SettingsFrame; 41 import static jdk.incubator.http.internal.frame.SettingsFrame.INITIAL_WINDOW_SIZE; 42 import static jdk.incubator.http.internal.frame.SettingsFrame.ENABLE_PUSH; 43 import static jdk.incubator.http.internal.frame.SettingsFrame.HEADER_TABLE_SIZE; 44 import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_CONCURRENT_STREAMS; 45 import static jdk.incubator.http.internal.frame.SettingsFrame.MAX_FRAME_SIZE; 46 47 /** 48 * Http2 specific aspects of HttpClientImpl 49 */ 50 class Http2ClientImpl { 51 52 private final HttpClientImpl client; 53 54 Http2ClientImpl(HttpClientImpl client) { 55 this.client = client; 56 } 57 58 /* Map key is "scheme:host:port" */ 59 private final Map<String,Http2Connection> connections = new ConcurrentHashMap<>(); 60 61 private final Set<String> opening = Collections.synchronizedSet(new HashSet<>()); 62 63 boolean haveConnectionFor(URI uri, InetSocketAddress proxy) { 64 return connections.containsKey(Http2Connection.keyFor(uri,proxy)); 65 } 66 67 /** 68 * If a https request then blocks and waits until a connection is opened. 69 * Returns null if the request is 'http' as a different (upgrade) 70 * mechanism is used. 71 * 72 * Only one connection per destination is created. Blocks when opening 73 * connection, or when waiting for connection to be opened. 74 * First thread opens the connection and notifies the others when done. 75 * 76 * If the request is secure (https) then we open the connection here. 77 * If not, then the more complicated upgrade from 1.1 to 2 happens (not here) 78 * In latter case, when the Http2Connection is connected, putConnection() must 79 * be called to store it. 80 */ 81 Http2Connection getConnectionFor(HttpRequestImpl req) 82 throws IOException, InterruptedException { 83 URI uri = req.uri(); 84 InetSocketAddress proxy = req.proxy(client); 85 String key = Http2Connection.keyFor(uri, proxy); 86 Http2Connection connection = connections.get(key); 87 if (connection != null) { // fast path if connection already exists 88 return connection; 89 } 90 synchronized (opening) { 91 while ((connection = connections.get(key)) == null) { 92 if (!req.secure()) { 93 return null; 94 } 95 if (!opening.contains(key)) { 96 opening.add(key); 97 break; 98 } else { 99 opening.wait(); 100 } 101 } 102 } 103 if (connection != null) { 104 return connection; 105 } 106 // we are opening the connection here blocking until it is done. 107 try { 108 connection = new Http2Connection(req, this); 109 } catch (Throwable t) { 110 synchronized (opening) { 111 opening.remove(key); 112 opening.notifyAll(); 113 } 114 throw t; 115 } 116 synchronized (opening) { 117 connections.put(key, connection); 118 opening.remove(key); 119 opening.notifyAll(); 120 } 121 return connection; 122 } 123 124 125 /* 126 * TODO: If there isn't a connection to the same destination, then 127 * store it. If there is already a connection, then close it 128 */ 129 void putConnection(Http2Connection c) { 130 connections.put(c.key(), c); 131 } 132 133 void deleteConnection(Http2Connection c) { 134 connections.remove(c.key()); 135 } 136 137 HttpClientImpl client() { 138 return client; 139 } 140 141 /** Returns the client settings as a base64 (url) encoded string */ 142 String getSettingsString() { 143 SettingsFrame sf = getClientSettings(); 144 byte[] settings = sf.toByteArray(); // without the header 145 Base64.Encoder encoder = Base64.getUrlEncoder() 146 .withoutPadding(); 147 return encoder.encodeToString(settings); 148 } 149 150 private static final int K = 1024; 151 152 SettingsFrame getClientSettings() { 153 SettingsFrame frame = new SettingsFrame(); 154 frame.setParameter(HEADER_TABLE_SIZE, Utils.getIntegerNetProperty( 155 "jdk.httpclient.hpack.maxheadertablesize", 16 * K)); 156 frame.setParameter(ENABLE_PUSH, Utils.getIntegerNetProperty( 157 "jdk.httpclient.enablepush", 1)); 158 frame.setParameter(MAX_CONCURRENT_STREAMS, Utils.getIntegerNetProperty( 159 "jdk.httpclient.maxstreams", 16)); 160 frame.setParameter(INITIAL_WINDOW_SIZE, Utils.getIntegerNetProperty( 161 "jdk.httpclient.windowsize", 64 * K - 1)); 162 frame.setParameter(MAX_FRAME_SIZE, Utils.getIntegerNetProperty( 163 "jdk.httpclient.maxframesize", 16 * K)); 164 return frame; 165 } 166 }