001 /*
002 * Copyright 1999-2002,2004 The Apache Software Foundation.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.apache.commons.latka.http;
018 // java imports
019 import java.net.URL;
020 import java.io.ByteArrayInputStream;
021 import java.io.InputStream;
022 import java.io.IOException;
023 import java.util.List;
024 import java.util.LinkedList;
025 // latka imports
026 import org.apache.commons.httpclient.HostConfiguration;
027 import org.apache.commons.httpclient.UsernamePasswordCredentials;
028 import org.apache.commons.httpclient.HttpClient;
029 import org.apache.commons.httpclient.HttpException;
030 import org.apache.commons.httpclient.HttpMethod;
031 import org.apache.commons.httpclient.HttpState;
032 import org.apache.commons.httpclient.HttpMethodBase;
033 import org.apache.commons.httpclient.methods.PostMethod;
034 // log4j imports
035 import org.apache.log4j.Category;
036
037 /**
038 * An implementation of a Latka Request interface based on the Apache Commons
039 * HttpClient package.
040 *
041 * @todo pass proxy host and port to httpclient
042 * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
043 * @author <a href="mailto:mdelagra@us.britannica.com">Morgan Delagrange</a>
044 * @author dIon Gillard
045 * @version $Id: RequestImpl.java 561366 2007-07-31 15:58:29Z rahul $
046 * @see Request
047 */
048 public class RequestImpl implements Request {
049
050 /** Standard HTTP Port */
051 public static final int HTTP_PORT = 80;
052
053 /** Standard HTTPS Port */
054 public static final int HTTPS_PORT = 443;
055 /** host the request is being made on */
056 protected String _host = null;
057 /** port the request is being made on */
058 protected int _port = -1;
059 /** http method being used to make the request */
060 protected int _method = -1;
061 /** http session the request is part of */
062 protected SessionImpl _session = null;
063 /** credentials for the request */
064 protected Credentials _credentials = null;
065 /** http method implementation */
066 protected HttpMethod _httpMethod = null;
067 /** URL being requested */
068 protected URL _targetURL = null;
069 /** query string portion of the URL */
070 protected String _query = null;
071 /** time taken for the request in milliseconds */
072 protected long _requestTiming = -1;
073 /** name given to the request */
074 protected String _label = null;
075 /** headers sent with this request */
076 protected RequestHeaders _requestHeaders = new RequestHeadersImpl();
077 /** parameters sent with this request */
078 protected Parameters _parameters = new ParametersImpl();
079 /** manually constructed post body */
080 protected String _requestBody = null;
081 /** whether or not redirect responses should be followed as part of the
082 request processing*/
083 protected boolean _followRedirects = true;
084 /** object used to perform the http requests */
085 protected HttpClient _httpClient = new HttpClient();
086 /** proxy used to perform the requests */
087 private Proxy _proxy = null;
088 /** log4j category used when logging messages */
089 protected static final Category _log =
090 Category.getInstance(RequestImpl.class);
091
092 /**
093 * HTTP Version 1.0
094 */
095 private static final String HTTP_10 = "1.0";
096 /**
097 * HTTP Version 1.1
098 */
099 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 }