6726695: HttpURLConnection shoul support 'Expect: 100-contimue' headers for PUT
Summary: Added code triggered when 'Expect: 100-continue' header has been added
Reviewed-by: chegar
--- a/jdk/src/share/classes/sun/net/www/http/HttpClient.java Tue May 26 16:43:22 2009 +0800
+++ b/jdk/src/share/classes/sun/net/www/http/HttpClient.java Tue May 26 16:03:51 2009 +0200
@@ -27,10 +27,8 @@
import java.io.*;
import java.net.*;
-import java.util.*;
import sun.net.NetworkClient;
import sun.net.ProgressSource;
-import sun.net.ProgressMonitor;
import sun.net.www.MessageHeader;
import sun.net.www.HeaderParser;
import sun.net.www.MeteredStream;
@@ -38,7 +36,6 @@
import sun.net.www.protocol.http.HttpURLConnection;
import sun.misc.RegexpPool;
-import java.security.*;
/**
* @author Herb Jellinek
* @author Dave Brown
@@ -60,16 +57,8 @@
// if we've had one io error
boolean failedOnce = false;
- /** regexp pool of hosts for which we should connect directly, not Proxy
- * these are intialized from a property.
- */
- private static RegexpPool nonProxyHostsPool = null;
-
- /** The string source of nonProxyHostsPool
- */
- private static String nonProxyHostsSource = null;
-
/** Response code for CONTINUE */
+ private boolean ignoreContinue = true;
private static final int HTTP_CONTINUE = 100;
/** Default port number for http daemons. REMIND: make these private */
@@ -610,7 +599,10 @@
return (parseHTTPHeader(responses, pi, httpuc));
} catch (SocketTimeoutException stex) {
// We don't want to retry the request when the app. sets a timeout
- closeServer();
+ // but don't close the server if timeout while waiting for 100-continue
+ if (ignoreContinue) {
+ closeServer();
+ }
throw stex;
} catch (IOException e) {
closeServer();
@@ -635,12 +627,6 @@
}
- public int setTimeout (int timeout) throws SocketException {
- int old = serverSocket.getSoTimeout ();
- serverSocket.setSoTimeout (timeout);
- return old;
- }
-
private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
throws IOException {
/* If "HTTP/*" is found in the beginning, return true. Let
@@ -768,7 +754,7 @@
code = Integer.parseInt(resp.substring(ind, ind + 3));
} catch (Exception e) {}
- if (code == HTTP_CONTINUE) {
+ if (code == HTTP_CONTINUE && ignoreContinue) {
responses.reset();
return parseHTTPHeader(responses, pi, httpuc);
}
@@ -893,6 +879,7 @@
return serverOutput;
}
+ @Override
public String toString() {
return getClass().getName()+"("+url+")";
}
@@ -909,6 +896,7 @@
return cacheRequest;
}
+ @Override
protected void finalize() throws Throwable {
// This should do nothing. The stream finalizer will
// close the fd.
@@ -919,8 +907,12 @@
failedOnce = value;
}
+ public void setIgnoreContinue(boolean value) {
+ ignoreContinue = value;
+ }
/* Use only on connections in error. */
+ @Override
public void closeServer() {
try {
keepingAlive = false;
--- a/jdk/src/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java Tue May 26 16:43:22 2009 +0800
+++ b/jdk/src/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java Tue May 26 16:03:51 2009 +0200
@@ -105,7 +105,8 @@
HttpClient hc = kace.getHttpClient();
try {
if (hc != null && !hc.isInKeepAliveCache()) {
- int oldTimeout = hc.setTimeout(TIMEOUT);
+ int oldTimeout = hc.getReadTimeout();
+ hc.setReadTimeout(TIMEOUT);
long remainingToRead = kas.remainingToRead();
if (remainingToRead > 0) {
long n = 0;
@@ -119,7 +120,7 @@
remainingToRead = remainingToRead - n;
}
if (remainingToRead == 0) {
- hc.setTimeout(oldTimeout);
+ hc.setReadTimeout(oldTimeout);
hc.finished();
} else
hc.closeServer();
--- a/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Tue May 26 16:43:22 2009 +0800
+++ b/jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java Tue May 26 16:03:51 2009 +0200
@@ -51,7 +51,6 @@
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.Iterator;
-import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import sun.net.*;
@@ -64,7 +63,6 @@
import java.util.TimeZone;
import java.net.MalformedURLException;
import java.nio.ByteBuffer;
-import java.lang.reflect.*;
/**
* A class to represent an HTTP connection to a remote object.
@@ -829,6 +827,56 @@
return HttpClient.New(url, p, connectTimeout, useCache);
}
+ private void expect100Continue() throws IOException {
+ // Expect: 100-Continue was set, so check the return code for
+ // Acceptance
+ int oldTimeout = http.getReadTimeout();
+ boolean enforceTimeOut = false;
+ boolean timedOut = false;
+ if (oldTimeout <= 0) {
+ // 5s read timeout in case the server doesn't understand
+ // Expect: 100-Continue
+ http.setReadTimeout(5000);
+ enforceTimeOut = true;
+ }
+
+ try {
+ http.parseHTTP(responses, pi, this);
+ } catch (SocketTimeoutException se) {
+ if (!enforceTimeOut) {
+ throw se;
+ }
+ timedOut = true;
+ http.setIgnoreContinue(true);
+ }
+ if (!timedOut) {
+ // Can't use getResponseCode() yet
+ String resp = responses.getValue(0);
+ // Parse the response which is of the form:
+ // HTTP/1.1 417 Expectation Failed
+ // HTTP/1.1 100 Continue
+ if (resp != null && resp.startsWith("HTTP/")) {
+ String[] sa = resp.split("\\s+");
+ responseCode = -1;
+ try {
+ // Response code is 2nd token on the line
+ if (sa.length > 1)
+ responseCode = Integer.parseInt(sa[1]);
+ } catch (NumberFormatException numberFormatException) {
+ }
+ }
+ if (responseCode != 100) {
+ throw new ProtocolException("Server rejected operation");
+ }
+ }
+ if (oldTimeout > 0) {
+ http.setReadTimeout(oldTimeout);
+ }
+ responseCode = -1;
+ responses.reset();
+ // Proceed
+ }
+
/*
* Allowable input/output sequences:
* [interpreted as POST/PUT]
@@ -866,14 +914,20 @@
if (!checkReuseConnection())
connect();
- /* REMIND: This exists to fix the HttpsURLConnection subclass.
- * Hotjava needs to run on JDK1.1FCS. Do proper fix in subclass
- * for 1.2 and remove this.
- */
+ boolean expectContinue = false;
+ String expects = requests.findValue("Expect");
+ if ("100-Continue".equalsIgnoreCase(expects)) {
+ http.setIgnoreContinue(false);
+ expectContinue = true;
+ }
if (streaming() && strOutputStream == null) {
writeRequests();
}
+
+ if (expectContinue) {
+ expect100Continue();
+ }
ps = (PrintStream)http.getOutputStream();
if (streaming()) {
if (strOutputStream == null) {
@@ -900,6 +954,13 @@
} catch (RuntimeException e) {
disconnectInternal();
throw e;
+ } catch (ProtocolException e) {
+ // Save the response code which may have been set while enforcing
+ // the 100-continue. disconnectInternal() forces it to -1
+ int i = responseCode;
+ disconnectInternal();
+ responseCode = i;
+ throw e;
} catch (IOException e) {
disconnectInternal();
throw e;
@@ -2752,7 +2813,8 @@
try {
// set SO_TIMEOUT to 1/5th of the total timeout
// remember the old timeout value so that we can restore it
- int oldTimeout = http.setTimeout(timeout4ESBuffer/5);
+ int oldTimeout = http.getReadTimeout();
+ http.setReadTimeout(timeout4ESBuffer/5);
long expected = 0;
boolean isChunked = false;
@@ -2790,7 +2852,7 @@
} while (count < exp && time < timeout4ESBuffer);
// reset SO_TIMEOUT to old value
- http.setTimeout(oldTimeout);
+ http.setReadTimeout(oldTimeout);
// if count < cl at this point, we will not try to reuse
// the connection
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/net/www/http/HttpClient/B6726695.java Tue May 26 16:03:51 2009 +0200
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6726695
+ * @summary HttpURLConnection shoul support 'Expect: 100-contimue' headers for PUT
+*/
+
+import java.net.*;
+import java.io.*;
+
+public class B6726695 extends Thread {
+ private ServerSocket server = null;
+ private int port = 0;
+ private byte[] data = new byte[512];
+ private String boundary = "----------------7774563516523621";
+
+ public static void main(String[] args) throws Exception {
+ B6726695 test = new B6726695();
+ // Exit even if server is still running
+ test.setDaemon(true);
+ // start server
+ test.start();
+ // run test
+ test.test();
+ }
+
+ public B6726695() {
+ try {
+ server = new ServerSocket(0);
+ port = server.getLocalPort();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void test() throws Exception {
+ /**
+ * This is a hardcoded test. The server side expects 3 requests with a
+ * Expect: 100-continue header. It will reject the 1st one and accept
+ * the second one. Thus allowing us to test both scenarios.
+ * The 3rd case is the simulation of a server that just plains ignore
+ * the Expect: 100-Continue header. So the POST should proceed after
+ * a timeout.
+ */
+ URL url = new URL("http://localhost:" + port + "/foo");
+
+ // 1st Connection. Should be rejected. I.E. get a ProtocolException
+ URLConnection con = url.openConnection();
+ HttpURLConnection http = (HttpURLConnection) con;
+ http.setRequestMethod("POST");
+ http.setRequestProperty("Expect", "100-Continue");
+ http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+ http.setDoOutput(true);
+ http.setFixedLengthStreamingMode(512);
+ OutputStream out = null;
+ int errorCode = -1;
+ try {
+ out = http.getOutputStream();
+ } catch (ProtocolException e) {
+ errorCode = http.getResponseCode();
+ }
+ if (errorCode != 417) {
+ throw new RuntimeException("Didn't get the ProtocolException");
+ }
+
+ // 2nd connection. Should be accepted by server.
+ http = (HttpURLConnection) url.openConnection();
+ http.setRequestMethod("POST");
+ http.setRequestProperty("Expect", "100-Continue");
+ http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+ http.setDoOutput(true);
+ http.setFixedLengthStreamingMode(data.length);
+ out = null;
+ try {
+ out = http.getOutputStream();
+ } catch (ProtocolException e) {
+ }
+ if (out == null) {
+ throw new RuntimeException("Didn't get an OutputStream");
+ }
+ out.write(data);
+ out.flush();
+ errorCode = http.getResponseCode();
+ if (errorCode != 200) {
+ throw new RuntimeException("Response code is " + errorCode);
+ }
+ out.close();
+
+ // 3rd connection. Simulate a server that doesn't implement 100-continue
+ http = (HttpURLConnection) url.openConnection();
+ http.setRequestMethod("POST");
+ http.setRequestProperty("Expect", "100-Continue");
+ http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
+ http.setDoOutput(true);
+ http.setFixedLengthStreamingMode(data.length);
+ out = null;
+ try {
+ out = http.getOutputStream();
+ } catch (ProtocolException e) {
+ }
+ if (out == null) {
+ throw new RuntimeException("Didn't get an OutputStream");
+ }
+ out.write(data);
+ out.flush();
+ out.close();
+ errorCode = http.getResponseCode();
+ if (errorCode != 200) {
+ throw new RuntimeException("Response code is " + errorCode);
+ }
+ }
+
+
+ @Override
+ public void run() {
+ try {
+ // Fist connection: don't accetpt the request
+ Socket s = server.accept();
+ serverReject(s);
+ // Second connection: accept the request (send 100-continue)
+ s = server.accept();
+ serverAccept(s);
+ // 3rd connection: just ignore the 'Expect:' header
+ s = server.accept();
+ serverIgnore(s);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void serverReject(Socket s) throws IOException {
+ BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
+ PrintStream out = new PrintStream(new BufferedOutputStream(s.getOutputStream()));
+ String line = null;
+ do {
+ line = in.readLine();
+ } while (line != null && line.length() != 0);
+
+ out.print("HTTP/1.1 417 Expectation Failed\r\n");
+ out.print("Server: Sun-Java-System-Web-Server/7.0\r\n");
+ out.print("Connection: close\r\n");
+ out.print("Content-Length: 0\r\n");
+ out.print("\r\n");
+ out.flush();
+ out.close();
+ in.close();
+ }
+
+ public void serverAccept(Socket s) throws IOException {
+ BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
+ PrintStream out = new PrintStream(new BufferedOutputStream(s.getOutputStream()));
+ String line = null;
+ do {
+ line = in.readLine();
+ } while (line != null && line.length() != 0);
+
+ // Send 100-Continue
+ out.print("HTTP/1.1 100 Continue\r\n");
+ out.print("\r\n");
+ out.flush();
+ // Then read the body
+ char[] cbuf = new char[512];
+ int l = in.read(cbuf);
+ // finally send the 200 OK
+ out.print("HTTP/1.1 200 OK");
+ out.print("Server: Sun-Java-System-Web-Server/7.0\r\n");
+ out.print("Connection: close\r\n");
+ out.print("Content-Length: 0\r\n");
+ out.print("\r\n");
+ out.flush();
+ out.close();
+ in.close();
+ }
+
+ public void serverIgnore(Socket s) throws IOException {
+ BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
+ PrintStream out = new PrintStream(new BufferedOutputStream(s.getOutputStream()));
+ String line = null;
+ do {
+ line = in.readLine();
+ } while (line != null && line.length() != 0);
+
+ // Then read the body
+ char[] cbuf = new char[512];
+ int l = in.read(cbuf);
+ // finally send the 200 OK
+ out.print("HTTP/1.1 200 OK");
+ out.print("Server: Sun-Java-System-Web-Server/7.0\r\n");
+ out.print("Content-Length: 0\r\n");
+ out.print("Connection: close\r\n");
+ out.print("\r\n");
+ out.flush();
+ out.close();
+ in.close();
+ }
+}