Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/PlainTunnelingConnection.java
41171 views
/*1* Copyright (c) 2015, 2020, 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.http.HttpTimeoutException;30import java.nio.ByteBuffer;31import java.nio.channels.SocketChannel;32import java.time.Duration;33import java.util.concurrent.CompletableFuture;34import java.util.concurrent.CompletionException;35import java.util.function.Function;36import java.net.http.HttpHeaders;37import jdk.internal.net.http.common.FlowTube;38import jdk.internal.net.http.common.MinimalFuture;39import static java.net.http.HttpResponse.BodyHandlers.discarding;40import static jdk.internal.net.http.common.Utils.ProxyHeaders;4142/**43* A plain text socket tunnel through a proxy. Uses "CONNECT" but does not44* encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.45* Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.46*/47final class PlainTunnelingConnection extends HttpConnection {4849final PlainHttpConnection delegate;50final ProxyHeaders proxyHeaders;51final InetSocketAddress proxyAddr;52private volatile boolean connected;5354protected PlainTunnelingConnection(InetSocketAddress addr,55InetSocketAddress proxy,56HttpClientImpl client,57ProxyHeaders proxyHeaders) {58super(addr, client);59this.proxyAddr = proxy;60this.proxyHeaders = proxyHeaders;61delegate = new PlainHttpConnection(proxy, client);62}6364@Override65public CompletableFuture<Void> connectAsync(Exchange<?> exchange) {66if (debug.on()) debug.log("Connecting plain connection");67return delegate.connectAsync(exchange)68.thenCompose(unused -> delegate.finishConnect())69.thenCompose((Void v) -> {70if (debug.on()) debug.log("sending HTTP/1.1 CONNECT");71HttpClientImpl client = client();72assert client != null;73HttpRequestImpl req = new HttpRequestImpl("CONNECT", address, proxyHeaders);74MultiExchange<Void> mulEx = new MultiExchange<>(null, req,75client, discarding(), null, null);76Exchange<Void> connectExchange = mulEx.getExchange();7778return connectExchange79.responseAsyncImpl(delegate)80.thenCompose((Response resp) -> {81CompletableFuture<Void> cf = new MinimalFuture<>();82if (debug.on()) debug.log("got response: %d", resp.statusCode());83if (resp.statusCode() == 407) {84return connectExchange.ignoreBody().handle((r,t) -> {85// close delegate after reading body: we won't86// be reusing that connection anyway.87delegate.close();88ProxyAuthenticationRequired authenticationRequired =89new ProxyAuthenticationRequired(resp);90cf.completeExceptionally(authenticationRequired);91return cf;92}).thenCompose(Function.identity());93} else if (resp.statusCode() != 200) {94delegate.close();95cf.completeExceptionally(new IOException(96"Tunnel failed, got: "+ resp.statusCode()));97} else {98// get the initial/remaining bytes99ByteBuffer b = ((Http1Exchange<?>)connectExchange.exchImpl).drainLeftOverBytes();100int remaining = b.remaining();101assert remaining == 0: "Unexpected remaining: " + remaining;102cf.complete(null);103}104return cf;105})106.handle((result, ex) -> {107if (ex == null) {108return MinimalFuture.completedFuture(result);109} else {110if (debug.on())111debug.log("tunnel failed with \"%s\"", ex.toString());112Throwable t = ex;113if (t instanceof CompletionException)114t = t.getCause();115if (t instanceof HttpTimeoutException) {116String msg = "proxy tunneling CONNECT request timed out";117t = new HttpTimeoutException(msg);118t.initCause(ex);119}120return MinimalFuture.<Void>failedFuture(t);121}122})123.thenCompose(Function.identity());124});125}126127public CompletableFuture<Void> finishConnect() {128connected = true;129return MinimalFuture.completedFuture(null);130}131132@Override133boolean isTunnel() { return true; }134135@Override136HttpPublisher publisher() { return delegate.publisher(); }137138@Override139boolean connected() {140return connected;141}142143@Override144SocketChannel channel() {145return delegate.channel();146}147148@Override149FlowTube getConnectionFlow() {150return delegate.getConnectionFlow();151}152153@Override154ConnectionPool.CacheKey cacheKey() {155return new ConnectionPool.CacheKey(null, proxyAddr);156}157158@Override159public void close() {160delegate.close();161connected = false;162}163164@Override165boolean isSecure() {166return false;167}168169@Override170boolean isProxied() {171return true;172}173174@Override175InetSocketAddress proxy() {176return proxyAddr;177}178}179180181