8181422: ClassCastException in HTTP Client
Added missing AsyncSSLTunnelConnection Reviewed-by: michaelm
This commit is contained in:
parent
55fe55c4ff
commit
997d77996c
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.incubator.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import jdk.incubator.http.internal.common.ExceptionallyCloseable;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronous version of SSLConnection.
|
||||||
|
*
|
||||||
|
* There are two concrete implementations of this class: AsyncSSLConnection
|
||||||
|
* and AsyncSSLTunnelConnection.
|
||||||
|
* This abstraction is useful when downgrading from HTTP/2 to HTTP/1.1 over
|
||||||
|
* an SSL connection. See ExchangeImpl::get in the case where an ALPNException
|
||||||
|
* is thrown.
|
||||||
|
*
|
||||||
|
* Note: An AsyncSSLConnection wraps a PlainHttpConnection, while an
|
||||||
|
* AsyncSSLTunnelConnection wraps a PlainTunnelingConnection.
|
||||||
|
* If both these wrapped classes where made to inherit from a
|
||||||
|
* common abstraction then it might be possible to merge
|
||||||
|
* AsyncSSLConnection and AsyncSSLTunnelConnection back into
|
||||||
|
* a single class - and simply use different factory methods to
|
||||||
|
* create different wrappees, but this is left up for further cleanup.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
abstract class AbstractAsyncSSLConnection extends HttpConnection
|
||||||
|
implements AsyncConnection, ExceptionallyCloseable {
|
||||||
|
|
||||||
|
|
||||||
|
AbstractAsyncSSLConnection(InetSocketAddress addr, HttpClientImpl client) {
|
||||||
|
super(addr, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract SSLEngine getEngine();
|
||||||
|
abstract AsyncSSLDelegate sslDelegate();
|
||||||
|
abstract HttpConnection plainConnection();
|
||||||
|
abstract HttpConnection downgrade();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final boolean isSecure() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocking read functions not used here
|
||||||
|
@Override
|
||||||
|
protected final ByteBuffer readImpl() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// whenReceivedResponse only used in HTTP/1.1 (Http1Exchange)
|
||||||
|
// AbstractAsyncSSLConnection is only used with HTTP/2
|
||||||
|
@Override
|
||||||
|
final CompletableFuture<Void> whenReceivingResponse() {
|
||||||
|
throw new UnsupportedOperationException("Not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -35,14 +35,12 @@ import java.util.function.Supplier;
|
|||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
import jdk.incubator.http.internal.common.ByteBufferReference;
|
import jdk.incubator.http.internal.common.ByteBufferReference;
|
||||||
import jdk.incubator.http.internal.common.ExceptionallyCloseable;
|
|
||||||
import jdk.incubator.http.internal.common.Utils;
|
import jdk.incubator.http.internal.common.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous version of SSLConnection.
|
* Asynchronous version of SSLConnection.
|
||||||
*/
|
*/
|
||||||
class AsyncSSLConnection extends HttpConnection
|
class AsyncSSLConnection extends AbstractAsyncSSLConnection {
|
||||||
implements AsyncConnection, ExceptionallyCloseable {
|
|
||||||
|
|
||||||
final AsyncSSLDelegate sslDelegate;
|
final AsyncSSLDelegate sslDelegate;
|
||||||
final PlainHttpConnection plainConnection;
|
final PlainHttpConnection plainConnection;
|
||||||
@ -61,15 +59,14 @@ class AsyncSSLConnection extends HttpConnection
|
|||||||
plainConnection.configureMode(mode);
|
plainConnection.configureMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Void> configureModeAsync(Void ignore) {
|
@Override
|
||||||
CompletableFuture<Void> cf = new CompletableFuture<>();
|
PlainHttpConnection plainConnection() {
|
||||||
try {
|
return plainConnection;
|
||||||
configureMode(Mode.ASYNC);
|
}
|
||||||
cf.complete(null);
|
|
||||||
} catch (Throwable t) {
|
@Override
|
||||||
cf.completeExceptionally(t);
|
AsyncSSLDelegate sslDelegate() {
|
||||||
}
|
return sslDelegate;
|
||||||
return cf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -91,11 +88,6 @@ class AsyncSSLConnection extends HttpConnection
|
|||||||
return plainConnection.connected() && sslDelegate.connected();
|
return plainConnection.connected() && sslDelegate.connected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean isSecure() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isProxied() {
|
boolean isProxied() {
|
||||||
return false;
|
return false;
|
||||||
@ -172,6 +164,7 @@ class AsyncSSLConnection extends HttpConnection
|
|||||||
plainConnection.channel().shutdownOutput();
|
plainConnection.channel().shutdownOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
SSLEngine getEngine() {
|
SSLEngine getEngine() {
|
||||||
return sslDelegate.getEngine();
|
return sslDelegate.getEngine();
|
||||||
}
|
}
|
||||||
@ -184,18 +177,6 @@ class AsyncSSLConnection extends HttpConnection
|
|||||||
plainConnection.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
|
plainConnection.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking read functions not used here
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ByteBuffer readImpl() throws IOException {
|
|
||||||
throw new UnsupportedOperationException("Not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
CompletableFuture<Void> whenReceivingResponse() {
|
|
||||||
throw new UnsupportedOperationException("Not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startReading() {
|
public void startReading() {
|
||||||
plainConnection.startReading();
|
plainConnection.startReading();
|
||||||
@ -206,4 +187,9 @@ class AsyncSSLConnection extends HttpConnection
|
|||||||
public void stopAsyncReading() {
|
public void stopAsyncReading() {
|
||||||
plainConnection.stopAsyncReading();
|
plainConnection.stopAsyncReading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SSLConnection downgrade() {
|
||||||
|
return new SSLConnection(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.incubator.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import jdk.incubator.http.internal.common.ByteBufferReference;
|
||||||
|
import jdk.incubator.http.internal.common.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An SSL tunnel built on a Plain (CONNECT) TCP tunnel.
|
||||||
|
*/
|
||||||
|
class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
|
||||||
|
|
||||||
|
final PlainTunnelingConnection plainConnection;
|
||||||
|
final AsyncSSLDelegate sslDelegate;
|
||||||
|
final String serverName;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void connect() throws IOException, InterruptedException {
|
||||||
|
plainConnection.connect();
|
||||||
|
configureMode(Mode.ASYNC);
|
||||||
|
startReading();
|
||||||
|
sslDelegate.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean connected() {
|
||||||
|
return plainConnection.connected() && sslDelegate.connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Void> connectAsync() {
|
||||||
|
throw new InternalError();
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncSSLTunnelConnection(InetSocketAddress addr,
|
||||||
|
HttpClientImpl client,
|
||||||
|
String[] alpn,
|
||||||
|
InetSocketAddress proxy)
|
||||||
|
{
|
||||||
|
super(addr, client);
|
||||||
|
this.serverName = Utils.getServerName(addr);
|
||||||
|
this.plainConnection = new PlainTunnelingConnection(addr, proxy, client);
|
||||||
|
this.sslDelegate = new AsyncSSLDelegate(plainConnection, client, alpn, serverName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
synchronized void configureMode(Mode mode) throws IOException {
|
||||||
|
super.configureMode(mode);
|
||||||
|
plainConnection.configureMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SSLParameters sslParameters() {
|
||||||
|
return sslDelegate.getSSLParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AsyncSSLTunnelConnection: " + super.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
PlainTunnelingConnection plainConnection() {
|
||||||
|
return plainConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
AsyncSSLDelegate sslDelegate() {
|
||||||
|
return sslDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ConnectionPool.CacheKey cacheKey() {
|
||||||
|
return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
long write(ByteBuffer[] buffers, int start, int number) throws IOException {
|
||||||
|
//debugPrint("Send", buffers, start, number);
|
||||||
|
ByteBuffer[] bufs = Utils.reduce(buffers, start, number);
|
||||||
|
long n = Utils.remaining(bufs);
|
||||||
|
sslDelegate.writeAsync(ByteBufferReference.toReferences(bufs));
|
||||||
|
sslDelegate.flushAsync();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
long write(ByteBuffer buffer) throws IOException {
|
||||||
|
//debugPrint("Send", buffer);
|
||||||
|
long n = buffer.remaining();
|
||||||
|
sslDelegate.writeAsync(ByteBufferReference.toReferences(buffer));
|
||||||
|
sslDelegate.flushAsync();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeAsync(ByteBufferReference[] buffers) throws IOException {
|
||||||
|
sslDelegate.writeAsync(buffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
|
||||||
|
sslDelegate.writeAsyncUnordered(buffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushAsync() throws IOException {
|
||||||
|
sslDelegate.flushAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
Utils.close(sslDelegate, plainConnection.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void shutdownInput() throws IOException {
|
||||||
|
plainConnection.channel().shutdownInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void shutdownOutput() throws IOException {
|
||||||
|
plainConnection.channel().shutdownOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SocketChannel channel() {
|
||||||
|
return plainConnection.channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isProxied() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsyncCallbacks(Consumer<ByteBufferReference> asyncReceiver,
|
||||||
|
Consumer<Throwable> errorReceiver,
|
||||||
|
Supplier<ByteBufferReference> readBufferSupplier) {
|
||||||
|
sslDelegate.setAsyncCallbacks(asyncReceiver, errorReceiver, readBufferSupplier);
|
||||||
|
plainConnection.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startReading() {
|
||||||
|
plainConnection.startReading();
|
||||||
|
sslDelegate.startReading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopAsyncReading() {
|
||||||
|
plainConnection.stopAsyncReading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableCallback() {
|
||||||
|
sslDelegate.enableCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeExceptionally(Throwable cause) throws IOException {
|
||||||
|
Utils.close(cause, sslDelegate, plainConnection.channel());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SSLEngine getEngine() {
|
||||||
|
return sslDelegate.getEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
SSLTunnelConnection downgrade() {
|
||||||
|
return new SSLTunnelConnection(this);
|
||||||
|
}
|
||||||
|
}
|
@ -82,9 +82,9 @@ abstract class ExchangeImpl<T> {
|
|||||||
c = c2.getConnectionFor(request);
|
c = c2.getConnectionFor(request);
|
||||||
} catch (Http2Connection.ALPNException e) {
|
} catch (Http2Connection.ALPNException e) {
|
||||||
// failed to negotiate "h2"
|
// failed to negotiate "h2"
|
||||||
AsyncSSLConnection as = e.getConnection();
|
AbstractAsyncSSLConnection as = e.getConnection();
|
||||||
as.stopAsyncReading();
|
as.stopAsyncReading();
|
||||||
SSLConnection sslc = new SSLConnection(as);
|
HttpConnection sslc = as.downgrade();
|
||||||
ExchangeImpl<U> ex = new Http1Exchange<>(exchange, sslc);
|
ExchangeImpl<U> ex = new Http1Exchange<>(exchange, sslc);
|
||||||
return ex;
|
return ex;
|
||||||
}
|
}
|
||||||
|
@ -211,12 +211,13 @@ class Http2Connection {
|
|||||||
this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
|
this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
|
||||||
this.windowUpdater = new ConnectionWindowUpdateSender(this, client.getReceiveBufferSize());
|
this.windowUpdater = new ConnectionWindowUpdateSender(this, client.getReceiveBufferSize());
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Case 1) Create from upgraded HTTP/1.1 connection.
|
/**
|
||||||
* Is ready to use. Will not be SSL. exchange is the Exchange
|
* Case 1) Create from upgraded HTTP/1.1 connection.
|
||||||
* that initiated the connection, whose response will be delivered
|
* Is ready to use. Will not be SSL. exchange is the Exchange
|
||||||
* on a Stream.
|
* that initiated the connection, whose response will be delivered
|
||||||
*/
|
* on a Stream.
|
||||||
|
*/
|
||||||
Http2Connection(HttpConnection connection,
|
Http2Connection(HttpConnection connection,
|
||||||
Http2ClientImpl client2,
|
Http2ClientImpl client2,
|
||||||
Exchange<?> exchange,
|
Exchange<?> exchange,
|
||||||
@ -280,7 +281,7 @@ class Http2Connection {
|
|||||||
* Throws an IOException if h2 was not negotiated
|
* Throws an IOException if h2 was not negotiated
|
||||||
*/
|
*/
|
||||||
private void checkSSLConfig() throws IOException {
|
private void checkSSLConfig() throws IOException {
|
||||||
AsyncSSLConnection aconn = (AsyncSSLConnection)connection;
|
AbstractAsyncSSLConnection aconn = (AbstractAsyncSSLConnection)connection;
|
||||||
SSLEngine engine = aconn.getEngine();
|
SSLEngine engine = aconn.getEngine();
|
||||||
String alpn = engine.getApplicationProtocol();
|
String alpn = engine.getApplicationProtocol();
|
||||||
if (alpn == null || !alpn.equals("h2")) {
|
if (alpn == null || !alpn.equals("h2")) {
|
||||||
@ -906,14 +907,14 @@ class Http2Connection {
|
|||||||
*/
|
*/
|
||||||
static final class ALPNException extends IOException {
|
static final class ALPNException extends IOException {
|
||||||
private static final long serialVersionUID = 23138275393635783L;
|
private static final long serialVersionUID = 23138275393635783L;
|
||||||
final AsyncSSLConnection connection;
|
final AbstractAsyncSSLConnection connection;
|
||||||
|
|
||||||
ALPNException(String msg, AsyncSSLConnection connection) {
|
ALPNException(String msg, AbstractAsyncSSLConnection connection) {
|
||||||
super(msg);
|
super(msg);
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncSSLConnection getConnection() {
|
AbstractAsyncSSLConnection getConnection() {
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ import java.nio.channels.SocketChannel;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import jdk.incubator.http.internal.common.ByteBufferReference;
|
import jdk.incubator.http.internal.common.ByteBufferReference;
|
||||||
import jdk.incubator.http.internal.common.Utils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps socket channel layer and takes care of SSL also.
|
* Wraps socket channel layer and takes care of SSL also.
|
||||||
@ -136,7 +135,11 @@ abstract class HttpConnection implements Closeable {
|
|||||||
String[] alpn, boolean isHttp2, HttpClientImpl client)
|
String[] alpn, boolean isHttp2, HttpClientImpl client)
|
||||||
{
|
{
|
||||||
if (proxy != null) {
|
if (proxy != null) {
|
||||||
return new SSLTunnelConnection(addr, client, proxy);
|
if (!isHttp2) {
|
||||||
|
return new SSLTunnelConnection(addr, client, proxy);
|
||||||
|
} else {
|
||||||
|
return new AsyncSSLTunnelConnection(addr, client, alpn, proxy);
|
||||||
|
}
|
||||||
} else if (!isHttp2) {
|
} else if (!isHttp2) {
|
||||||
return new SSLConnection(addr, client, alpn);
|
return new SSLConnection(addr, client, alpn);
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,12 +34,15 @@ import java.net.InetSocketAddress;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
|
* A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
|
||||||
* encrypt. Used by WebSocket. Subclassed in SSLTunnelConnection for encryption.
|
* encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
|
||||||
|
* Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
|
||||||
*/
|
*/
|
||||||
class PlainTunnelingConnection extends HttpConnection {
|
class PlainTunnelingConnection extends HttpConnection implements AsyncConnection {
|
||||||
|
|
||||||
final PlainHttpConnection delegate;
|
final PlainHttpConnection delegate;
|
||||||
protected final InetSocketAddress proxyAddr;
|
protected final InetSocketAddress proxyAddr;
|
||||||
@ -116,17 +119,17 @@ class PlainTunnelingConnection extends HttpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void writeAsync(ByteBufferReference[] buffers) throws IOException {
|
public void writeAsync(ByteBufferReference[] buffers) throws IOException {
|
||||||
delegate.writeAsync(buffers);
|
delegate.writeAsync(buffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
|
public void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
|
||||||
delegate.writeAsyncUnordered(buffers);
|
delegate.writeAsyncUnordered(buffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void flushAsync() throws IOException {
|
public void flushAsync() throws IOException {
|
||||||
delegate.flushAsync();
|
delegate.flushAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,4 +168,32 @@ class PlainTunnelingConnection extends HttpConnection {
|
|||||||
boolean isProxied() {
|
boolean isProxied() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAsyncCallbacks(Consumer<ByteBufferReference> asyncReceiver,
|
||||||
|
Consumer<Throwable> errorReceiver,
|
||||||
|
Supplier<ByteBufferReference> readBufferSupplier) {
|
||||||
|
delegate.setAsyncCallbacks(asyncReceiver, errorReceiver, readBufferSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startReading() {
|
||||||
|
delegate.startReading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopAsyncReading() {
|
||||||
|
delegate.stopAsyncReading();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableCallback() {
|
||||||
|
delegate.enableCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
synchronized void configureMode(Mode mode) throws IOException {
|
||||||
|
super.configureMode(mode);
|
||||||
|
delegate.configureMode(mode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,8 @@ class SSLConnection extends HttpConnection {
|
|||||||
*/
|
*/
|
||||||
SSLConnection(AsyncSSLConnection c) {
|
SSLConnection(AsyncSSLConnection c) {
|
||||||
super(c.address, c.client);
|
super(c.address, c.client);
|
||||||
this.delegate = c.plainConnection;
|
this.delegate = c.plainConnection();
|
||||||
AsyncSSLDelegate adel = c.sslDelegate;
|
AsyncSSLDelegate adel = c.sslDelegate();
|
||||||
this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName);
|
this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName);
|
||||||
this.alpn = adel.alpn;
|
this.alpn = adel.alpn;
|
||||||
this.serverName = adel.serverName;
|
this.serverName = adel.serverName;
|
||||||
|
@ -85,6 +85,19 @@ class SSLTunnelConnection extends HttpConnection {
|
|||||||
delegate = new PlainTunnelingConnection(addr, proxy, client);
|
delegate = new PlainTunnelingConnection(addr, proxy, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an SSLTunnelConnection from an existing connected AsyncSSLTunnelConnection.
|
||||||
|
* Used when downgrading from HTTP/2 to HTTP/1.1
|
||||||
|
*/
|
||||||
|
SSLTunnelConnection(AsyncSSLTunnelConnection c) {
|
||||||
|
super(c.address, c.client);
|
||||||
|
this.delegate = c.plainConnection();
|
||||||
|
AsyncSSLDelegate adel = c.sslDelegate();
|
||||||
|
this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName);
|
||||||
|
this.serverName = adel.serverName;
|
||||||
|
connected = c.connected();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
SSLParameters sslParameters() {
|
SSLParameters sslParameters() {
|
||||||
return sslDelegate.getSSLParameters();
|
return sslDelegate.getSSLParameters();
|
||||||
|
@ -56,9 +56,12 @@ import jdk.testlibrary.SimpleSSLContext;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @bug 8185852
|
* @bug 8185852 8181422
|
||||||
* @summary verifies that passing a proxy with an unresolved address does
|
* @summary Verifies that passing a proxy with an unresolved address does
|
||||||
* not cause java.nio.channels.UnresolvedAddressException
|
* not cause java.nio.channels.UnresolvedAddressException.
|
||||||
|
* Verifies that downgrading from HTTP/2 to HTTP/1.1 works through
|
||||||
|
* an SSL Tunnel connection when the client is HTTP/2 and the server
|
||||||
|
* and proxy are HTTP/1.1
|
||||||
* @modules jdk.incubator.httpclient
|
* @modules jdk.incubator.httpclient
|
||||||
* @library /lib/testlibrary/
|
* @library /lib/testlibrary/
|
||||||
* @build jdk.testlibrary.SimpleSSLContext ProxyTest
|
* @build jdk.testlibrary.SimpleSSLContext ProxyTest
|
||||||
@ -111,7 +114,7 @@ public class ProxyTest {
|
|||||||
server.start();
|
server.start();
|
||||||
try {
|
try {
|
||||||
test(server, HttpClient.Version.HTTP_1_1);
|
test(server, HttpClient.Version.HTTP_1_1);
|
||||||
// test(server, HttpClient.Version.HTTP_2);
|
test(server, HttpClient.Version.HTTP_2);
|
||||||
} finally {
|
} finally {
|
||||||
server.stop(0);
|
server.stop(0);
|
||||||
System.out.println("Server stopped");
|
System.out.println("Server stopped");
|
||||||
|
323
jdk/test/java/net/httpclient/http2/ProxyTest2.java
Normal file
323
jdk/test/java/net/httpclient/http2/ProxyTest2.java
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpContext;
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
import com.sun.net.httpserver.HttpHandler;
|
||||||
|
import com.sun.net.httpserver.HttpServer;
|
||||||
|
import com.sun.net.httpserver.HttpsConfigurator;
|
||||||
|
import com.sun.net.httpserver.HttpsParameters;
|
||||||
|
import com.sun.net.httpserver.HttpsServer;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import jdk.incubator.http.HttpClient;
|
||||||
|
import jdk.incubator.http.HttpRequest;
|
||||||
|
import jdk.incubator.http.HttpResponse;
|
||||||
|
import jdk.testlibrary.SimpleSSLContext;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8181422
|
||||||
|
* @summary Verifies that you can access an HTTP/2 server over HTTPS by
|
||||||
|
* tunnelling through an HTTP/1.1 proxy.
|
||||||
|
* @modules jdk.incubator.httpclient
|
||||||
|
* @library /lib/testlibrary server
|
||||||
|
* @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common
|
||||||
|
* jdk.incubator.httpclient/jdk.incubator.http.internal.frame
|
||||||
|
* jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
|
||||||
|
* @build jdk.testlibrary.SimpleSSLContext ProxyTest2
|
||||||
|
* @run main/othervm ProxyTest2
|
||||||
|
* @author danielfuchs
|
||||||
|
*/
|
||||||
|
public class ProxyTest2 {
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
|
||||||
|
public boolean verify(String hostname, SSLSession session) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SSLContext.setDefault(new SimpleSSLContext().get());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new ExceptionInInitializerError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final String RESPONSE = "<html><body><p>Hello World!</body></html>";
|
||||||
|
static final String PATH = "/foo/";
|
||||||
|
|
||||||
|
static Http2TestServer createHttpsServer(ExecutorService exec) throws Exception {
|
||||||
|
Http2TestServer server = new Http2TestServer(true, 0, exec, SSLContext.getDefault());
|
||||||
|
server.addHandler(new Http2Handler() {
|
||||||
|
@Override
|
||||||
|
public void handle(Http2TestExchange he) throws IOException {
|
||||||
|
he.getResponseHeaders().addHeader("encoding", "UTF-8");
|
||||||
|
he.sendResponseHeaders(200, RESPONSE.length());
|
||||||
|
he.getResponseBody().write(RESPONSE.getBytes(StandardCharsets.UTF_8));
|
||||||
|
he.close();
|
||||||
|
}
|
||||||
|
}, PATH);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
ExecutorService exec = Executors.newCachedThreadPool();
|
||||||
|
Http2TestServer server = createHttpsServer(exec);
|
||||||
|
server.start();
|
||||||
|
try {
|
||||||
|
// Http2TestServer over HTTPS does not support HTTP/1.1
|
||||||
|
// => only test with a HTTP/2 client
|
||||||
|
test(server, HttpClient.Version.HTTP_2);
|
||||||
|
} finally {
|
||||||
|
server.stop();
|
||||||
|
exec.shutdown();
|
||||||
|
System.out.println("Server stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void test(Http2TestServer server, HttpClient.Version version)
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
System.out.println("Server is: " + server.getAddress().toString());
|
||||||
|
URI uri = new URI("https://localhost:" + server.getAddress().getPort() + PATH + "x");
|
||||||
|
TunnelingProxy proxy = new TunnelingProxy(server);
|
||||||
|
proxy.start();
|
||||||
|
try {
|
||||||
|
System.out.println("Proxy started");
|
||||||
|
Proxy p = new Proxy(Proxy.Type.HTTP,
|
||||||
|
InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
|
||||||
|
System.out.println("Setting up request with HttpClient for version: "
|
||||||
|
+ version.name() + "URI=" + uri);
|
||||||
|
ProxySelector ps = ProxySelector.of(
|
||||||
|
InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
|
||||||
|
HttpClient client = HttpClient.newBuilder()
|
||||||
|
.version(version)
|
||||||
|
.proxy(ps)
|
||||||
|
.build();
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(uri)
|
||||||
|
.GET()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
System.out.println("Sending request with HttpClient");
|
||||||
|
HttpResponse<String> response
|
||||||
|
= client.send(request, HttpResponse.BodyHandler.asString());
|
||||||
|
System.out.println("Got response");
|
||||||
|
String resp = response.body();
|
||||||
|
System.out.println("Received: " + resp);
|
||||||
|
if (!RESPONSE.equals(resp)) {
|
||||||
|
throw new AssertionError("Unexpected response");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
System.out.println("Stopping proxy");
|
||||||
|
proxy.stop();
|
||||||
|
System.out.println("Proxy stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TunnelingProxy {
|
||||||
|
final Thread accept;
|
||||||
|
final ServerSocket ss;
|
||||||
|
final boolean DEBUG = false;
|
||||||
|
final Http2TestServer serverImpl;
|
||||||
|
TunnelingProxy(Http2TestServer serverImpl) throws IOException {
|
||||||
|
this.serverImpl = serverImpl;
|
||||||
|
ss = new ServerSocket();
|
||||||
|
accept = new Thread(this::accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() throws IOException {
|
||||||
|
ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
|
||||||
|
accept.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipe the input stream to the output stream.
|
||||||
|
private synchronized Thread pipe(InputStream is, OutputStream os, char tag) {
|
||||||
|
return new Thread("TunnelPipe("+tag+")") {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
int c;
|
||||||
|
while ((c = is.read()) != -1) {
|
||||||
|
os.write(c);
|
||||||
|
os.flush();
|
||||||
|
// if DEBUG prints a + or a - for each transferred
|
||||||
|
// character.
|
||||||
|
if (DEBUG) System.out.print(tag);
|
||||||
|
}
|
||||||
|
is.close();
|
||||||
|
} finally {
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
if (DEBUG) ex.printStackTrace(System.out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getAddress() {
|
||||||
|
return new InetSocketAddress(ss.getInetAddress(), ss.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a bit shaky. It doesn't handle continuation
|
||||||
|
// lines, but our client shouldn't send any.
|
||||||
|
// Read a line from the input stream, swallowing the final
|
||||||
|
// \r\n sequence. Stops at the first \n, doesn't complain
|
||||||
|
// if it wasn't preceded by '\r'.
|
||||||
|
//
|
||||||
|
String readLine(InputStream r) throws IOException {
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
int c;
|
||||||
|
while ((c = r.read()) != -1) {
|
||||||
|
if (c == '\n') break;
|
||||||
|
b.appendCodePoint(c);
|
||||||
|
}
|
||||||
|
if (b.codePointAt(b.length() -1) == '\r') {
|
||||||
|
b.delete(b.length() -1, b.length());
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accept() {
|
||||||
|
Socket clientConnection = null;
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
System.out.println("Tunnel: Waiting for client");
|
||||||
|
Socket previous = clientConnection;
|
||||||
|
try {
|
||||||
|
clientConnection = ss.accept();
|
||||||
|
} catch (IOException io) {
|
||||||
|
if (DEBUG) io.printStackTrace(System.out);
|
||||||
|
break;
|
||||||
|
} finally {
|
||||||
|
// we have only 1 client at a time, so it is safe
|
||||||
|
// to close the previous connection here
|
||||||
|
if (previous != null) previous.close();
|
||||||
|
}
|
||||||
|
System.out.println("Tunnel: Client accepted");
|
||||||
|
Socket targetConnection = null;
|
||||||
|
InputStream ccis = clientConnection.getInputStream();
|
||||||
|
OutputStream ccos = clientConnection.getOutputStream();
|
||||||
|
Writer w = new OutputStreamWriter(ccos, "UTF-8");
|
||||||
|
PrintWriter pw = new PrintWriter(w);
|
||||||
|
System.out.println("Tunnel: Reading request line");
|
||||||
|
String requestLine = readLine(ccis);
|
||||||
|
System.out.println("Tunnel: Request status line: " + requestLine);
|
||||||
|
if (requestLine.startsWith("CONNECT ")) {
|
||||||
|
// We should probably check that the next word following
|
||||||
|
// CONNECT is the host:port of our HTTPS serverImpl.
|
||||||
|
// Some improvement for a followup!
|
||||||
|
|
||||||
|
// Read all headers until we find the empty line that
|
||||||
|
// signals the end of all headers.
|
||||||
|
while(!requestLine.equals("")) {
|
||||||
|
System.out.println("Tunnel: Reading header: "
|
||||||
|
+ (requestLine = readLine(ccis)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open target connection
|
||||||
|
targetConnection = new Socket(
|
||||||
|
serverImpl.getAddress().getAddress(),
|
||||||
|
serverImpl.getAddress().getPort());
|
||||||
|
|
||||||
|
// Then send the 200 OK response to the client
|
||||||
|
System.out.println("Tunnel: Sending "
|
||||||
|
+ "HTTP/1.1 200 OK\r\n\r\n");
|
||||||
|
pw.print("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
|
||||||
|
pw.flush();
|
||||||
|
} else {
|
||||||
|
// This should not happen.
|
||||||
|
throw new IOException("Tunnel: Unexpected status line: "
|
||||||
|
+ requestLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipe the input stream of the client connection to the
|
||||||
|
// output stream of the target connection and conversely.
|
||||||
|
// Now the client and target will just talk to each other.
|
||||||
|
System.out.println("Tunnel: Starting tunnel pipes");
|
||||||
|
Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+');
|
||||||
|
Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-');
|
||||||
|
t1.start();
|
||||||
|
t2.start();
|
||||||
|
|
||||||
|
// We have only 1 client... wait until it has finished before
|
||||||
|
// accepting a new connection request.
|
||||||
|
// System.out.println("Tunnel: Waiting for pipes to close");
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
System.out.println("Tunnel: Done - waiting for next client");
|
||||||
|
}
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
try {
|
||||||
|
ss.close();
|
||||||
|
} catch (IOException ex1) {
|
||||||
|
ex.addSuppressed(ex1);
|
||||||
|
}
|
||||||
|
ex.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() throws IOException {
|
||||||
|
ss.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Configurator extends HttpsConfigurator {
|
||||||
|
public Configurator(SSLContext ctx) {
|
||||||
|
super(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure (HttpsParameters params) {
|
||||||
|
params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -201,7 +201,17 @@ public class Http2TestServer implements AutoCloseable {
|
|||||||
InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
|
InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||||
Http2TestServerConnection c = new Http2TestServerConnection(this, socket);
|
Http2TestServerConnection c = new Http2TestServerConnection(this, socket);
|
||||||
connections.put(addr, c);
|
connections.put(addr, c);
|
||||||
c.run();
|
try {
|
||||||
|
c.run();
|
||||||
|
} catch(Throwable e) {
|
||||||
|
// we should not reach here, but if we do
|
||||||
|
// the connection might not have been closed
|
||||||
|
// and if so then the client might wait
|
||||||
|
// forever.
|
||||||
|
connections.remove(addr, c);
|
||||||
|
c.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (!stopping) {
|
if (!stopping) {
|
||||||
|
@ -133,10 +133,10 @@ public class Http2TestServerConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
|
stopping = true;
|
||||||
streams.forEach((i, q) -> {
|
streams.forEach((i, q) -> {
|
||||||
q.close();
|
q.close();
|
||||||
});
|
});
|
||||||
stopping = true;
|
|
||||||
try {
|
try {
|
||||||
socket.close();
|
socket.close();
|
||||||
// TODO: put a reset on each stream
|
// TODO: put a reset on each stream
|
||||||
@ -557,7 +557,14 @@ public class Http2TestServerConnection {
|
|||||||
void writeLoop() {
|
void writeLoop() {
|
||||||
try {
|
try {
|
||||||
while (!stopping) {
|
while (!stopping) {
|
||||||
Http2Frame frame = outputQ.take();
|
Http2Frame frame;
|
||||||
|
try {
|
||||||
|
frame = outputQ.take();
|
||||||
|
} catch(IOException x) {
|
||||||
|
if (stopping && x.getCause() instanceof InterruptedException) {
|
||||||
|
break;
|
||||||
|
} else throw x;
|
||||||
|
}
|
||||||
if (frame instanceof ResponseHeaders) {
|
if (frame instanceof ResponseHeaders) {
|
||||||
ResponseHeaders rh = (ResponseHeaders)frame;
|
ResponseHeaders rh = (ResponseHeaders)frame;
|
||||||
HeadersFrame hf = new HeadersFrame(rh.streamid(), rh.getFlags(), encodeHeaders(rh.headers));
|
HeadersFrame hf = new HeadersFrame(rh.streamid(), rh.getFlags(), encodeHeaders(rh.headers));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user