001// Copyright (C) 1998-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
002// All rights reserved.  Use of this class is limited.
003// Please see the LICENSE for more information.
004
005package com.oreilly.servlet;
006
007import java.io.*;
008import java.net.*;
009import java.util.*;
010
011/** 
012 * A class to simplify HTTP applet-server communication.  It abstracts
013 * the communication into messages, which can be either GET or POST.
014 * <p>
015 * It can be used like this:
016 * <blockquote><pre>
017 * URL url = new URL(getCodeBase(), "/servlet/ServletName");
018 * &nbsp;
019 * HttpMessage msg = new HttpMessage(url);
020 * &nbsp;
021 * // Parameters may optionally be set using java.util.Properties
022 * Properties props = new Properties();
023 * props.put("name", "value");
024 * &nbsp;
025 * // Headers, cookies, and authorization may be set as well
026 * msg.setHeader("Accept", "image/png");             // optional
027 * msg.setCookie("JSESSIONID", "9585155923883872");  // optional
028 * msg.setAuthorization("guest", "try2gueSS");       // optional
029 * &nbsp;
030 * InputStream in = msg.sendGetMessage(props);
031 * </pre></blockquote>
032 * <p>
033 * This class is loosely modeled after the ServletMessage class written 
034 * by Rod McChesney of JavaSoft.
035 *
036 * @author <b>Jason Hunter</b>, Copyright &#169; 1998
037 * @version 1.3, 2000/10/24, fixed headers NPE bug
038 * @version 1.2, 2000/10/15, changed uploaded object MIME type to
039 *                           application/x-java-serialized-object
040 * @version 1.1, 2000/06/11, added ability to set headers, cookies, 
041                             and authorization
042 * @version 1.0, 1998/09/18
043 */
044public class HttpMessage {
045
046  URL servlet = null;
047  Hashtable headers = null;
048
049  /**
050   * Constructs a new HttpMessage that can be used to communicate with the 
051   * servlet at the specified URL.
052   *
053   * @param servlet the server resource (typically a servlet) with which 
054   * to communicate
055   */
056  public HttpMessage(URL servlet) {
057    this.servlet = servlet;
058  }
059
060  /**
061   * Performs a GET request to the servlet, with no query string.
062   *
063   * @return an InputStream to read the response
064   * @exception IOException if an I/O error occurs
065   */
066  public InputStream sendGetMessage() throws IOException {
067    return sendGetMessage(null);
068  }
069
070  /**
071   * Performs a GET request to the servlet, building
072   * a query string from the supplied properties list.
073   *
074   * @param args the properties list from which to build a query string
075   * @return an InputStream to read the response
076   * @exception IOException if an I/O error occurs
077   */
078  public InputStream sendGetMessage(Properties args) throws IOException {
079    String argString = "";  // default
080
081    if (args != null) {
082      argString = "?" + toEncodedString(args);
083    }
084    URL url = new URL(servlet.toExternalForm() + argString); 
085
086    // Turn off caching
087    URLConnection con = url.openConnection();
088    con.setUseCaches(false);
089
090    // Send headers
091    sendHeaders(con);
092
093    return con.getInputStream();
094  }
095
096  /**
097   * Performs a POST request to the servlet, with no query string.
098   *
099   * @return an InputStream to read the response
100   * @exception IOException if an I/O error occurs
101   */
102  public InputStream sendPostMessage() throws IOException {
103    return sendPostMessage(null);
104  }
105
106  /**
107   * Performs a POST request to the servlet, building
108   * post data from the supplied properties list.
109   *
110   * @param args the properties list from which to build the post data
111   * @return an InputStream to read the response
112   * @exception IOException if an I/O error occurs
113   */
114  public InputStream sendPostMessage(Properties args) throws IOException {
115    String argString = "";  // default
116    if (args != null) {
117      argString = toEncodedString(args);  // notice no "?"
118    }
119
120    URLConnection con = servlet.openConnection();
121
122    // Prepare for both input and output
123    con.setDoInput(true);
124    con.setDoOutput(true);
125
126    // Turn off caching
127    con.setUseCaches(false);
128
129    // Work around a Netscape bug
130    con.setRequestProperty("Content-Type",
131                           "application/x-www-form-urlencoded");
132
133    // Send headers
134    sendHeaders(con);
135
136    // Write the arguments as post data
137    DataOutputStream out = new DataOutputStream(con.getOutputStream());
138    out.writeBytes(argString);
139    out.flush();
140    out.close();
141
142    return con.getInputStream();
143  }
144
145  /**
146   * Performs a POST request to the servlet, uploading a serialized object.
147   * <p>
148   * The servlet can receive the object in its <tt>doPost()</tt> method 
149   * like this:
150   * <pre>
151   *     ObjectInputStream objin =
152   *       new ObjectInputStream(req.getInputStream());
153   *     Object obj = objin.readObject();
154   * </pre>
155   * The type of the uploaded object can be determined through introspection.
156   *
157   * @param obj the serializable object to upload
158   * @return an InputStream to read the response
159   * @exception IOException if an I/O error occurs
160   */
161  public InputStream sendPostMessage(Serializable obj) throws IOException {
162    URLConnection con = servlet.openConnection();
163
164    // Prepare for both input and output
165    con.setDoInput(true);
166    con.setDoOutput(true);
167
168    // Turn off caching
169    con.setUseCaches(false);
170
171    // Set the content type to be application/x-java-serialized-object
172    con.setRequestProperty("Content-Type",
173                           "application/x-java-serialized-object");
174
175    // Send headers
176    sendHeaders(con);
177
178    // Write the serialized object as post data
179    ObjectOutputStream out = new ObjectOutputStream(con.getOutputStream());
180    out.writeObject(obj);
181    out.flush();
182    out.close();
183
184    return con.getInputStream();
185  }
186
187  /**
188   * Sets a request header with the given name and value.  The header 
189   * persists across multiple requests.  The caller is responsible for
190   * ensuring there are no illegal characters in the name and value.
191   *
192   * @param name the header name
193   * @param value the header value
194   */
195  public void setHeader(String name, String value) {
196    if (headers == null) {
197      headers = new Hashtable();
198    }
199    headers.put(name, value);
200  }
201
202  // Send the contents of the headers hashtable to the server
203  private void sendHeaders(URLConnection con) {
204    if (headers != null) {
205      Enumeration myEnum = headers.keys();
206      while (myEnum.hasMoreElements()) {
207        String name = (String) myEnum.nextElement();
208        String value = (String) headers.get(name);
209        con.setRequestProperty(name, value);
210      }
211    }
212  }
213
214  /**
215   * Sets a request cookie with the given name and value.  The cookie 
216   * persists across multiple requests.  The caller is responsible for
217   * ensuring there are no illegal characters in the name and value.
218   *
219   * @param name the header name
220   * @param value the header value
221   */
222  public void setCookie(String name, String value) {
223    if (headers == null) {
224      headers = new Hashtable();
225    }
226    String existingCookies = (String) headers.get("Cookie");
227    if (existingCookies == null) {
228      setHeader("Cookie", name + "=" + value);
229    }
230    else {
231      setHeader("Cookie", existingCookies + "; " + name + "=" + value);
232    }
233  }
234
235  /**
236   * Sets the authorization information for the request (using BASIC
237   * authentication via the HTTP Authorization header).  The authorization 
238   * persists across multiple requests.
239   *
240   * @param name the user name
241   * @param name the user password
242   */
243  public void setAuthorization(String name, String password) {
244    String authorization = Base64Encoder.encode(name + ":" + password);
245    setHeader("Authorization", "Basic " + authorization);
246  }
247
248  /*
249   * Converts a properties list to a URL-encoded query string
250   */
251  private String toEncodedString(Properties args) {
252    StringBuffer buf = new StringBuffer();
253    Enumeration names = args.propertyNames();
254    while (names.hasMoreElements()) {
255      String name = (String) names.nextElement();
256      String value = args.getProperty(name);
257      buf.append(URLEncoder.encode(name) + "=" + URLEncoder.encode(value));
258      if (names.hasMoreElements()) buf.append("&");
259    }
260    return buf.toString();
261  }
262}