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.io.IOException; 29 import java.net.InetSocketAddress; 30 import java.net.Proxy; 31 import java.net.ProxySelector; 32 import java.net.URI; 33 import java.security.AccessControlContext; 34 import java.security.AccessController; 35 import java.security.PrivilegedAction; 36 import java.time.Duration; 37 import java.util.List; 38 import java.util.Locale; 39 import java.util.Objects; 40 import java.util.Optional; 41 import java.net.http.HttpClient; 42 import java.net.http.HttpHeaders; 43 import java.net.http.HttpRequest; 44 import jdk.internal.net.http.common.HttpHeadersBuilder; 45 import jdk.internal.net.http.common.Utils; 46 import jdk.internal.net.http.websocket.WebSocketRequest; 47 48 import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS; 49 50 public class HttpRequestImpl extends HttpRequest implements WebSocketRequest { 51 52 private final HttpHeaders userHeaders; 53 private final HttpHeadersBuilder systemHeadersBuilder; 54 private final URI uri; 55 private volatile Proxy proxy; // ensure safe publishing 56 private final InetSocketAddress authority; // only used when URI not specified 57 private final String method; 58 final BodyPublisher requestPublisher; 59 final boolean secure; 60 final boolean expectContinue; 61 private volatile boolean isWebSocket; 62 private volatile AccessControlContext acc; 63 private final Duration timeout; // may be null 64 private final Optional<HttpClient.Version> version; 65 66 private static String userAgent() { 67 PrivilegedAction<String> pa = () -> System.getProperty("java.version"); 68 String version = AccessController.doPrivileged(pa); 69 return "Java-http-client/" + version; 70 } 71 72 /** The value of the User-Agent header for all requests sent by the client. */ 73 public static final String USER_AGENT = userAgent(); 74 75 /** 76 * Creates an HttpRequestImpl from the given builder. 77 */ 78 public HttpRequestImpl(HttpRequestBuilderImpl builder) { 79 String method = builder.method(); 80 this.method = method == null ? "GET" : method; 81 this.userHeaders = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS); 82 this.systemHeadersBuilder = new HttpHeadersBuilder(); 83 this.uri = builder.uri(); 84 assert uri != null; 85 this.proxy = null; 86 this.expectContinue = builder.expectContinue(); 87 this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https"); 88 this.requestPublisher = builder.bodyPublisher(); // may be null 89 this.timeout = builder.timeout(); 90 this.version = builder.version(); 91 this.authority = null; 92 } 93 94 /** 95 * Creates an HttpRequestImpl from the given request. 96 */ 97 public HttpRequestImpl(HttpRequest request, ProxySelector ps) { 98 String method = request.method(); 99 if (method != null && !Utils.isValidName(method)) 100 throw new IllegalArgumentException("illegal method \"" 101 + method.replace("\n","\\n") 102 .replace("\r", "\\r") 103 .replace("\t", "\\t") 104 + "\""); 105 URI requestURI = Objects.requireNonNull(request.uri(), 106 "uri must be non null"); 107 Duration timeout = request.timeout().orElse(null); 108 this.method = method == null ? "GET" : method; 109 this.userHeaders = HttpHeaders.of(request.headers().map(), Utils.VALIDATE_USER_HEADER); 110 if (request instanceof HttpRequestImpl) { 111 // all cases exception WebSocket should have a new system headers 112 this.isWebSocket = ((HttpRequestImpl) request).isWebSocket; 113 if (isWebSocket) { 114 this.systemHeadersBuilder = ((HttpRequestImpl)request).systemHeadersBuilder; 115 } else { 116 this.systemHeadersBuilder = new HttpHeadersBuilder(); 117 } 118 } else { 119 HttpRequestBuilderImpl.checkURI(requestURI); 120 checkTimeout(timeout); 121 this.systemHeadersBuilder = new HttpHeadersBuilder(); 122 } 123 if (!userHeaders.firstValue("User-Agent").isPresent()) { 124 this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT); 125 } 126 this.uri = requestURI; 127 if (isWebSocket) { 128 // WebSocket determines and sets the proxy itself 129 this.proxy = ((HttpRequestImpl) request).proxy; 130 } else { 131 if (ps != null) 132 this.proxy = retrieveProxy(ps, uri); 133 else 134 this.proxy = null; 135 } 136 this.expectContinue = request.expectContinue(); 137 this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https"); 138 this.requestPublisher = request.bodyPublisher().orElse(null); 139 this.timeout = timeout; 140 this.version = request.version(); 141 this.authority = null; 142 } 143 144 private static void checkTimeout(Duration duration) { 145 if (duration != null) { 146 if (duration.isNegative() || Duration.ZERO.equals(duration)) 147 throw new IllegalArgumentException("Invalid duration: " + duration); 148 } 149 } 150 151 /** Returns a new instance suitable for redirection. */ 152 public static HttpRequestImpl newInstanceForRedirection(URI uri, 153 String method, 154 HttpRequestImpl other) { 155 return new HttpRequestImpl(uri, method, other); 156 } 157 158 /** Returns a new instance suitable for authentication. */ 159 public static HttpRequestImpl newInstanceForAuthentication(HttpRequestImpl other) { 160 return new HttpRequestImpl(other.uri(), other.method(), other); 161 } 162 163 /** 164 * Creates a HttpRequestImpl using fields of an existing request impl. 165 * The newly created HttpRequestImpl does not copy the system headers. 166 */ 167 private HttpRequestImpl(URI uri, 168 String method, 169 HttpRequestImpl other) { 170 assert method == null || Utils.isValidName(method); 171 this.method = method == null? "GET" : method; 172 this.userHeaders = other.userHeaders; 173 this.isWebSocket = other.isWebSocket; 174 this.systemHeadersBuilder = new HttpHeadersBuilder(); 175 if (!userHeaders.firstValue("User-Agent").isPresent()) { 176 this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT); 177 } 178 this.uri = uri; 179 this.proxy = other.proxy; 180 this.expectContinue = other.expectContinue; 181 this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https"); 182 this.requestPublisher = other.requestPublisher; // may be null 183 this.acc = other.acc; 184 this.timeout = other.timeout; 185 this.version = other.version(); 186 this.authority = null; 187 } 188 189 /* used for creating CONNECT requests */ 190 HttpRequestImpl(String method, InetSocketAddress authority, HttpHeaders headers) { 191 // TODO: isWebSocket flag is not specified, but the assumption is that 192 // such a request will never be made on a connection that will be returned 193 // to the connection pool (we might need to revisit this constructor later) 194 assert "CONNECT".equalsIgnoreCase(method); 195 this.method = method; 196 this.systemHeadersBuilder = new HttpHeadersBuilder(); 197 this.userHeaders = headers; 198 this.uri = URI.create("socket://" + authority.getHostString() + ":" 199 + Integer.toString(authority.getPort()) + "/"); 200 this.proxy = null; 201 this.requestPublisher = null; 202 this.authority = authority; 203 this.secure = false; 204 this.expectContinue = false; 205 this.timeout = null; 206 // The CONNECT request sent for tunneling is only used in two cases: 207 // 1. websocket, which only supports HTTP/1.1 208 // 2. SSL tunneling through a HTTP/1.1 proxy 209 // In either case we do not want to upgrade the connection to the proxy. 210 // What we want to possibly upgrade is the tunneled connection to the 211 // target server (so not the CONNECT request itself) 212 this.version = Optional.of(HttpClient.Version.HTTP_1_1); 213 } 214 215 final boolean isConnect() { 216 return "CONNECT".equalsIgnoreCase(method); 217 } 218 219 /** 220 * Creates a HttpRequestImpl from the given set of Headers and the associated 221 * "parent" request. Fields not taken from the headers are taken from the 222 * parent. 223 */ 224 static HttpRequestImpl createPushRequest(HttpRequestImpl parent, 225 HttpHeaders headers) 226 throws IOException 227 { 228 return new HttpRequestImpl(parent, headers); 229 } 230 231 // only used for push requests 232 private HttpRequestImpl(HttpRequestImpl parent, HttpHeaders headers) 233 throws IOException 234 { 235 this.method = headers.firstValue(":method") 236 .orElseThrow(() -> new IOException("No method in Push Promise")); 237 String path = headers.firstValue(":path") 238 .orElseThrow(() -> new IOException("No path in Push Promise")); 239 String scheme = headers.firstValue(":scheme") 240 .orElseThrow(() -> new IOException("No scheme in Push Promise")); 241 String authority = headers.firstValue(":authority") 242 .orElseThrow(() -> new IOException("No authority in Push Promise")); 243 StringBuilder sb = new StringBuilder(); 244 sb.append(scheme).append("://").append(authority).append(path); 245 this.uri = URI.create(sb.toString()); 246 this.proxy = null; 247 this.userHeaders = HttpHeaders.of(headers.map(), ALLOWED_HEADERS); 248 this.systemHeadersBuilder = parent.systemHeadersBuilder; 249 this.expectContinue = parent.expectContinue; 250 this.secure = parent.secure; 251 this.requestPublisher = parent.requestPublisher; 252 this.acc = parent.acc; 253 this.timeout = parent.timeout; 254 this.version = parent.version; 255 this.authority = null; 256 } 257 258 @Override 259 public String toString() { 260 return (uri == null ? "" : uri.toString()) + " " + method; 261 } 262 263 @Override 264 public HttpHeaders headers() { 265 return userHeaders; 266 } 267 268 InetSocketAddress authority() { return authority; } 269 270 void setH2Upgrade(Http2ClientImpl h2client) { 271 systemHeadersBuilder.setHeader("Connection", "Upgrade, HTTP2-Settings"); 272 systemHeadersBuilder.setHeader("Upgrade", "h2c"); 273 systemHeadersBuilder.setHeader("HTTP2-Settings", h2client.getSettingsString()); 274 } 275 276 @Override 277 public boolean expectContinue() { return expectContinue; } 278 279 /** Retrieves the proxy, from the given ProxySelector, if there is one. */ 280 private static Proxy retrieveProxy(ProxySelector ps, URI uri) { 281 Proxy proxy = null; 282 List<Proxy> pl = ps.select(uri); 283 if (!pl.isEmpty()) { 284 Proxy p = pl.get(0); 285 if (p.type() == Proxy.Type.HTTP) 286 proxy = p; 287 } 288 return proxy; 289 } 290 291 InetSocketAddress proxy() { 292 if (proxy == null || proxy.type() != Proxy.Type.HTTP 293 || method.equalsIgnoreCase("CONNECT")) { 294 return null; 295 } 296 return (InetSocketAddress)proxy.address(); 297 } 298 299 boolean secure() { return secure; } 300 301 @Override 302 public void setProxy(Proxy proxy) { 303 assert isWebSocket; 304 this.proxy = proxy; 305 } 306 307 @Override 308 public void isWebSocket(boolean is) { 309 isWebSocket = is; 310 } 311 312 boolean isWebSocket() { 313 return isWebSocket; 314 } 315 316 @Override 317 public Optional<BodyPublisher> bodyPublisher() { 318 return requestPublisher == null ? Optional.empty() 319 : Optional.of(requestPublisher); 320 } 321 322 /** 323 * Returns the request method for this request. If not set explicitly, 324 * the default method for any request is "GET". 325 */ 326 @Override 327 public String method() { return method; } 328 329 @Override 330 public URI uri() { return uri; } 331 332 @Override 333 public Optional<Duration> timeout() { 334 return timeout == null ? Optional.empty() : Optional.of(timeout); 335 } 336 337 HttpHeaders getUserHeaders() { return userHeaders; } 338 339 HttpHeadersBuilder getSystemHeadersBuilder() { return systemHeadersBuilder; } 340 341 @Override 342 public Optional<HttpClient.Version> version() { return version; } 343 344 void addSystemHeader(String name, String value) { 345 systemHeadersBuilder.addHeader(name, value); 346 } 347 348 @Override 349 public void setSystemHeader(String name, String value) { 350 systemHeadersBuilder.setHeader(name, value); 351 } 352 353 InetSocketAddress getAddress() { 354 URI uri = uri(); 355 if (uri == null) { 356 return authority(); 357 } 358 int p = uri.getPort(); 359 if (p == -1) { 360 if (uri.getScheme().equalsIgnoreCase("https")) { 361 p = 443; 362 } else { 363 p = 80; 364 } 365 } 366 final String host = uri.getHost(); 367 final int port = p; 368 if (proxy() == null) { 369 PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port); 370 return AccessController.doPrivileged(pa); 371 } else { 372 return InetSocketAddress.createUnresolved(host, port); 373 } 374 } 375 }