View Javadoc

1   /*
2    * Copyright 1999-2002,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.latka.http;
18  // java imports
19  import java.net.URL;
20  import java.io.ByteArrayInputStream;
21  import java.io.InputStream;
22  import java.io.IOException;
23  import java.util.List;
24  import java.util.LinkedList;
25  // latka imports
26  import org.apache.commons.httpclient.HostConfiguration;
27  import org.apache.commons.httpclient.UsernamePasswordCredentials;
28  import org.apache.commons.httpclient.HttpClient;
29  import org.apache.commons.httpclient.HttpException;
30  import org.apache.commons.httpclient.HttpMethod;
31  import org.apache.commons.httpclient.HttpState;
32  import org.apache.commons.httpclient.HttpMethodBase;
33  import org.apache.commons.httpclient.methods.PostMethod;
34  // log4j imports
35  import org.apache.log4j.Category;
36  
37  /**
38   * An implementation of a Latka Request interface based on the Apache Commons
39   * HttpClient package.
40   *
41   * @todo pass proxy host and port to httpclient
42   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
43   * @author <a href="mailto:mdelagra@us.britannica.com">Morgan Delagrange</a>
44   * @author dIon Gillard
45   * @version $Id: RequestImpl.java 561366 2007-07-31 15:58:29Z rahul $
46   * @see Request
47   */
48  public class RequestImpl implements Request {
49      
50      /** Standard HTTP Port */
51      public static final int HTTP_PORT = 80;
52      
53      /** Standard HTTPS Port */
54      public static final int HTTPS_PORT = 443;
55      /** host the request is being made on */
56      protected String  _host   = null;
57      /** port the request is being made on */
58      protected int     _port   = -1;
59      /** http method being used to make the request */
60      protected int     _method = -1;
61      /** http session the request is part of */
62      protected SessionImpl _session    = null;
63      /** credentials for the request */
64      protected Credentials _credentials = null;
65      /** http method implementation */
66      protected HttpMethod  _httpMethod = null;
67      /** URL being requested */
68      protected URL         _targetURL  = null;
69      /** query string portion of the URL */
70      protected String      _query      = null;
71      /** time taken for the request in milliseconds */
72      protected long        _requestTiming = -1;
73      /** name given to the request */
74      protected String      _label      = null;
75      /** headers sent with this request */
76      protected RequestHeaders _requestHeaders = new RequestHeadersImpl();
77      /** parameters sent with this request */
78      protected Parameters _parameters = new ParametersImpl();
79      /** manually constructed post body */
80      protected String _requestBody = null;
81      /** whether or not redirect responses should be followed as part of the
82          request processing*/
83      protected boolean _followRedirects = true;
84      /** object used to perform the http requests */
85      protected HttpClient _httpClient = new HttpClient();
86      /** proxy used to perform the requests */
87      private Proxy _proxy = null;
88      /** log4j category used when logging messages */
89      protected static final Category _log = 
90          Category.getInstance(RequestImpl.class);
91  
92      /**
93       * HTTP Version 1.0
94       */
95      private static final String HTTP_10 = "1.0";
96      /**
97       * HTTP Version 1.1
98       */    
99      private static final String HTTP_11 = "1.1";
100 
101     protected String _httpVersion = HTTP_11;
102 
103     protected List _visitedURLs = new LinkedList();
104 
105     /**
106      * Create a request for the specified URL, process using the supplied method
107      * and use the supplied state and session for persistent data
108      * @param url the URL to send the request to
109      * @param httpMethod the http method to use in sending the request, as 
110      *        defined in {@link Request}
111      * @param state shared information across requests
112      * @param session state shared across servers and requests
113      */
114     protected RequestImpl(URL url, int httpMethod, HttpState state,
115         SessionImpl session) {
116         this(null, url, httpMethod, state, session, true);
117     }
118     
119     /**
120      * Create a RequestImpl
121      *
122      * @param label       a name for the request
123      * @param url         the url that this request embodies
124      * @param httpMethod  the method by which this request should be executed
125      * @param state       shared information across requests
126      * @param session     the session that the request should be executed in
127      * @param followRedirects whether http redirect responses should be honoured
128      */
129     protected RequestImpl(String label, URL url, int httpMethod, 
130         HttpState state, SessionImpl session, boolean followRedirects) {
131             
132         _followRedirects = followRedirects;
133         _method = httpMethod;
134         _httpClient.setState(state);
135         
136         _label = label;
137         _query = url.getQuery();
138         
139         _session = session;
140         _targetURL = url;
141         _httpMethod = MethodFactory.getInstance(httpMethod, url);
142         if (_query != null) {
143             _httpMethod.setQueryString(_query);
144         }
145         _httpMethod.setFollowRedirects(followRedirects);
146     }
147     
148     /**
149      * Returns the object implementing the HttpMethod
150      * (get, post, etc.) for this request.
151      *
152      * @return the underlying HttpMethod object representing the request/
153      *      response pair.
154      */
155     protected HttpMethod getHttpMethod() {
156         return _httpMethod;
157     }
158     
159     /**
160      * Defined in the implemented interface
161      * @return the headers used for this request
162      * @see Request#getHeaders()
163      */
164     public RequestHeaders getHeaders() {
165         return _requestHeaders;
166     }
167     
168     /**
169      * Defined in the implemented interface
170      * @param requestHeaders new value for the headers property
171      * @see Request#setHeaders(RequestHeaders)
172      */
173     public void setHeaders(RequestHeaders requestHeaders) {
174         _requestHeaders = requestHeaders;
175     }
176     
177     /**
178      * Defined in the implemented interface
179      * @return the parameters property
180      * @see Request#getParameters()
181      */
182     public Parameters getParameters() {
183         return _parameters;
184     }
185 
186     /**
187      * Defined in the implemented interface
188      * @see Request#setRequestBody(String)
189      */
190     public void setRequestBody(String body) {
191         _requestBody = body;
192     }
193     
194     /**
195      * Defined in the implemented interface
196      * @param parameters new value for parameters property
197      * @see Request#setParameters(Parameters)
198      */
199     public void setParameters(Parameters parameters) {
200         _parameters = parameters;
201     }
202     
203     /**
204      * Defined in the implemented interface
205      * @param credentials username and password to use
206      * @see Request#setCredentials(Credentials)
207      */
208     public void setCredentials(Credentials credentials) {
209         // null implies that this credential is the default (vs. specifying a 
210         // realm)
211         UsernamePasswordCredentials creds = new UsernamePasswordCredentials(
212             credentials.getUserName(), credentials.getPassword());
213         _session._state.setCredentials(null, creds);
214         _credentials = credentials;
215     }
216 
217     public Credentials getCredentials() {
218         return _credentials;
219     }
220     
221     /**
222      * Execute the request - perform the http interaction.
223      * Since HttpClient doesn't follow off-server
224      * redirects, execute() may have to construct further 
225      * requests that call this method.
226      * 
227      * @return a {@link Response} detailing the html etc
228      * @exception IOException
229      *                   when there are problems reading and writing
230      * @see Request#execute()
231      */
232     protected Response executeRequestPerHost() throws IOException {
233         
234         // set the request headers in HTTPClient
235         List headers = _requestHeaders.getHeaders();
236         for (int i = 0; i < headers.size(); ++i) {
237             String[] header = (String[]) headers.get(i);
238             _httpMethod.addRequestHeader(header[0], header[1]);
239         }
240         
241         if (_requestBody != null) {
242             InputStream  is = new ByteArrayInputStream(
243                 _requestBody.getBytes("ISO-8859-1"));
244             ((PostMethod) _httpMethod).setRequestBody(is);
245         } else {
246             List parameters = _parameters.getParameters();
247             for (int i = 0; i < parameters.size(); ++i) {
248                 String[] parameter = (String[]) parameters.get(i);
249                 addHttpClientParameter(parameter[0], parameter[1]);
250             }
251         }
252         
253         // for timing
254         long startDate = System.currentTimeMillis();
255         
256         Response response = null;
257         try {
258             // open the connection
259             openConnection();
260             
261             _log.debug("executing request");
262             
263             _httpClient.executeMethod(_httpMethod);
264             
265             _log.debug("request executed");
266             
267             response = new ResponseImpl(this);
268 
269             // Cache the response body to allow it to be accessed
270             // after the http connection is closed.
271             String sideEffectOnly = response.getResource();
272             
273             // set the referer
274             // note: If followRedirects
275             // is enabled, HTTPClient may return a path
276             // that is different from the initial request.
277             // HTTPClient will not follow redirects to another
278             // host, port, or protocol; in that event, it will always
279             // return a 301 or 302.
280             _session.setReferer(new URL(_targetURL.getProtocol(), _host, _port,
281                 _httpMethod.getPath()));
282             
283         } catch (HttpException e) {
284             throw new IOException(e.toString());
285         } catch (IOException e) {
286             // rethrow it after closing the connection
287             throw e;
288         } finally {
289             try {
290                 closeConnection(); 
291                 // FIXME: Shouldn't use Exception here.
292             } catch (Exception e) {
293                 e.printStackTrace();
294             }
295         }
296         
297         
298         _requestTiming = System.currentTimeMillis() - startDate;
299         
300         if (_log.isInfoEnabled()) {
301             _log.info("response obtained (response logging disabled because "
302                 + "some responses are binary)");
303         }
304         
305         return response;
306     }
307     
308     /**
309      * Executes the request.  In the event of a 301 or 302,
310      * this method may need to create a new Request, 
311      * due to an idiosyncracy of
312      * HttpClient.  In that case, the Response.getRequest()
313      * method will return the actual Request that was executed.
314      */
315     public Response execute() throws IOException {
316         Response response = executeRequestPerHost();
317 
318         if (followRedirects() == false) {
319             return response;
320         }
321 
322         Request lastRequest = this;
323         // execute the request until either we get a non-redirect response, or
324         // we visit a URL we have already visited
325         while (response.getStatusCode() == 301 || response.getStatusCode() == 302) {
326             // follow the redirect
327             URL url = new URL(response.getHeader("location"));
328 
329             if (_visitedURLs.contains(url.toString())) {
330                 return response;
331             }
332 
333             Request request = _session.createRequest(lastRequest.getLabel(), url,
334                 lastRequest.getMethod(), lastRequest.getVersion(), true, getProxy());
335             request.setParameters(lastRequest.getParameters());
336             request.setHeaders(lastRequest.getHeaders());
337             Credentials credentials = lastRequest.getCredentials();
338             if (credentials != null) {
339                 request.setCredentials(credentials);
340             }
341             response = request.execute();
342             _visitedURLs.add(url.toString());
343             lastRequest = request;
344         }
345 
346         return response;
347     }
348 
349     /**
350      * Get the URL used for the request
351      * @return the {@link URL} of the request
352      * @see Request#getURL
353      */
354     public URL getURL() {
355         return _targetURL;
356     }
357     
358     /**
359      * Get the label used for the request
360      * @return the label used to create the request
361      * @see Request#getLabel
362      */
363     public String getLabel() {
364         return _label;
365     }
366     
367     /**
368      * Add a parameter (name and value) to the request
369      * @param name the name of the parameter to be added
370      * @param value the value of the parameter to be added
371      * @see Request#addParameter(String,String)
372      */
373     public void addParameter(String name, String value) {
374         _parameters.addParameter(name, value);
375     }
376     
377     /**
378      * Associate a parameter with this request.
379      *
380      * @param name  the lvalue of the parameter - must not be null
381      * @param value  the rvalue of the parameter - must not be null
382      */
383     protected void addHttpClientParameter(String name, String value) {
384         if (name == null) {
385             throw new NullPointerException("name parameter is null");
386         }
387         
388         if (value == null) {
389             throw new NullPointerException("value parameter is null");
390         }
391         _log.info("adding parameter, name: " + name + ", value: " + value);
392         
393         if (_httpMethod instanceof PostMethod) {
394             // addParameter adds to POST Entity, not URL
395             ((PostMethod) _httpMethod).addParameter(name, value);
396         } else {
397             StringBuffer query = new StringBuffer();
398 
399             // setParameter adds to URL as query string
400             if (_query == null || _query.equals("")) {
401                 query.append(name);
402                 query.append("=");
403                 query.append(value);
404             } else {
405                 query.append(_query);
406                 query.append("&");
407                 query.append(name);
408                 query.append("=");
409                 query.append(value);
410             }
411             _query = query.toString();
412             ((HttpMethod) _httpMethod).setQueryString(_query);
413         }
414     }
415     
416     /**
417      * Set a header in the request
418      *
419      * @param headerName name of any HTTP request header
420      * @param headerValue value of that header
421      */
422     public void addHeader(String headerName, String headerValue) {
423         _requestHeaders.addHeader(headerName, headerValue);
424     }
425     
426     /**
427      * Retrieve the session associated with this request.
428      *
429      * @return a <code>Session</code> object
430      */
431     public Session getSession() {
432         return _session;
433     }
434     
435     /**
436      * @return the time it took to execute this request in milliseconds
437      * or -1 if the request has not yet been executed
438      */
439     public int getRequestTiming() {
440         return (int) _requestTiming;
441     }
442     
443     /**
444      * opens an HTTP connection.  This method is called
445      * before executing the method.
446      *
447      * @exception IOException
448      *                   if the server could not be contacted
449      */
450     protected void openConnection() throws IOException {
451         _log.debug("Opening connection");
452         
453         URL url = getURL();
454         String protocol = url.getProtocol();
455         String host = url.getHost();
456         int port = url.getPort();
457         
458         // explicitly set port if not in url,
459         // just for storing away and comparison
460         if (port == -1) {
461             if (protocol.equals("http")) {
462                 port = HTTP_PORT;
463             } else if (protocol.equals("https")) {
464                 port = HTTPS_PORT;
465             } else {
466                 throw new IllegalArgumentException("Unsupported Protocol");
467             }
468         }
469         
470         // save session values
471         _host = host;
472         _port = port;
473         
474         HostConfiguration hostConfiguration = _httpClient.getHostConfiguration();
475         hostConfiguration.setHost(host, port, protocol);
476         if (getProxy() != null) {
477             hostConfiguration.setProxy(getProxy().getHost(), getProxy().getPort());
478         }
479         
480         _log.debug("connection open");
481     }
482     
483     /**
484      * Closes the http connection associated with the request
485      * @throws IOException if there are problems closing the connection
486      */
487     protected void closeConnection() throws IOException {
488         _log.debug("closing connection");
489         _httpMethod.releaseConnection();
490         _log.debug("connection closed");
491     }
492     
493     /**
494      * Defined in the interface
495      * @return whether the request will honour http redirect status codes
496      * @see Request#followRedirects()
497      */
498     public boolean followRedirects() {
499         return _followRedirects;
500     }
501     
502     /**
503      * Defined in the interface
504      * @return the http method being used for the request, as defined in the
505      *      interface
506      * @see Request#getMethod
507      */
508     public int getMethod() {
509         return _method;
510     }
511     
512     /** Getter for property proxy.
513      * @return Value of property proxy.
514      */
515     public Proxy getProxy() {
516         return _proxy;
517     }
518     
519     /** Setter for property proxy.
520      * @param proxy New value of property proxy.
521      */
522     public void setProxy(Proxy proxy) {
523         _proxy = proxy;
524     }
525     
526     /**
527      * Sets the HTTP version for this request.  If
528      * the version provided is not 1.0 or 1.1, then
529      * 1.1 will be used.
530      *
531      * @param version  HTTP version
532      */
533     public void setVersion(String version) {
534         ((HttpMethodBase) _httpMethod).setHttp11(!HTTP_10.equals(version));
535         _httpVersion = version;
536     }
537 
538     /**
539      * Get the HTTP version to use in this request.
540      * 
541      * @return HTTP version for the request
542      */
543     public String getVersion() {
544         return _httpVersion;
545     }
546 }