8011719: Properties.loadFromXML fails with a chunked HTTP connection
Reviewed-by: michaelm
--- a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Wed Jun 05 18:20:34 2013 +0400
+++ b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Wed Jun 05 16:06:28 2013 +0100
@@ -3158,6 +3158,7 @@
private boolean marked = false;
private int inCache = 0;
private int markCount = 0;
+ private boolean closed; // false
public HttpInputStream (InputStream is) {
super (is);
@@ -3233,8 +3234,14 @@
}
}
+ private void ensureOpen() throws IOException {
+ if (closed)
+ throw new IOException("stream is closed");
+ }
+
@Override
public int read() throws IOException {
+ ensureOpen();
try {
byte[] b = new byte[1];
int ret = read(b);
@@ -3254,6 +3261,7 @@
@Override
public int read(byte[] b, int off, int len) throws IOException {
+ ensureOpen();
try {
int newLen = super.read(b, off, len);
int nWrite;
@@ -3291,7 +3299,7 @@
@Override
public long skip (long n) throws IOException {
-
+ ensureOpen();
long remaining = n;
int nr;
if (skipBuffer == null)
@@ -3317,6 +3325,9 @@
@Override
public void close () throws IOException {
+ if (closed)
+ return;
+
try {
if (outputStream != null) {
if (read() != -1) {
@@ -3332,6 +3343,7 @@
}
throw ioex;
} finally {
+ closed = true;
HttpURLConnection.this.http = null;
checkResponseCredentials (true);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/net/www/protocol/http/HttpStreams.java Wed Jun 05 16:06:28 2013 +0100
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2013, 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 8011719
+ * @summary Basic checks to verify behavior of returned input streams
+ */
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.*;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+public class HttpStreams {
+
+ void client(String u) throws Exception {
+ byte[] ba = new byte[5];
+ HttpURLConnection urlc = (HttpURLConnection)(new URL(u)).openConnection();
+ int resp = urlc.getResponseCode();
+ InputStream is;
+ if (resp == 200)
+ is = urlc.getInputStream();
+ else
+ is = urlc.getErrorStream();
+
+ expectNoThrow(() -> { is.read(); }, "read on open stream should not throw :" + u);
+ expectNoThrow(() -> { is.close(); }, "close should never throw: " + u);
+ expectNoThrow(() -> { is.close(); }, "close should never throw: " + u);
+ expectThrow(() -> { is.read(); }, "read on closed stream should throw: " + u);
+ expectThrow(() -> { is.read(ba); }, "read on closed stream should throw: " + u);
+ expectThrow(() -> { is.read(ba, 0, 2); }, "read on closed stream should throw: " + u);
+ }
+
+ void test() throws Exception {
+ HttpServer server = null;
+ try {
+ server = startHttpServer();
+ String baseUrl = "http://localhost:" + server.getAddress().getPort() + "/";
+ client(baseUrl + "chunked/");
+ client(baseUrl + "fixed/");
+ client(baseUrl + "error/");
+ client(baseUrl + "chunkedError/");
+
+ // Test with a response cache
+ ResponseCache ch = ResponseCache.getDefault();
+ ResponseCache.setDefault(new TrivialCacheHandler());
+ try {
+ client(baseUrl + "chunked/");
+ client(baseUrl + "fixed/");
+ client(baseUrl + "error/");
+ client(baseUrl + "chunkedError/");
+ } finally {
+ ResponseCache.setDefault(ch);
+ }
+ } finally {
+ if (server != null)
+ server.stop(0);
+ }
+
+ System.out.println("passed: " + pass + ", failed: " + fail);
+ if (fail > 0)
+ throw new RuntimeException("some tests failed check output");
+ }
+
+ public static void main(String[] args) throws Exception {
+ (new HttpStreams()).test();
+ }
+
+ // HTTP Server
+ HttpServer startHttpServer() throws IOException {
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(0), 0);
+ httpServer.createContext("/chunked/", new ChunkedHandler());
+ httpServer.createContext("/fixed/", new FixedHandler());
+ httpServer.createContext("/error/", new ErrorHandler());
+ httpServer.createContext("/chunkedError/", new ChunkedErrorHandler());
+ httpServer.start();
+ return httpServer;
+ }
+
+ static abstract class AbstractHandler implements HttpHandler {
+ @Override
+ public void handle(HttpExchange t) throws IOException {
+ try (InputStream is = t.getRequestBody()) {
+ while (is.read() != -1);
+ }
+ t.sendResponseHeaders(respCode(), length());
+ try (OutputStream os = t.getResponseBody()) {
+ os.write(message());
+ }
+ t.close();
+ }
+
+ abstract int respCode();
+ abstract int length();
+ abstract byte[] message();
+ }
+
+ static class ChunkedHandler extends AbstractHandler {
+ static final byte[] ba =
+ "Hello there from chunked handler!".getBytes(StandardCharsets.US_ASCII);
+ int respCode() { return 200; }
+ int length() { return 0; }
+ byte[] message() { return ba; }
+ }
+
+ static class FixedHandler extends AbstractHandler {
+ static final byte[] ba =
+ "Hello there from fixed handler!".getBytes(StandardCharsets.US_ASCII);
+ int respCode() { return 200; }
+ int length() { return ba.length; }
+ byte[] message() { return ba; }
+ }
+
+ static class ErrorHandler extends AbstractHandler {
+ static final byte[] ba =
+ "This is an error mesg from the server!".getBytes(StandardCharsets.US_ASCII);
+ int respCode() { return 400; }
+ int length() { return ba.length; }
+ byte[] message() { return ba; }
+ }
+
+ static class ChunkedErrorHandler extends ErrorHandler {
+ int length() { return 0; }
+ }
+
+ static class TrivialCacheHandler extends ResponseCache
+ {
+ public CacheResponse get(URI uri, String rqstMethod, Map rqstHeaders) {
+ return null;
+ }
+
+ public CacheRequest put(URI uri, URLConnection conn) {
+ return new TrivialCacheRequest();
+ }
+ }
+
+ static class TrivialCacheRequest extends CacheRequest
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ public void abort() {}
+ public OutputStream getBody() throws IOException { return baos; }
+ }
+
+ static interface ThrowableRunnable {
+ void run() throws IOException;
+ }
+
+ void expectThrow(ThrowableRunnable r, String msg) {
+ try { r.run(); fail(msg); } catch (IOException x) { pass(); }
+ }
+
+ void expectNoThrow(ThrowableRunnable r, String msg) {
+ try { r.run(); pass(); } catch (IOException x) { fail(msg, x); }
+ }
+
+ private int pass;
+ private int fail;
+ void pass() { pass++; }
+ void fail(String msg, Exception x) { System.out.println(msg); x.printStackTrace(); fail++; }
+ void fail(String msg) { System.out.println(msg); Thread.dumpStack(); fail++; }
+}