Coverage Report - org.mule.providers.http.HttpClientMessageDispatcher
 
Classes in this File Line Coverage Branch Coverage Complexity
HttpClientMessageDispatcher
45%
67/150
32%
20/62
3.471
HttpClientMessageDispatcher$StreamPayloadRequestEntity
0%
0/9
N/A
3.471
 
 1  
 /*
 2  
  * $Id: HttpClientMessageDispatcher.java 8426 2007-09-15 01:31:06Z aperepel $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 
 11  
 package org.mule.providers.http;
 12  
 
 13  
 import org.mule.impl.MuleMessage;
 14  
 import org.mule.impl.message.ExceptionPayload;
 15  
 import org.mule.providers.AbstractMessageDispatcher;
 16  
 import org.mule.providers.http.i18n.HttpMessages;
 17  
 import org.mule.providers.http.transformers.HttpClientMethodResponseToObject;
 18  
 import org.mule.providers.http.transformers.ObjectToHttpClientMethodRequest;
 19  
 import org.mule.providers.streaming.StreamMessageAdapter;
 20  
 import org.mule.umo.UMOEvent;
 21  
 import org.mule.umo.UMOMessage;
 22  
 import org.mule.umo.endpoint.UMOImmutableEndpoint;
 23  
 import org.mule.umo.provider.DispatchException;
 24  
 import org.mule.umo.provider.ReceiveException;
 25  
 import org.mule.umo.provider.UMOMessageAdapter;
 26  
 import org.mule.umo.provider.UMOStreamMessageAdapter;
 27  
 import org.mule.umo.transformer.TransformerException;
 28  
 import org.mule.umo.transformer.UMOTransformer;
 29  
 import org.mule.util.StringUtils;
 30  
 
 31  
 import java.io.IOException;
 32  
 import java.io.OutputStream;
 33  
 import java.net.URI;
 34  
 import java.net.URISyntaxException;
 35  
 import java.util.Iterator;
 36  
 import java.util.Map;
 37  
 import java.util.Properties;
 38  
 
 39  
 import org.apache.commons.codec.binary.Base64;
 40  
 import org.apache.commons.httpclient.Cookie;
 41  
 import org.apache.commons.httpclient.Header;
 42  
 import org.apache.commons.httpclient.HostConfiguration;
 43  
 import org.apache.commons.httpclient.HttpClient;
 44  
 import org.apache.commons.httpclient.HttpMethod;
 45  
 import org.apache.commons.httpclient.HttpState;
 46  
 import org.apache.commons.httpclient.HttpStatus;
 47  
 import org.apache.commons.httpclient.UsernamePasswordCredentials;
 48  
 import org.apache.commons.httpclient.auth.AuthScope;
 49  
 import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
 50  
 import org.apache.commons.httpclient.methods.DeleteMethod;
 51  
 import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
 52  
 import org.apache.commons.httpclient.methods.GetMethod;
 53  
 import org.apache.commons.httpclient.methods.HeadMethod;
 54  
 import org.apache.commons.httpclient.methods.OptionsMethod;
 55  
 import org.apache.commons.httpclient.methods.PostMethod;
 56  
 import org.apache.commons.httpclient.methods.PutMethod;
 57  
 import org.apache.commons.httpclient.methods.RequestEntity;
 58  
 import org.apache.commons.httpclient.methods.TraceMethod;
 59  
 import org.apache.commons.httpclient.params.HttpMethodParams;
 60  
 import org.apache.commons.httpclient.protocol.Protocol;
 61  
 import org.apache.commons.io.IOUtils;
 62  
 
 63  
 /**
 64  
  * <code>HttpClientMessageDispatcher</code> dispatches Mule events over HTTP.
 65  
  */
 66  
 public class HttpClientMessageDispatcher extends AbstractMessageDispatcher
 67  
 {
 68  
     /**
 69  
      * Range start for http error status codes.
 70  
      */
 71  
     public static final int ERROR_STATUS_CODE_RANGE_START = 400;
 72  
     private final HttpConnector connector;
 73  46
     private volatile HttpClient client = null;
 74  
     private final UMOTransformer receiveTransformer;
 75  
 
 76  
     public HttpClientMessageDispatcher(UMOImmutableEndpoint endpoint)
 77  
     {
 78  46
         super(endpoint);
 79  46
         this.connector = (HttpConnector) endpoint.getConnector();
 80  46
         this.receiveTransformer = new HttpClientMethodResponseToObject();
 81  46
     }
 82  
 
 83  
     protected void doConnect() throws Exception
 84  
     {
 85  46
         if (client == null)
 86  
         {
 87  46
             HttpState state = new HttpState();
 88  
 
 89  46
             if (connector.getProxyUsername() != null)
 90  
             {
 91  0
                 state.setProxyCredentials(new AuthScope(null, -1, null, null),
 92  
                     new UsernamePasswordCredentials(connector.getProxyUsername(),
 93  
                         connector.getProxyPassword()));
 94  
             }
 95  
 
 96  46
             client = new HttpClient();
 97  46
             client.setState(state);
 98  46
             client.setHttpConnectionManager(connector.getClientConnectionManager());
 99  
         }
 100  
 
 101  46
     }
 102  
 
 103  
     protected void doDisconnect() throws Exception
 104  
     {
 105  46
         client = null;
 106  46
     }
 107  
 
 108  
     protected void doDispatch(UMOEvent event) throws Exception
 109  
     {
 110  0
         HttpMethod httpMethod = getMethod(event);
 111  0
         execute(event, httpMethod, true);
 112  0
         if (httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START)
 113  
         {
 114  0
             logger.error(httpMethod.getResponseBodyAsString());
 115  0
             throw new DispatchException(event.getMessage(), event.getEndpoint(), new Exception(
 116  
                 "Http call returned a status of: " + httpMethod.getStatusCode() + " "
 117  
                                 + httpMethod.getStatusText()));
 118  
         }
 119  0
     }
 120  
 
 121  
     /**
 122  
      * Make a specific request to the underlying transport
 123  
      * 
 124  
      * @param timeout the maximum time the operation should block before returning.
 125  
      *            The call should return immediately if there is data available. If
 126  
      *            no data becomes available before the timeout elapses, null will be
 127  
      *            returned
 128  
      * @return the result of the request wrapped in a UMOMessage object. Null will be
 129  
      *         returned if no data was avaialable
 130  
      * @throws Exception if the call to the underlying protocal cuases an exception
 131  
      */
 132  
     protected UMOMessage doReceive(long timeout) throws Exception
 133  
     {
 134  0
         HttpMethod httpMethod = new GetMethod(endpoint.getEndpointURI().getAddress());
 135  0
         httpMethod.setDoAuthentication(true);
 136  0
         if (endpoint.getEndpointURI().getUserInfo() != null
 137  
             && endpoint.getProperty(HttpConstants.HEADER_AUTHORIZATION) == null)
 138  
         {
 139  
             // Add User Creds
 140  0
             StringBuffer header = new StringBuffer(128);
 141  0
             header.append("Basic ");
 142  0
             header.append(new String(Base64.encodeBase64(endpoint.getEndpointURI().getUserInfo().getBytes(
 143  
                 endpoint.getEncoding()))));
 144  0
             httpMethod.addRequestHeader(HttpConstants.HEADER_AUTHORIZATION, header.toString());
 145  
         }
 146  
         try
 147  
         {
 148  0
             HttpClient client = new HttpClient();
 149  0
             client.executeMethod(httpMethod);
 150  
 
 151  0
             if (httpMethod.getStatusCode() == HttpStatus.SC_OK)
 152  
             {
 153  0
                 return (UMOMessage) receiveTransformer.transform(httpMethod);
 154  
             }
 155  
             else
 156  
             {
 157  0
                 throw new ReceiveException(
 158  
                     HttpMessages.requestFailedWithStatus(httpMethod.getStatusLine().toString()),
 159  
                     endpoint, timeout);
 160  
             }
 161  
         }
 162  0
         catch (ReceiveException e)
 163  
         {
 164  0
             throw e;
 165  
         }
 166  0
         catch (Exception e)
 167  
         {
 168  0
             throw new ReceiveException(endpoint, timeout, e);
 169  
         }
 170  
         finally
 171  
         {
 172  0
             httpMethod.releaseConnection();
 173  
         }
 174  
     }
 175  
 
 176  
     protected HttpMethod execute(UMOEvent event, HttpMethod httpMethod, boolean closeConnection)
 177  
         throws Exception
 178  
     {
 179  
         // TODO set connection timeout buffer etc
 180  
         try
 181  
         {
 182  126
             URI uri = event.getEndpoint().getEndpointURI().getUri();
 183  
 
 184  126
             this.processCookies(event);
 185  
 
 186  
             // TODO can we use the return code for better reporting?
 187  126
             client.executeMethod(getHostConfig(uri), httpMethod);
 188  
 
 189  126
             return httpMethod;
 190  
         }
 191  0
         catch (IOException e)
 192  
         {
 193  
             // TODO employ dispatcher reconnection strategy at this point
 194  0
             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
 195  
         }
 196  0
         catch (Exception e)
 197  
         {
 198  0
             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
 199  
         }
 200  
         finally
 201  
         {
 202  126
             if (httpMethod != null && closeConnection)
 203  
             {
 204  0
                 httpMethod.releaseConnection();
 205  
             }
 206  
         }
 207  
     }
 208  
 
 209  
     protected void processCookies(UMOEvent event)
 210  
     {
 211  126
         UMOMessage msg = event.getMessage();
 212  126
         Cookie[] cookies = (Cookie[]) msg.removeProperty(HttpConnector.HTTP_COOKIES_PROPERTY);
 213  126
         if (cookies != null && cookies.length > 0)
 214  
         {
 215  12
             String policy = (String) msg.removeProperty(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY);
 216  12
             client.getParams().setCookiePolicy(CookieHelper.getCookiePolicy(policy));
 217  12
             client.getState().addCookies(cookies);
 218  
         }
 219  126
     }
 220  
 
 221  
     protected HttpMethod getMethod(UMOEvent event) throws TransformerException
 222  
     {
 223  126
         UMOMessage msg = event.getMessage();
 224  126
         String method = msg.getStringProperty(HttpConnector.HTTP_METHOD_PROPERTY, HttpConstants.METHOD_POST);
 225  126
         URI uri = event.getEndpoint().getEndpointURI().getUri();
 226  
         HttpMethod httpMethod;
 227  126
         Object body = event.getTransformedMessage();
 228  
 
 229  126
         if (body instanceof HttpMethod)
 230  
         {
 231  126
             httpMethod = (HttpMethod)body;
 232  
         }
 233  0
         else if (HttpConstants.METHOD_GET.equalsIgnoreCase(method))
 234  
         {
 235  0
             httpMethod = new GetMethod(uri.toString());
 236  
         }
 237  0
         else if (HttpConstants.METHOD_PUT.equalsIgnoreCase(method))
 238  
         {
 239  0
             PutMethod postMethod = new PutMethod(uri.toString());
 240  
 
 241  0
             httpMethod = createEntityMethod(event, body, postMethod);
 242  0
         }
 243  0
         else if (HttpConstants.METHOD_POST.equalsIgnoreCase(method))
 244  
         {
 245  0
             PostMethod postMethod = new PostMethod(uri.toString());
 246  
 
 247  0
             httpMethod = createEntityMethod(event, body, postMethod);
 248  0
         }
 249  0
         else if (HttpConstants.METHOD_DELETE.equalsIgnoreCase(method))
 250  
         {
 251  0
             httpMethod = new DeleteMethod(uri.toString());
 252  
         }
 253  0
         else if (HttpConstants.METHOD_HEAD.equalsIgnoreCase(method))
 254  
         {
 255  0
             httpMethod = new HeadMethod(uri.toString());
 256  
         }
 257  0
         else if (HttpConstants.METHOD_OPTIONS.equalsIgnoreCase(method))
 258  
         {
 259  0
             httpMethod = new OptionsMethod(uri.toString());
 260  
         }
 261  0
         else if (HttpConstants.METHOD_TRACE.equalsIgnoreCase(method))
 262  
         {
 263  0
             httpMethod = new TraceMethod(uri.toString());
 264  
         }
 265  
         else
 266  
         {
 267  0
             throw new TransformerException(HttpMessages.unsupportedMethod(method));
 268  
         }
 269  
         
 270  126
         httpMethod.setDoAuthentication(true);
 271  126
         if (event.getCredentials() != null)
 272  
         {
 273  0
             String authScopeHost = msg.getStringProperty("http.auth.scope.host", null);
 274  0
             int authScopePort = msg.getIntProperty("http.auth.scope.port", -1);
 275  0
             String authScopeRealm = msg.getStringProperty("http.auth.scope.realm", null);
 276  0
             String authScopeScheme = msg.getStringProperty("http.auth.scope.scheme", null);
 277  0
             client.getState().setCredentials(
 278  
                 new AuthScope(authScopeHost, authScopePort, authScopeRealm, authScopeScheme),
 279  
                 new UsernamePasswordCredentials(event.getCredentials().getUsername(), new String(
 280  
                     event.getCredentials().getPassword())));
 281  0
             client.getParams().setAuthenticationPreemptive(true);
 282  0
         }
 283  
         else
 284  
         {
 285  
             // don't use preemptive if there are no credentials to send
 286  126
             client.getParams().setAuthenticationPreemptive(false);
 287  
         }
 288  126
         return httpMethod;
 289  
     }
 290  
 
 291  
     protected HttpMethod createEntityMethod(UMOEvent event, Object body, EntityEnclosingMethod postMethod)
 292  
         throws TransformerException
 293  
     {
 294  
         HttpMethod httpMethod;
 295  0
         if (body instanceof String)
 296  
         {
 297  0
             ObjectToHttpClientMethodRequest trans = new ObjectToHttpClientMethodRequest();
 298  0
             httpMethod = (HttpMethod)trans.transform(body.toString());
 299  0
         }
 300  0
         else if (body instanceof UMOStreamMessageAdapter)
 301  
         {
 302  0
             UMOStreamMessageAdapter sma = (UMOStreamMessageAdapter)body;
 303  0
             Map headers = sma.getOutputHandler().getHeaders(event);
 304  0
             for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();)
 305  
             {
 306  0
                 Map.Entry entry = (Map.Entry)iterator.next();
 307  0
                 postMethod.addRequestHeader((String)entry.getKey(), (String)entry.getValue());
 308  0
             }
 309  0
             postMethod.setRequestEntity(new StreamPayloadRequestEntity((StreamMessageAdapter)body, event));
 310  0
             postMethod.setContentChunked(true);
 311  0
             httpMethod = postMethod;
 312  0
         }
 313  
         else
 314  
         {
 315  0
             byte[] buffer = event.getTransformedMessageAsBytes();
 316  0
             postMethod.setRequestEntity(new ByteArrayRequestEntity(buffer, event.getEncoding()));
 317  0
             httpMethod = postMethod;
 318  
         }
 319  0
         return httpMethod;
 320  
     }
 321  
 
 322  
     /*
 323  
      * (non-Javadoc)
 324  
      * 
 325  
      * @see org.mule.umo.provider.UMOConnector#send(org.mule.umo.UMOEvent)
 326  
      */
 327  
     protected UMOMessage doSend(UMOEvent event) throws Exception
 328  
     {        
 329  126
         HttpMethod httpMethod = getMethod(event);
 330  126
         httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new MuleHttpMethodRetryHandler());
 331  
 
 332  126
         httpMethod = execute(event, httpMethod, false);
 333  
         
 334  
         try
 335  
         {
 336  126
             Properties h = new Properties();
 337  126
             Header[] headers = httpMethod.getResponseHeaders();
 338  786
             for (int i = 0; i < headers.length; i++)
 339  
             {
 340  660
                 h.setProperty(headers[i].getName(), headers[i].getValue());
 341  
             }
 342  
 
 343  126
             String status = String.valueOf(httpMethod.getStatusCode());
 344  
 
 345  126
             h.setProperty(HttpConnector.HTTP_STATUS_PROPERTY, status);
 346  126
             if (logger.isDebugEnabled())
 347  
             {
 348  0
                 logger.debug("Http response is: " + status);
 349  
             }
 350  126
             ExceptionPayload ep = null;
 351  126
             if (httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START)
 352  
             {
 353  6
                 ep = new ExceptionPayload(new DispatchException(event.getMessage(), event.getEndpoint(),
 354  
                     new Exception("Http call returned a status of: " + httpMethod.getStatusCode() + " "
 355  
                                   + httpMethod.getStatusText())));
 356  
             }
 357  
             UMOMessage m;
 358  
             // text or binary content?
 359  126
             Header header = httpMethod.getResponseHeader(HttpConstants.HEADER_CONTENT_TYPE);
 360  126
             if ((header != null) && event.isStreaming())
 361  
             {
 362  0
                 HttpStreamMessageAdapter sp = (HttpStreamMessageAdapter)connector.getStreamMessageAdapter(
 363  
                     httpMethod.getResponseBodyAsStream(), null);
 364  0
                 sp.setHttpMethod(httpMethod);
 365  0
                 m = new MuleMessage(sp, h);
 366  0
             }
 367  
             else
 368  
             {
 369  126
                 Object body = IOUtils.toByteArray(httpMethod.getResponseBodyAsStream());
 370  126
                 if (body == null)
 371  
                 {
 372  0
                     body = StringUtils.EMPTY;
 373  
                 }
 374  126
                 UMOMessageAdapter adapter = connector.getMessageAdapter(new Object[]{body, h});
 375  126
                 m = new MuleMessage(adapter);
 376  
             }
 377  126
             m.setExceptionPayload(ep);
 378  126
             return m;
 379  
         }
 380  0
         catch (Exception e)
 381  
         {
 382  0
             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
 383  
         }
 384  
         finally
 385  
         {
 386  126
             if (httpMethod != null && !event.isStreaming())
 387  
             {
 388  126
                 httpMethod.releaseConnection();
 389  
             }
 390  
         }
 391  
     }
 392  
 
 393  
     protected HostConfiguration getHostConfig(URI uri) throws URISyntaxException
 394  
     {
 395  126
         Protocol protocol = Protocol.getProtocol(uri.getScheme().toLowerCase());
 396  
 
 397  126
         String host = uri.getHost();
 398  126
         int port = uri.getPort();
 399  126
         HostConfiguration config = new HostConfiguration();
 400  126
         config.setHost(host, port, protocol);
 401  126
         if (StringUtils.isNotBlank(connector.getProxyHostname()))
 402  
         {
 403  
             // add proxy support
 404  0
             config.setProxy(connector.getProxyHostname(), connector.getProxyPort());
 405  
         }
 406  126
         return config;
 407  
     }
 408  
 
 409  
     protected void doDispose()
 410  
     {
 411  
         // template method
 412  46
     }
 413  
 
 414  
     protected class StreamPayloadRequestEntity implements RequestEntity
 415  
     {
 416  
         private UMOStreamMessageAdapter messageAdapter;
 417  
         private UMOEvent event;
 418  
 
 419  
         public StreamPayloadRequestEntity(UMOStreamMessageAdapter messageAdapter, UMOEvent event)
 420  0
         {
 421  0
             this.messageAdapter = messageAdapter;
 422  0
             this.event = event;
 423  0
         }
 424  
 
 425  
         public boolean isRepeatable()
 426  
         {
 427  0
             return true;
 428  
         }
 429  
 
 430  
         public void writeRequest(OutputStream outputStream) throws IOException
 431  
         {
 432  0
             messageAdapter.getOutputHandler().write(event, outputStream);
 433  0
         }
 434  
 
 435  
         public long getContentLength()
 436  
         {
 437  0
             return -1L;
 438  
         }
 439  
 
 440  
         public String getContentType()
 441  
         {
 442  0
             return event.getMessage().getStringProperty(HttpConstants.HEADER_CONTENT_TYPE, null);
 443  
         }
 444  
     }
 445  
 
 446  
 }