6726695: HttpURLConnection shoul support 'Expect: 100-contimue' headers for PUT
authorjccollet
Tue, 26 May 2009 16:03:51 +0200
changeset 2928 80b0b6c2d527
parent 2927 7780913dc7d6
child 2929 8de202ca71c3
child 2930 58a6440b92dc
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
jdk/src/share/classes/sun/net/www/http/HttpClient.java
jdk/src/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java
jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java
jdk/test/sun/net/www/http/HttpClient/B6726695.java
--- 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();
+    }
+}