blob: 3d6013f557c0779ab5b3aab77c9c408fcc90d85b [file] [log] [blame]
/*
* Copyright (c) 2019, 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.
*/
/*
* @test
* @summary This test will timeout if the ALPN CF is not completed
* when a 'Connection reset by peer' exception is raised
* during the handshake.
* @bug 8217094
* @modules java.net.http
* java.logging
* @build ALPNFailureTest
* @run main/othervm -Djdk.internal.httpclient.debug=true ALPNFailureTest HTTP_1_1
* @run main/othervm ALPNFailureTest HTTP_2
*/
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLContext;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ProxySelector;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.StandardSocketOptions;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpTimeoutException;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class ALPNFailureTest {
public static void main(String[] args) throws Exception{
if (args == null || args.length == 0) {
args = new String[] {HttpClient.Version.HTTP_1_1.name()};
}
ServerSocket socket = ServerSocketFactory.getDefault()
.createServerSocket(0, 10, InetAddress.getLoopbackAddress());
test(socket, null, null, args);
}
public static void test(ServerSocket socket, SSLContext context,
ProxySelector ps, String... args)
throws Exception
{
System.out.println("Tests a race condition in SSLTube/SSLFlowDelegate");
System.out.println("This test will timeout if the ALPN CF is not completed" +
" when a 'Connection reset by peer' exception is raised" +
" during the handshake - see 8217094.");
URI uri = new URI("https", null,
socket.getInetAddress().getHostAddress(), socket.getLocalPort(),
"/ReadOnlyServer/https_1_1/", null, null);
HttpRequest request1 = HttpRequest.newBuilder(uri)
.GET().build();
HttpRequest request2 = HttpRequest.newBuilder(uri)
.POST(HttpRequest.BodyPublishers.ofString("foo")).build();
ReadOnlyServer server = new ReadOnlyServer(socket);
Thread serverThread = new Thread(server, "ServerThread");
serverThread.start();
try {
for (var arg : args) {
var version = HttpClient.Version.valueOf(arg);
HttpClient.Builder builder = HttpClient.newBuilder()
.version(version);
if (ps != null) builder.proxy(ps);
if (context != null) builder.sslContext(context);
HttpClient client = builder.build();
for (var request : List.of(request1, request2)) {
System.out.println("Server is " + socket.getLocalSocketAddress()
+ ", Version is " + version + ", Method is " + request.method()
+ (ps == null ? ", no proxy"
: (", Proxy is " + ps.select(request.uri()))));
try {
HttpResponse<String> resp =
client.send(request, HttpResponse.BodyHandlers.ofString());
throw new AssertionError(
"Client should not have received any response: " + resp);
} catch (HttpTimeoutException x) {
System.out.println("Unexpected " + x);
x.printStackTrace();
throw new AssertionError("Unexpected exception " + x, x);
} catch (Exception x) {
// We expect IOException("Connection reset by peer"), but
// any exception would do: we just don't want to linger
// forever.
System.err.println("Client got expected exception: " + x);
x.printStackTrace(System.out);
}
}
}
} finally {
server.close();
}
}
public static class ReadOnlyServer implements Runnable, Closeable {
final ServerSocket socket;
final AtomicReference<Throwable> errorRef = new AtomicReference<>();
final AtomicBoolean closing = new AtomicBoolean();
ReadOnlyServer(ServerSocket socket) {
this.socket = socket;
}
@Override
public void run() {
int count = 0;
int all = 0;
try {
System.out.println("Server starting");
while (!closing.get()) {
all += count;
count = 0;
try (Socket client = socket.accept()) {
client.setSoTimeout(1000);
client.setOption(StandardSocketOptions.SO_LINGER, 0);
InputStream is = client.getInputStream();
OutputStream os = client.getOutputStream();
boolean drain = true;
int timeouts = 0;
// now read some byte from the ClientHello
// and abruptly close the socket.
while (drain) {
try {
is.read();
count++;
if (count >= 50) {
drain = false;
}
} catch (SocketTimeoutException so) {
// make sure we read something
if (count > 0) timeouts++;
if (timeouts == 5) {
// presumably the client is
// waiting for us to answer...
// but we should not reach here.
drain = false;
}
}
}
System.out.println("Got " + count + " bytes");
}
}
} catch (Throwable t) {
if (!closing.get()) {
errorRef.set(t);
t.printStackTrace();
}
} finally {
System.out.println("Server existing after reading " + (all + count) + " bytes");
close();
}
}
@Override
public void close() {
if (closing.getAndSet(true))
return; // already closed
try {
socket.close();
} catch (IOException x) {
System.out.println("Exception while closing: " + x);
}
}
}
}