|
| 1 | +/* |
| 2 | + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. |
| 3 | + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | + * |
| 5 | + * This code is free software; you can redistribute it and/or modify it |
| 6 | + * under the terms of the GNU General Public License version 2 only, as |
| 7 | + * published by the Free Software Foundation. |
| 8 | + * |
| 9 | + * This code is distributed in the hope that it will be useful, but WITHOUT |
| 10 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | + * version 2 for more details (a copy is included in the LICENSE file that |
| 13 | + * accompanied this code). |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License version |
| 16 | + * 2 along with this work; if not, write to the Free Software Foundation, |
| 17 | + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | + * |
| 19 | + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| 20 | + * or visit www.oracle.com if you need additional information or have any |
| 21 | + * questions. |
| 22 | + */ |
| 23 | + |
| 24 | +import java.io.IOException; |
| 25 | +import java.io.InputStream; |
| 26 | +import java.io.OutputStream; |
| 27 | +import java.net.HttpURLConnection; |
| 28 | +import java.net.InetAddress; |
| 29 | +import java.net.ProtocolException; |
| 30 | +import java.net.ServerSocket; |
| 31 | +import java.net.Socket; |
| 32 | +import java.net.URI; |
| 33 | +import java.nio.charset.StandardCharsets; |
| 34 | + |
| 35 | +import jdk.test.lib.net.URIBuilder; |
| 36 | +import org.testng.Assert; |
| 37 | +import org.testng.annotations.AfterClass; |
| 38 | +import org.testng.annotations.BeforeClass; |
| 39 | +import org.testng.annotations.Test; |
| 40 | + |
| 41 | +/** |
| 42 | + * @test |
| 43 | + * @bug 8170305 |
| 44 | + * @summary Tests behaviour of HttpURLConnection when server responds with 1xx interim response status codes |
| 45 | + * @library /test/lib |
| 46 | + * @run testng Response1xxTest |
| 47 | + */ |
| 48 | +public class Response1xxTest { |
| 49 | + private static final String EXPECTED_RSP_BODY = "Hello World"; |
| 50 | + |
| 51 | + private ServerSocket serverSocket; |
| 52 | + private Http11Server server; |
| 53 | + private String requestURIBase; |
| 54 | + |
| 55 | + |
| 56 | + @BeforeClass |
| 57 | + public void setup() throws Exception { |
| 58 | + serverSocket = new ServerSocket(0, 0, InetAddress.getLoopbackAddress()); |
| 59 | + server = new Http11Server(serverSocket); |
| 60 | + new Thread(server).start(); |
| 61 | + requestURIBase = URIBuilder.newBuilder().scheme("http").loopback() |
| 62 | + .port(serverSocket.getLocalPort()).build().toString(); |
| 63 | + } |
| 64 | + |
| 65 | + @AfterClass |
| 66 | + public void teardown() throws Exception { |
| 67 | + if (server != null) { |
| 68 | + server.stop = true; |
| 69 | + System.out.println("(HTTP 1.1) Server stop requested"); |
| 70 | + } |
| 71 | + if (serverSocket != null) { |
| 72 | + serverSocket.close(); |
| 73 | + System.out.println("Closed (HTTP 1.1) server socket"); |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + private static final class Http11Server implements Runnable { |
| 78 | + private static final int CONTENT_LENGTH = EXPECTED_RSP_BODY.getBytes(StandardCharsets.UTF_8).length; |
| 79 | + |
| 80 | + private static final String HTTP_1_1_RSP_200 = "HTTP/1.1 200 OK\r\n" + |
| 81 | + "Content-Length: " + CONTENT_LENGTH + "\r\n\r\n" + |
| 82 | + EXPECTED_RSP_BODY; |
| 83 | + |
| 84 | + private static final String REQ_LINE_FOO = "GET /test/foo HTTP/1.1\r\n"; |
| 85 | + private static final String REQ_LINE_BAR = "GET /test/bar HTTP/1.1\r\n"; |
| 86 | + private static final String REQ_LINE_HELLO = "GET /test/hello HTTP/1.1\r\n"; |
| 87 | + private static final String REQ_LINE_BYE = "GET /test/bye HTTP/1.1\r\n"; |
| 88 | + |
| 89 | + |
| 90 | + private final ServerSocket serverSocket; |
| 91 | + private volatile boolean stop; |
| 92 | + |
| 93 | + private Http11Server(final ServerSocket serverSocket) { |
| 94 | + this.serverSocket = serverSocket; |
| 95 | + } |
| 96 | + |
| 97 | + @Override |
| 98 | + public void run() { |
| 99 | + System.out.println("Server running at " + serverSocket); |
| 100 | + while (!stop) { |
| 101 | + Socket socket = null; |
| 102 | + try { |
| 103 | + // accept a connection |
| 104 | + socket = serverSocket.accept(); |
| 105 | + System.out.println("Accepted connection from client " + socket); |
| 106 | + // read request |
| 107 | + final String requestLine; |
| 108 | + try { |
| 109 | + requestLine = readRequestLine(socket); |
| 110 | + } catch (Throwable t) { |
| 111 | + // ignore connections from potential rogue client |
| 112 | + System.err.println("Ignoring connection/request from client " + socket |
| 113 | + + " due to exception:"); |
| 114 | + t.printStackTrace(); |
| 115 | + // close the socket |
| 116 | + safeClose(socket); |
| 117 | + continue; |
| 118 | + } |
| 119 | + System.out.println("Received following request line from client " + socket |
| 120 | + + " :\n" + requestLine); |
| 121 | + final int informationalResponseCode; |
| 122 | + if (requestLine.startsWith(REQ_LINE_FOO)) { |
| 123 | + // we will send intermediate/informational 102 response |
| 124 | + informationalResponseCode = 102; |
| 125 | + } else if (requestLine.startsWith(REQ_LINE_BAR)) { |
| 126 | + // we will send intermediate/informational 103 response |
| 127 | + informationalResponseCode = 103; |
| 128 | + } else if (requestLine.startsWith(REQ_LINE_HELLO)) { |
| 129 | + // we will send intermediate/informational 100 response |
| 130 | + informationalResponseCode = 100; |
| 131 | + } else if (requestLine.startsWith(REQ_LINE_BYE)) { |
| 132 | + // we will send intermediate/informational 101 response |
| 133 | + informationalResponseCode = 101; |
| 134 | + } else { |
| 135 | + // unexpected client. ignore and close the client |
| 136 | + System.err.println("Ignoring unexpected request from client " + socket); |
| 137 | + safeClose(socket); |
| 138 | + continue; |
| 139 | + } |
| 140 | + try (final OutputStream os = socket.getOutputStream()) { |
| 141 | + // send informational response headers a few times (spec allows them to |
| 142 | + // be sent multiple times) |
| 143 | + for (int i = 0; i < 3; i++) { |
| 144 | + // send 1xx response header |
| 145 | + os.write(("HTTP/1.1 " + informationalResponseCode + "\r\n\r\n") |
| 146 | + .getBytes(StandardCharsets.UTF_8)); |
| 147 | + os.flush(); |
| 148 | + System.out.println("Sent response code " + informationalResponseCode |
| 149 | + + " to client " + socket); |
| 150 | + } |
| 151 | + // now send a final response |
| 152 | + System.out.println("Now sending 200 response code to client " + socket); |
| 153 | + os.write(HTTP_1_1_RSP_200.getBytes(StandardCharsets.UTF_8)); |
| 154 | + os.flush(); |
| 155 | + System.out.println("Sent 200 response code to client " + socket); |
| 156 | + } |
| 157 | + } catch (Throwable t) { |
| 158 | + // close the client connection |
| 159 | + safeClose(socket); |
| 160 | + // continue accepting any other client connections until we are asked to stop |
| 161 | + System.err.println("Ignoring exception in server:"); |
| 162 | + t.printStackTrace(); |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + static String readRequestLine(final Socket sock) throws IOException { |
| 168 | + final InputStream is = sock.getInputStream(); |
| 169 | + final StringBuilder sb = new StringBuilder(""); |
| 170 | + byte[] buf = new byte[1024]; |
| 171 | + while (!sb.toString().endsWith("\r\n\r\n")) { |
| 172 | + final int numRead = is.read(buf); |
| 173 | + if (numRead == -1) { |
| 174 | + return sb.toString(); |
| 175 | + } |
| 176 | + final String part = new String(buf, 0, numRead, StandardCharsets.ISO_8859_1); |
| 177 | + sb.append(part); |
| 178 | + } |
| 179 | + return sb.toString(); |
| 180 | + } |
| 181 | + |
| 182 | + private static void safeClose(final Socket socket) { |
| 183 | + try { |
| 184 | + socket.close(); |
| 185 | + } catch (Throwable t) { |
| 186 | + // ignore |
| 187 | + } |
| 188 | + } |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Tests that when a HTTP/1.1 server sends intermediate 1xx response codes and then the final |
| 193 | + * response, the client (internally) will ignore those intermediate informational response codes |
| 194 | + * and only return the final response to the application |
| 195 | + */ |
| 196 | + @Test |
| 197 | + public void test1xx() throws Exception { |
| 198 | + final URI[] requestURIs = new URI[]{ |
| 199 | + new URI(requestURIBase + "/test/foo"), |
| 200 | + new URI(requestURIBase + "/test/bar"), |
| 201 | + new URI(requestURIBase + "/test/hello")}; |
| 202 | + for (final URI requestURI : requestURIs) { |
| 203 | + System.out.println("Issuing request to " + requestURI); |
| 204 | + final HttpURLConnection urlConnection = (HttpURLConnection) requestURI.toURL().openConnection(); |
| 205 | + final int responseCode = urlConnection.getResponseCode(); |
| 206 | + Assert.assertEquals(responseCode, 200, "Unexpected response code"); |
| 207 | + final String body; |
| 208 | + try (final InputStream is = urlConnection.getInputStream()) { |
| 209 | + final byte[] bytes = is.readAllBytes(); |
| 210 | + body = new String(bytes, StandardCharsets.UTF_8); |
| 211 | + } |
| 212 | + Assert.assertEquals(body, EXPECTED_RSP_BODY, "Unexpected response body"); |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + /** |
| 217 | + * Tests that when a HTTP/1.1 server sends 101 response code, when the client |
| 218 | + * didn't ask for a connection upgrade, then the request fails with an exception. |
| 219 | + */ |
| 220 | + @Test |
| 221 | + public void test101CausesRequestFailure() throws Exception { |
| 222 | + final URI requestURI = new URI(requestURIBase + "/test/bye"); |
| 223 | + System.out.println("Issuing request to " + requestURI); |
| 224 | + final HttpURLConnection urlConnection = (HttpURLConnection) requestURI.toURL().openConnection(); |
| 225 | + // we expect the request to fail because the server unexpectedly sends a 101 response |
| 226 | + Assert.assertThrows(ProtocolException.class, () -> urlConnection.getResponseCode()); |
| 227 | + } |
| 228 | +} |
0 commit comments