| /* |
| * 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); |
| } |
| } |
| } |
| } |