Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java
41171 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.internal.net.http;2627import java.io.IOException;28import java.net.InetSocketAddress;29import java.net.Proxy;30import java.net.ProxySelector;31import java.net.URI;32import java.security.AccessControlContext;33import java.security.AccessController;34import java.security.PrivilegedAction;35import java.time.Duration;36import java.util.List;37import java.util.Locale;38import java.util.Objects;39import java.util.Optional;40import java.net.http.HttpClient;41import java.net.http.HttpHeaders;42import java.net.http.HttpRequest;4344import jdk.internal.net.http.common.HttpHeadersBuilder;45import jdk.internal.net.http.common.Utils;46import jdk.internal.net.http.websocket.OpeningHandshake;47import jdk.internal.net.http.websocket.WebSocketRequest;4849import static jdk.internal.net.http.common.Utils.ALLOWED_HEADERS;50import static jdk.internal.net.http.common.Utils.ProxyHeaders;5152public class HttpRequestImpl extends HttpRequest implements WebSocketRequest {5354private final HttpHeaders userHeaders;55private final HttpHeadersBuilder systemHeadersBuilder;56private final URI uri;57private volatile Proxy proxy; // ensure safe publishing58private final InetSocketAddress authority; // only used when URI not specified59private final String method;60final BodyPublisher requestPublisher;61final boolean secure;62final boolean expectContinue;63private volatile boolean isWebSocket;64@SuppressWarnings("removal")65private volatile AccessControlContext acc;66private final Duration timeout; // may be null67private final Optional<HttpClient.Version> version;6869private static String userAgent() {70PrivilegedAction<String> pa = () -> System.getProperty("java.version");71@SuppressWarnings("removal")72String version = AccessController.doPrivileged(pa);73return "Java-http-client/" + version;74}7576/** The value of the User-Agent header for all requests sent by the client. */77public static final String USER_AGENT = userAgent();7879/**80* Creates an HttpRequestImpl from the given builder.81*/82public HttpRequestImpl(HttpRequestBuilderImpl builder) {83String method = builder.method();84this.method = method == null ? "GET" : method;85this.userHeaders = HttpHeaders.of(builder.headersBuilder().map(), ALLOWED_HEADERS);86this.systemHeadersBuilder = new HttpHeadersBuilder();87this.uri = builder.uri();88assert uri != null;89this.proxy = null;90this.expectContinue = builder.expectContinue();91this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");92this.requestPublisher = builder.bodyPublisher(); // may be null93this.timeout = builder.timeout();94this.version = builder.version();95this.authority = null;96}9798/**99* Creates an HttpRequestImpl from the given request.100*/101public HttpRequestImpl(HttpRequest request, ProxySelector ps) {102String method = request.method();103if (method != null && !Utils.isValidName(method))104throw new IllegalArgumentException("illegal method \""105+ method.replace("\n","\\n")106.replace("\r", "\\r")107.replace("\t", "\\t")108+ "\"");109URI requestURI = Objects.requireNonNull(request.uri(),110"uri must be non null");111Duration timeout = request.timeout().orElse(null);112this.method = method == null ? "GET" : method;113this.userHeaders = HttpHeaders.of(request.headers().map(), Utils.VALIDATE_USER_HEADER);114if (request instanceof HttpRequestImpl) {115// all cases exception WebSocket should have a new system headers116this.isWebSocket = ((HttpRequestImpl) request).isWebSocket;117if (isWebSocket) {118this.systemHeadersBuilder = ((HttpRequestImpl)request).systemHeadersBuilder;119} else {120this.systemHeadersBuilder = new HttpHeadersBuilder();121}122} else {123HttpRequestBuilderImpl.checkURI(requestURI);124checkTimeout(timeout);125this.systemHeadersBuilder = new HttpHeadersBuilder();126}127if (!userHeaders.firstValue("User-Agent").isPresent()) {128this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);129}130this.uri = requestURI;131if (isWebSocket) {132// WebSocket determines and sets the proxy itself133this.proxy = ((HttpRequestImpl) request).proxy;134} else {135if (ps != null)136this.proxy = retrieveProxy(ps, uri);137else138this.proxy = null;139}140this.expectContinue = request.expectContinue();141this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");142this.requestPublisher = request.bodyPublisher().orElse(null);143this.timeout = timeout;144this.version = request.version();145this.authority = null;146}147148private static void checkTimeout(Duration duration) {149if (duration != null) {150if (duration.isNegative() || Duration.ZERO.equals(duration))151throw new IllegalArgumentException("Invalid duration: " + duration);152}153}154155/** Returns a new instance suitable for redirection. */156public static HttpRequestImpl newInstanceForRedirection(URI uri,157String method,158HttpRequestImpl other,159boolean mayHaveBody) {160return new HttpRequestImpl(uri, method, other, mayHaveBody);161}162163/** Returns a new instance suitable for authentication. */164public static HttpRequestImpl newInstanceForAuthentication(HttpRequestImpl other) {165HttpRequestImpl request = new HttpRequestImpl(other.uri(), other.method(), other, true);166if (request.isWebSocket()) {167Utils.setWebSocketUpgradeHeaders(request);168}169return request;170}171172/**173* Creates a HttpRequestImpl using fields of an existing request impl.174* The newly created HttpRequestImpl does not copy the system headers.175*/176private HttpRequestImpl(URI uri,177String method,178HttpRequestImpl other,179boolean mayHaveBody) {180assert method == null || Utils.isValidName(method);181this.method = method == null? "GET" : method;182this.userHeaders = other.userHeaders;183this.isWebSocket = other.isWebSocket;184this.systemHeadersBuilder = new HttpHeadersBuilder();185if (!userHeaders.firstValue("User-Agent").isPresent()) {186this.systemHeadersBuilder.setHeader("User-Agent", USER_AGENT);187}188this.uri = uri;189this.proxy = other.proxy;190this.expectContinue = other.expectContinue;191this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");192this.requestPublisher = mayHaveBody ? publisher(other) : null; // may be null193this.acc = other.acc;194this.timeout = other.timeout;195this.version = other.version();196this.authority = null;197}198199private BodyPublisher publisher(HttpRequestImpl other) {200BodyPublisher res = other.requestPublisher;201if (!Objects.equals(method, other.method)) {202res = null;203}204return res;205}206207/* used for creating CONNECT requests */208HttpRequestImpl(String method, InetSocketAddress authority, ProxyHeaders headers) {209// TODO: isWebSocket flag is not specified, but the assumption is that210// such a request will never be made on a connection that will be returned211// to the connection pool (we might need to revisit this constructor later)212assert "CONNECT".equalsIgnoreCase(method);213this.method = method;214this.systemHeadersBuilder = new HttpHeadersBuilder();215this.systemHeadersBuilder.map().putAll(headers.systemHeaders().map());216this.userHeaders = headers.userHeaders();217this.uri = URI.create("socket://" + authority.getHostString() + ":"218+ Integer.toString(authority.getPort()) + "/");219this.proxy = null;220this.requestPublisher = null;221this.authority = authority;222this.secure = false;223this.expectContinue = false;224this.timeout = null;225// The CONNECT request sent for tunneling is only used in two cases:226// 1. websocket, which only supports HTTP/1.1227// 2. SSL tunneling through a HTTP/1.1 proxy228// In either case we do not want to upgrade the connection to the proxy.229// What we want to possibly upgrade is the tunneled connection to the230// target server (so not the CONNECT request itself)231this.version = Optional.of(HttpClient.Version.HTTP_1_1);232}233234final boolean isConnect() {235return "CONNECT".equalsIgnoreCase(method);236}237238/**239* Creates a HttpRequestImpl from the given set of Headers and the associated240* "parent" request. Fields not taken from the headers are taken from the241* parent.242*/243static HttpRequestImpl createPushRequest(HttpRequestImpl parent,244HttpHeaders headers)245throws IOException246{247return new HttpRequestImpl(parent, headers);248}249250// only used for push requests251private HttpRequestImpl(HttpRequestImpl parent, HttpHeaders headers)252throws IOException253{254this.method = headers.firstValue(":method")255.orElseThrow(() -> new IOException("No method in Push Promise"));256String path = headers.firstValue(":path")257.orElseThrow(() -> new IOException("No path in Push Promise"));258String scheme = headers.firstValue(":scheme")259.orElseThrow(() -> new IOException("No scheme in Push Promise"));260String authority = headers.firstValue(":authority")261.orElseThrow(() -> new IOException("No authority in Push Promise"));262StringBuilder sb = new StringBuilder();263sb.append(scheme).append("://").append(authority).append(path);264this.uri = URI.create(sb.toString());265this.proxy = null;266this.userHeaders = HttpHeaders.of(headers.map(), ALLOWED_HEADERS);267this.systemHeadersBuilder = parent.systemHeadersBuilder;268this.expectContinue = parent.expectContinue;269this.secure = parent.secure;270this.requestPublisher = parent.requestPublisher;271this.acc = parent.acc;272this.timeout = parent.timeout;273this.version = parent.version;274this.authority = null;275}276277@Override278public String toString() {279return (uri == null ? "" : uri.toString()) + " " + method;280}281282@Override283public HttpHeaders headers() {284return userHeaders;285}286287InetSocketAddress authority() { return authority; }288289void setH2Upgrade(Http2ClientImpl h2client) {290systemHeadersBuilder.setHeader("Connection", "Upgrade, HTTP2-Settings");291systemHeadersBuilder.setHeader("Upgrade", "h2c");292systemHeadersBuilder.setHeader("HTTP2-Settings", h2client.getSettingsString());293}294295@Override296public boolean expectContinue() { return expectContinue; }297298/** Retrieves the proxy, from the given ProxySelector, if there is one. */299private static Proxy retrieveProxy(ProxySelector ps, URI uri) {300Proxy proxy = null;301List<Proxy> pl = ps.select(uri);302if (!pl.isEmpty()) {303Proxy p = pl.get(0);304if (p.type() == Proxy.Type.HTTP)305proxy = p;306}307return proxy;308}309310InetSocketAddress proxy() {311if (proxy == null || proxy.type() != Proxy.Type.HTTP312|| method.equalsIgnoreCase("CONNECT")) {313return null;314}315return (InetSocketAddress)proxy.address();316}317318boolean secure() { return secure; }319320@Override321public void setProxy(Proxy proxy) {322assert isWebSocket;323this.proxy = proxy;324}325326@Override327public void isWebSocket(boolean is) {328isWebSocket = is;329}330331boolean isWebSocket() {332return isWebSocket;333}334335@Override336public Optional<BodyPublisher> bodyPublisher() {337return requestPublisher == null ? Optional.empty()338: Optional.of(requestPublisher);339}340341/**342* Returns the request method for this request. If not set explicitly,343* the default method for any request is "GET".344*/345@Override346public String method() { return method; }347348@Override349public URI uri() { return uri; }350351@Override352public Optional<Duration> timeout() {353return timeout == null ? Optional.empty() : Optional.of(timeout);354}355356HttpHeaders getUserHeaders() { return userHeaders; }357358HttpHeadersBuilder getSystemHeadersBuilder() { return systemHeadersBuilder; }359360@Override361public Optional<HttpClient.Version> version() { return version; }362363void addSystemHeader(String name, String value) {364systemHeadersBuilder.addHeader(name, value);365}366367@Override368public void setSystemHeader(String name, String value) {369systemHeadersBuilder.setHeader(name, value);370}371372@SuppressWarnings("removal")373InetSocketAddress getAddress() {374URI uri = uri();375if (uri == null) {376return authority();377}378int p = uri.getPort();379if (p == -1) {380if (uri.getScheme().equalsIgnoreCase("https")) {381p = 443;382} else {383p = 80;384}385}386final String host = uri.getHost();387final int port = p;388if (proxy() == null) {389PrivilegedAction<InetSocketAddress> pa = () -> new InetSocketAddress(host, port);390return AccessController.doPrivileged(pa);391} else {392return InetSocketAddress.createUnresolved(host, port);393}394}395}396397398