# HG changeset patch # User jccollet # Date 1243346631 -7200 # Node ID 80b0b6c2d5279102f192ca90982d27ed3bf46d8c # Parent 7780913dc7d63f2013f3fb892a2c54a51bea8e99 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 diff -r 7780913dc7d6 -r 80b0b6c2d527 jdk/src/share/classes/sun/net/www/http/HttpClient.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; diff -r 7780913dc7d6 -r 80b0b6c2d527 jdk/src/share/classes/sun/net/www/http/KeepAliveStreamCleaner.java --- 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(); diff -r 7780913dc7d6 -r 80b0b6c2d527 jdk/src/share/classes/sun/net/www/protocol/http/HttpURLConnection.java --- 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 diff -r 7780913dc7d6 -r 80b0b6c2d527 jdk/test/sun/net/www/http/HttpClient/B6726695.java --- /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(); + } +}