Path: blob/master/test/jdk/com/sun/net/httpserver/bugs/HandlerConnectionClose.java
41155 views
/*1* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/**24* @test25* @bug 821855426* @summary test that the handler can request a connection close.27* @library /test/lib28* @build jdk.test.lib.net.SimpleSSLContext29* @run main/othervm HandlerConnectionClose30*/3132import com.sun.net.httpserver.HttpExchange;33import com.sun.net.httpserver.HttpHandler;34import com.sun.net.httpserver.HttpServer;35import com.sun.net.httpserver.HttpsConfigurator;36import com.sun.net.httpserver.HttpsServer;3738import java.io.ByteArrayOutputStream;39import java.io.IOException;40import java.io.InputStream;41import java.io.OutputStream;42import java.net.HttpURLConnection;43import java.net.InetAddress;44import java.net.InetSocketAddress;45import java.net.Socket;46import java.net.URI;47import java.net.URL;48import java.nio.charset.StandardCharsets;49import java.util.List;50import java.util.Locale;51import java.util.logging.Handler;52import java.util.logging.Level;53import java.util.logging.Logger;54import java.util.logging.SimpleFormatter;55import java.util.logging.StreamHandler;56import jdk.test.lib.net.SimpleSSLContext;57import javax.net.ssl.HttpsURLConnection;58import javax.net.ssl.SSLContext;59import javax.net.ssl.SSLSession;6061public class HandlerConnectionClose62{63static final int ONEK = 1024;64static final long POST_SIZE = ONEK * 1L;65SSLContext sslContext;66Logger logger;6768void test(String[] args) throws Exception {6970HttpServer httpServer = startHttpServer("http");71try {72testHttpURLConnection(httpServer, "http","/close/legacy/http/chunked");73testHttpURLConnection(httpServer, "http","/close/legacy/http/fixed");74testPlainSocket(httpServer, "http","/close/plain/http/chunked");75testPlainSocket(httpServer, "http","/close/plain/http/fixed");76} finally {77httpServer.stop(0);78}79sslContext = new SimpleSSLContext().get();80HttpServer httpsServer = startHttpServer("https");81try {82testHttpURLConnection(httpsServer, "https","/close/legacy/https/chunked");83testHttpURLConnection(httpsServer, "https","/close/legacy/https/fixed");84testPlainSocket(httpsServer, "https","/close/plain/https/chunked");85testPlainSocket(httpsServer, "https","/close/plain/https/fixed");86} finally{87httpsServer.stop(0);88}89}9091void testHttpURLConnection(HttpServer httpServer, String protocol, String path) throws Exception {92int port = httpServer.getAddress().getPort();93String host = httpServer.getAddress().getHostString();94URL url = new URI(protocol, null, host, port, path, null, null).toURL();95HttpURLConnection uc = (HttpURLConnection) url.openConnection();96if ("https".equalsIgnoreCase(protocol)) {97((HttpsURLConnection)uc).setSSLSocketFactory(sslContext.getSocketFactory());98((HttpsURLConnection)uc).setHostnameVerifier((String hostname, SSLSession session) -> true);99}100uc.setDoOutput(true);101uc.setRequestMethod("POST");102uc.setFixedLengthStreamingMode(POST_SIZE);103OutputStream os = uc.getOutputStream();104105/* create a 1K byte array with data to POST */106byte[] ba = new byte[ONEK];107for (int i = 0; i < ONEK; i++)108ba[i] = (byte) i;109110System.out.println("\n" + uc.getClass().getSimpleName() +": POST " + url + " HTTP/1.1");111long times = POST_SIZE / ONEK;112for (int i = 0; i < times; i++) {113os.write(ba);114}115116os.close();117InputStream is = uc.getInputStream();118int read;119long count = 0;120while ((read = is.read(ba)) != -1) {121for (int i = 0; i < read; i++) {122byte expected = (byte) count++;123if (ba[i] != expected) {124throw new IOException("byte mismatch at "125+ (count - 1) + ": expected " + expected + " got " + ba[i]);126}127}128}129if (count != POST_SIZE) {130throw new IOException("Unexpected length: " + count + " expected " + POST_SIZE);131}132is.close();133134pass();135}136137void testPlainSocket(HttpServer httpServer, String protocol, String path) throws Exception {138int port = httpServer.getAddress().getPort();139String host = httpServer.getAddress().getHostString();140URL url = new URI(protocol, null, host, port, path, null, null).toURL();141Socket socket;142if ("https".equalsIgnoreCase(protocol)) {143socket = sslContext.getSocketFactory().createSocket(host, port);144} else {145socket = new Socket(host, port);146}147try (Socket sock = socket) {148OutputStream os = socket.getOutputStream();149150// send request headers151String request = new StringBuilder()152.append("POST ").append(path).append(" HTTP/1.1").append("\r\n")153.append("host: ").append(host).append(':').append(port).append("\r\n")154.append("Content-Length: ").append(POST_SIZE).append("\r\n")155.append("\r\n")156.toString();157os.write(request.getBytes(StandardCharsets.US_ASCII));158159/* create a 1K byte array with data to POST */160byte[] ba = new byte[ONEK];161for (int i = 0; i < ONEK; i++)162ba[i] = (byte) i;163164// send request data165long times = POST_SIZE / ONEK;166for (int i = 0; i < times; i++) {167os.write(ba);168}169os.flush();170171InputStream is = socket.getInputStream();172ByteArrayOutputStream bos = new ByteArrayOutputStream();173174// read all response headers175int c;176int crlf = 0;177while ((c = is.read()) != -1) {178if (c == '\r') continue;179if (c == '\n') crlf++;180else crlf = 0;181bos.write(c);182if (crlf == 2) break;183}184String responseHeadersStr = bos.toString(StandardCharsets.US_ASCII);185List<String> responseHeaders = List.of(responseHeadersStr.split("\n"));186System.out.println("\nPOST " + url + " HTTP/1.1");187responseHeaders.stream().forEach(s -> System.out.println("[reply]\t" + s));188String statusLine = responseHeaders.get(0);189if (!statusLine.startsWith("HTTP/1.1 200 "))190throw new IOException("Unexpected status: " + statusLine);191String cl = responseHeaders.stream()192.map(s -> s.toLowerCase(Locale.ROOT))193.filter(s -> s.startsWith("content-length: "))194.findFirst()195.orElse(null);196String te = responseHeaders.stream()197.map(s -> s.toLowerCase(Locale.ROOT))198.filter(s -> s.startsWith("transfer-encoding: "))199.findFirst()200.orElse(null);201202// check content-length and transfer-encoding are as expected203int read = 0;204long count = 0;205if (path.endsWith("/fixed")) {206if (!("content-length: " + POST_SIZE).equalsIgnoreCase(cl)) {207throw new IOException("Unexpected Content-Length: [" + cl + "]");208}209if (te != null) {210throw new IOException("Unexpected Transfer-Encoding: [" + te + "]");211}212// Got expected Content-Length: 1024 - read response data213while ((read = is.read()) != -1) {214int expected = (int) (count & 0xFF);215if ((read & 0xFF) != expected) {216throw new IOException("byte mismatch at "217+ (count - 1) + ": expected " + expected + " got " + read);218}219if (++count == POST_SIZE) break;220}221} else if (cl != null) {222throw new IOException("Unexpected Content-Length: [" + cl + "]");223} else {224if (!("transfer-encoding: chunked").equalsIgnoreCase(te)) {225throw new IOException("Unexpected Transfer-Encoding: [" + te + "]");226}227// This is a quick & dirty implementation of228// chunk decoding - no trailers - no extensions229StringBuilder chunks = new StringBuilder();230int cs = -1;231while (cs != 0) {232cs = 0;233chunks.setLength(0);234235// read next chunk length236while ((read = is.read()) != -1) {237if (read == '\r') continue;238if (read == '\n') break;239chunks.append((char) read);240}241cs = Integer.parseInt(chunks.toString().trim(), 16);242System.out.println("Got chunk length: " + cs);243244// If chunk size is 0, then we have read the last chunk.245if (cs == 0) break;246247// Read the chunk data248while (--cs >= 0) {249read = is.read();250if (read == -1) break; // EOF251int expected = (int) (count & 0xFF);252if ((read & 0xFF) != expected) {253throw new IOException("byte mismatch at "254+ (count - 1) + ": expected " + expected + " got " + read);255}256// This is cheating: we know the size :-)257if (++count == POST_SIZE) break;258}259260if (read == -1) {261throw new IOException("Unexpected EOF after " + count + " data bytes");262}263264// read CRLF265if ((read = is.read()) != '\r') {266throw new IOException("Expected CR at " + count + "after chunk data - got " + read);267}268if ((read = is.read()) != '\n') {269throw new IOException("Expected LF at " + count + "after chunk data - got " + read);270}271272if (cs == 0 && count == POST_SIZE) {273cs = -1;274}275276if (cs != -1) {277// count == POST_SIZE, but some chunk data still to be read?278throw new IOException("Unexpected chunk size, "279+ cs + " bytes still to read after " + count +280" data bytes received.");281}282}283// Last CRLF?284for (int i = 0; i < 2; i++) {285if ((read = is.read()) == -1) break;286}287}288289if (count != POST_SIZE) {290throw new IOException("Unexpected length: " + count + " expected " + POST_SIZE);291}292293if (!sock.isClosed()) {294try {295// We send an end request to the server to verify that the296// connection is closed. If the server has not closed the297// connection, it will reply. If we receive a response,298// we should fail...299String endrequest = new StringBuilder()300.append("GET ").append("/close/end").append(" HTTP/1.1").append("\r\n")301.append("host: ").append(host).append(':').append(port).append("\r\n")302.append("Content-Length: ").append(0).append("\r\n")303.append("\r\n")304.toString();305os.write(endrequest.getBytes(StandardCharsets.US_ASCII));306os.flush();307StringBuilder resp = new StringBuilder();308crlf = 0;309310// read all headers.311// If the server closed the connection as expected312// we should immediately read EOF313while ((read = is.read()) != -1) {314if (read == '\r') continue;315if (read == '\n') crlf++;316else crlf = 0;317if (crlf == 2) break;318resp.append((char) read);319}320321List<String> lines = List.of(resp.toString().split("\n"));322if (read != -1 || resp.length() != 0) {323System.err.println("Connection not closed!");324System.err.println("Got: ");325lines.stream().forEach(s -> System.err.println("[end]\t" + s));326throw new AssertionError("EOF not received after " + count + " data bytes");327}328if (read != -1) {329throw new AssertionError("EOF was expected after " + count + " bytes, but got: " + read);330} else {331System.out.println("Got expected EOF (" + read + ")");332}333} catch (IOException x) {334// expected! all is well335System.out.println("Socket closed as expected, got exception writing to it.");336}337} else {338System.out.println("Socket closed as expected");339}340pass();341}342}343344/**345* Http Server346*/347HttpServer startHttpServer(String protocol) throws IOException {348if (debug) {349logger = Logger.getLogger("com.sun.net.httpserver");350Handler outHandler = new StreamHandler(System.out,351new SimpleFormatter());352outHandler.setLevel(Level.FINEST);353logger.setLevel(Level.FINEST);354logger.addHandler(outHandler);355}356357InetSocketAddress serverAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);358HttpServer httpServer = null;359if ("http".equalsIgnoreCase(protocol)) {360httpServer = HttpServer.create(serverAddress, 0);361}362if ("https".equalsIgnoreCase(protocol)) {363HttpsServer httpsServer = HttpsServer.create(serverAddress, 0);364httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));365httpServer = httpsServer;366}367httpServer.createContext("/close/", new MyHandler(POST_SIZE));368System.out.println("Server created at: " + httpServer.getAddress());369httpServer.start();370return httpServer;371}372373class MyHandler implements HttpHandler {374static final int BUFFER_SIZE = 512;375final long expected;376377MyHandler(long expected){378this.expected = expected;379}380381@Override382public void handle(HttpExchange t) throws IOException {383System.out.println("Server: serving " + t.getRequestURI());384boolean chunked = t.getRequestURI().getPath().endsWith("/chunked");385boolean fixed = t.getRequestURI().getPath().endsWith("/fixed");386boolean end = t.getRequestURI().getPath().endsWith("/end");387long responseLength = fixed ? POST_SIZE : 0;388responseLength = end ? -1 : responseLength;389responseLength = chunked ? 0 : responseLength;390391if (!end) t.getResponseHeaders().add("connection", "CLose");392t.sendResponseHeaders(200, responseLength);393394if (!end) {395OutputStream os = t.getResponseBody();396InputStream is = t.getRequestBody();397byte[] ba = new byte[BUFFER_SIZE];398int read;399long count = 0L;400while ((read = is.read(ba)) != -1) {401count += read;402os.write(ba, 0, read);403}404is.close();405406check(count == expected, "Expected: " + expected + ", received "407+ count);408debug("Received " + count + " bytes");409os.close();410}411412t.close();413}414}415416//--------------------- Infrastructure ---------------------------417boolean debug = true;418volatile int passed = 0, failed = 0;419void pass() {passed++;}420void fail() {failed++; Thread.dumpStack();}421void fail(String msg) {System.err.println(msg); fail();}422void unexpected(Throwable t) {failed++; t.printStackTrace();}423void check(boolean cond) {if (cond) pass(); else fail();}424void check(boolean cond, String failMessage) {if (cond) pass(); else fail(failMessage);}425void debug(String message) {if(debug) { System.out.println(message); } }426public static void main(String[] args) throws Throwable {427Class<?> k = new Object(){}.getClass().getEnclosingClass();428try {k.getMethod("instanceMain",String[].class)429.invoke( k.newInstance(), (Object) args);}430catch (Throwable e) {throw e.getCause();}}431public void instanceMain(String[] args) throws Throwable {432try {test(args);} catch (Throwable t) {unexpected(t);}433System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);434if (failed > 0) throw new AssertionError("Some tests failed");}435436}437438439