Connecting via HTTP with the java.net Classes
47: osw.write(msg);
48: /** REMEMBER THIS osw.flush(); **/ 49: osw.flush();
50: osw.close(); 51:
52: System.out.println(“After flushing output stream. “); 53:
54: // any response?
55: InputStreamReader isr = new InputStreamReader(is); 56: BufferedReader br = new BufferedReader(isr); 57: String line = null;
58:
59: while ( (line = br.readLine()) != null) 60: { 61: System.out.println(“line: “ + line); 62: } 63: } catch (Throwable t) 64: { 65: t.printStackTrace(); 66: } 67: } 68: } Listing 17.2 (continued)
A run of Listing 17.2 produces the following:
E:\classes\org\javapitfalls\Item17>java
org.javapitfalls.item17.BadURLPost http://localhost/cgi-bin/echocgi.exe Æ Received a : sun.net.www.protocol.http.HttpURLConnection
Getting an input stream... Getting an output stream...
java.net.ProtocolException: Cannot write output after reading input.
at
sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLCo nnection.java:507)
at
com.javaworld.jpitfalls.article3.BadURLPost.main(BadURLPost.java:39)
When trying to get the output stream of the HttpURLConnectionclass, the pro- gram informed me that I cannot write output after reading input. The strange thing about this error message is that we have not tried to read any data yet. Of course, that assumes the getInputStream()method behaves in the same manner as in other IO classes. Specifically, there are three problems with the above code:
■■ The setRequestProperty()method parameters are not checked. This is demonstrated by setting a property called “stupid” with a value of “non- sense.” Since these properties actually go into the HTTP request and they are not validated by the method (as they should be), you must be extra careful to ensure the parameter names and values are correct.
■■ The getOutputStream() method causes the program to throw a
ProtocolExceptionwith the error message “Can’t write output after read- ing input.” By examining the JDK source code, we find that this is due to the
getInputStream()method having the side effect of sending the request (whose default request method is “GET”) to the Web server. As an aside, this is similar to a side effect in the ObjectInputStreamand ObjectOutput- Streamconstructors that are detailed in my first pitfalls book. So, the pitfall is the assumption that the getInputStream()and getOutputStream()meth- ods behave just like they do for a Socketconnection. Since the underlying mechanism for communicating to the Web server actually is a socket, this is not an unreasonable assumption. A better implementation of HttpURLConnec- tionwould be to postpone the side effects until the initial read or write to the respective input or output stream. This could be done by creating an
HttpInputStreamand HttpOutputStream. That would keep the socket metaphor intact. One could argue that HTTP is a request/response stateless protocol and the socket metaphor does not fit. The answer to that is that the API should fit the conceptual model. If the current model is identical to a socket connection, it should behave as such. If it does not, you have stretched the bounds of abstraction too far.
■■ Although it is commented out, it is also illegal to attempt to set a request property after getting an input or output stream. The documentation for
URLConnectiondoes state the sequence to set up a connection, although it does not state this is a mandatory sequence.
If we did not have the luxury of examining the source code (which definitely should not be a requirement to use an API), we would be reduced to trial and error (the absolute worst way to program). Neither the documentation nor the API of the
HttpURLConnectionclass afford us any understanding of how the protocol is imple- mented, so we feebly attempt to reverse the order of calls to getInputStream() and
getOutputStream(). Listing 17.3, BadURLPost1.java, is an abbreviated version of that program. 01: package org.javapitfalls.item17; 02: 03: import java.net.*; 04: import java.io.*; 05:
06: public class BadURLPost1 07: {
08: public static void main(String args[]) 09: {
// removed for brevity
35: System.out.println(“Getting an output stream...”); 36: OutputStream os = con.getOutputStream();
37:
38: System.out.println(“Getting an input stream...”); 39: InputStream is = con.getInputStream();
// removed for brevity 67: }
68: }
Listing 17.3 BadURLPost1.java
A run of Listing 17.3 produces the following:
E:\classes\org\javapitfalls\Item17>java org.javapitfalls. Æ item17.BadURLPost1 http://localhost/cgi-bin/echocgi.exe
Received a : sun.net.www.protocol.http.HttpURLConnection Getting an output stream...
Getting an input stream... After flushing output stream. line: <HEAD>
line: <TITLE> Echo CGI program </TITLE> line: </HEAD>
line: <H2> Echo </H2> line: </CENTER>
line: No content! ERROR!
line: </BODY> line: </HTML>
Although the program compiles and runs, the CGI program reports that no data was sent! Why? Again we were bitten by the side effects of getInputStream(), which caused the POST request to be sent before anything was put in the post’s output buffer, thus sending an empty post request.
Now, after having failed twice, we understand that the getInputStream()is the key method that actually writes the requests to the server. Therefore, we must perform the operations serially (open output, write, open input, read) as we do in Listing 17.4, GoodURLPost.java. 01: package org.javapitfalls.item17; 02: 03: import java.net.*; 04: import java.io.*; 05:
06: public class GoodURLPost 07: {
08: public static void main(String args[]) 09: {
10: // get an HTTP connection to POST to 11: if (args.length < 1) 12: { 13: System.out.println(“USAGE: java Æ GOV.dia.mditds.util.GoodURLPost url”); 14: System.exit(1); 15: } 16: 17: try 18: {
19: // get the url as a string 20: String surl = args[0]; 21: URL url = new URL(surl); 22:
23: URLConnection con = url.openConnection();
24: System.out.println(“Received a : “ + Æ con.getClass().getName()); 25: 26: con.setDoInput(true); 27: con.setDoOutput(true); 28: con.setUseCaches(false); 29:
30: String msg = “Hi HTTP SERVER! Just a quick hello!”;
31: con.setRequestProperty(“CONTENT_LENGTH”, “” + Æ msg.length()); // Not checked
32: System.out.println(“Msg Length: “ + msg.length()); 33:
34: System.out.println(“Getting an output stream...”);
35: OutputStream os = con.getOutputStream();
36:
37: OutputStreamWriter osw = new OutputStreamWriter(os);
38: osw.write(msg);
39: /** REMEMBER THIS osw.flush(); **/ 40: osw.flush();
41: osw.close(); 42:
43: System.out.println(“After flushing output stream. “); 44:
45: System.out.println(“Getting an input stream...”);
46: InputStream is = con.getInputStream();
47:
48: // any response?
49: InputStreamReader isr = new InputStreamReader(is); 50: BufferedReader br = new BufferedReader(isr); 51: String line = null;
52: