| /* | |
| * Copyright (c) 2023, 2024, 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 | |
| * @bug 8314978 | |
| * @summary Multiple server call from connection failing with expect100 in | |
| * getOutputStream | |
| * @library /test/lib | |
| * @run junit/othervm HttpURLConnectionExpect100Test | |
| */ | |
| import java.io.BufferedReader; | |
| import java.io.IOException; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.OutputStream; | |
| import java.io.PrintStream; | |
| import java.net.InetAddress; | |
| import java.net.InetSocketAddress; | |
| import java.net.ServerSocket; | |
| import java.net.Socket; | |
| import java.net.URL; | |
| import java.net.HttpURLConnection; | |
| import jdk.test.lib.net.URIBuilder; | |
| import org.junit.jupiter.api.AfterAll; | |
| import org.junit.jupiter.api.BeforeAll; | |
| import org.junit.jupiter.api.Test; | |
| import org.junit.jupiter.api.TestInstance; | |
| import static org.junit.jupiter.api.Assertions.assertEquals; | |
| @TestInstance(TestInstance.Lifecycle.PER_CLASS) | |
| public class HttpURLConnectionExpect100Test { | |
| private HttpServer server; | |
| private int port; | |
| static final String RESPONSE = "This is default response."; | |
| @BeforeAll | |
| void setup() throws Exception { | |
| server = HttpServer.create(); | |
| port = server.getPort(); | |
| } | |
| @AfterAll | |
| void teardown() throws Exception { | |
| server.close(); | |
| } | |
| @Test | |
| public void expect100ContinueHitCountTest() throws Exception { | |
| server.resetHitCount(); | |
| URL url = URIBuilder.newBuilder() | |
| .scheme("http") | |
| .loopback() | |
| .port(port) | |
| .toURL(); | |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | |
| conn.setRequestMethod("PUT"); | |
| //send expect continue | |
| conn.setRequestProperty("Expect", "100-continue"); | |
| sendRequest(conn); | |
| getHeaderField(conn); | |
| // Server rejects the expect 100-continue request with 417 response | |
| assertEquals(417, conn.getResponseCode()); | |
| assertEquals(1, server.getServerHitCount()); | |
| } | |
| @Test | |
| public void defaultRequestHitCountTest() throws Exception { | |
| server.resetHitCount(); | |
| URL url = URIBuilder.newBuilder() | |
| .scheme("http") | |
| .loopback() | |
| .port(port) | |
| .toURL(); | |
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | |
| conn.setRequestMethod("PUT"); | |
| sendRequest(conn); | |
| getHeaderField(conn); | |
| assertEquals(200, conn.getResponseCode()); | |
| try ( InputStream in = conn.getInputStream()) { | |
| byte[] data = in.readAllBytes(); | |
| assertEquals(RESPONSE.length(), data.length); | |
| } | |
| assertEquals(1, server.getServerHitCount()); | |
| } | |
| private void sendRequest(final HttpURLConnection conn) throws Exception { | |
| conn.setDoOutput(true); | |
| conn.setFixedLengthStreamingMode(10); | |
| byte[] payload = new byte[10]; | |
| try ( OutputStream os = conn.getOutputStream()) { | |
| os.write(payload); | |
| os.flush(); | |
| } catch (IOException e) { | |
| // intentional, server will reject the expect 100 | |
| System.err.println("Got expected exception: " + e); | |
| } | |
| } | |
| private void getHeaderField(final HttpURLConnection conn) { | |
| // Call getHeaderFiels in loop, this should not hit server. | |
| for (int i = 0; i < 5; i++) { | |
| System.out.println("Getting: field" + i); | |
| conn.getHeaderField("field" + i); | |
| } | |
| } | |
| static class HttpServer extends Thread { | |
| private final ServerSocket ss; | |
| private static HttpServer inst; | |
| private volatile int hitCount; | |
| private volatile boolean isRunning; | |
| private final int port; | |
| private HttpServer() throws IOException { | |
| InetAddress loopback = InetAddress.getLoopbackAddress(); | |
| ss = new ServerSocket(); | |
| ss.bind(new InetSocketAddress(loopback, 0)); | |
| port = ss.getLocalPort(); | |
| isRunning = true; | |
| } | |
| static HttpServer create() throws IOException { | |
| if (inst != null) { | |
| return inst; | |
| } else { | |
| inst = new HttpServer(); | |
| inst.setDaemon(true); | |
| inst.start(); | |
| return inst; | |
| } | |
| } | |
| int getServerHitCount() { | |
| return hitCount; | |
| } | |
| void resetHitCount() { | |
| hitCount = 0; | |
| } | |
| int getPort() { | |
| return port; | |
| } | |
| void close() { | |
| isRunning = false; | |
| if (ss != null && !ss.isClosed()) { | |
| try { | |
| ss.close(); | |
| } catch (IOException ex) { | |
| } | |
| } | |
| } | |
| @Override | |
| public void run() { | |
| Socket client; | |
| try (ss) { | |
| while (isRunning) { | |
| client = ss.accept(); | |
| System.out.println(client.getRemoteSocketAddress().toString()); | |
| hitCount++; | |
| handleConnection(client); | |
| } | |
| } catch (IOException ex) { | |
| // throw exception only if isRunning is true | |
| if (isRunning) { | |
| throw new RuntimeException(ex); | |
| } | |
| } | |
| } | |
| private void handleConnection(Socket client) throws IOException { | |
| try (client; BufferedReader in = new BufferedReader( | |
| new InputStreamReader(client.getInputStream())); | |
| PrintStream out = new PrintStream(client.getOutputStream())) { | |
| handle_connection(in, out); | |
| } catch (InterruptedException e) { | |
| Thread.currentThread().interrupt(); | |
| } | |
| } | |
| private void handle_connection(BufferedReader in, PrintStream out) | |
| throws IOException, InterruptedException { | |
| StringBuilder clientRequest = new StringBuilder(); | |
| String line = null; | |
| do { | |
| line = in.readLine(); | |
| clientRequest.append(line); | |
| } while (line != null && line.length() != 0); | |
| if (clientRequest.toString().contains("100-continue")) { | |
| rejectExpect100Continue(out); | |
| } else { | |
| defaultResponse(out); | |
| } | |
| // wait until the client closes the socket | |
| while (line != null) { | |
| line = in.readLine(); | |
| } | |
| } | |
| private void rejectExpect100Continue(PrintStream out) { | |
| out.print("HTTP/1.1 417 Expectation Failed\r\n"); | |
| out.print("Server: Test-Server\r\n"); | |
| out.print("Connection: close\r\n"); | |
| out.print("Content-Length: 0\r\n"); | |
| out.print("\r\n"); | |
| out.flush(); | |
| } | |
| private void defaultResponse(PrintStream out) { | |
| // send the 200 OK | |
| out.print("HTTP/1.1 200 OK\r\n"); | |
| out.print("Server: Test-Server\r\n"); | |
| out.print("Connection: close\r\n"); | |
| out.print("Content-Length: " + RESPONSE.length() + "\r\n\r\n"); | |
| out.print(RESPONSE); | |
| out.flush(); | |
| } | |
| } | |
| } |