View Javadoc

1   /*
2    * $Id: HttpClientMessageDispatcher.java 22686 2011-08-16 19:39:20Z pablo.lagreca $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.transport.http;
12  
13  import org.mule.api.ExceptionPayload;
14  import org.mule.api.MuleEvent;
15  import org.mule.api.MuleException;
16  import org.mule.api.MuleMessage;
17  import org.mule.api.config.MuleProperties;
18  import org.mule.api.endpoint.OutboundEndpoint;
19  import org.mule.api.lifecycle.InitialisationException;
20  import org.mule.api.transformer.DataType;
21  import org.mule.api.transformer.Transformer;
22  import org.mule.api.transformer.TransformerException;
23  import org.mule.api.transport.DispatchException;
24  import org.mule.api.transport.OutputHandler;
25  import org.mule.endpoint.EndpointURIEndpointBuilder;
26  import org.mule.message.DefaultExceptionPayload;
27  import org.mule.transformer.TransformerChain;
28  import org.mule.transformer.types.DataTypeFactory;
29  import org.mule.transport.AbstractMessageDispatcher;
30  import org.mule.transport.http.transformers.ObjectToHttpClientMethodRequest;
31  import org.mule.util.StringUtils;
32  
33  import java.io.IOException;
34  import java.net.URI;
35  import java.util.List;
36  
37  import org.apache.commons.httpclient.Header;
38  import org.apache.commons.httpclient.HostConfiguration;
39  import org.apache.commons.httpclient.HttpClient;
40  import org.apache.commons.httpclient.HttpMethod;
41  import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
42  import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
43  import org.apache.commons.httpclient.params.HttpMethodParams;
44  import org.apache.commons.httpclient.protocol.Protocol;
45  import org.apache.commons.lang.BooleanUtils;
46  
47  /**
48   * <code>HttpClientMessageDispatcher</code> dispatches Mule events over HTTP.
49   */
50  public class HttpClientMessageDispatcher extends AbstractMessageDispatcher
51  {
52      /**
53       * Range start for http error status codes.
54       */
55      public static final int ERROR_STATUS_CODE_RANGE_START = 400;
56      public static final int REDIRECT_STATUS_CODE_RANGE_START = 300;
57      protected final HttpConnector connector;
58      private volatile HttpClient client = null;
59      private final Transformer sendTransformer;
60  
61      public HttpClientMessageDispatcher(OutboundEndpoint endpoint)
62      {
63          super(endpoint);
64          this.connector = (HttpConnector) endpoint.getConnector();
65          List<Transformer> ts = connector.getDefaultOutboundTransformers(null);
66          if (ts.size() == 1)
67          {
68              this.sendTransformer = ts.get(0);
69          }
70          else if (ts.size() == 0)
71          {
72              this.sendTransformer = new ObjectToHttpClientMethodRequest();
73              this.sendTransformer.setMuleContext(connector.getMuleContext());
74              this.sendTransformer.setEndpoint(endpoint);
75          }
76          else
77          {
78              this.sendTransformer = new TransformerChain(ts);
79          }
80      }
81  
82      @Override
83      protected void doInitialise() throws InitialisationException
84      {
85          super.doInitialise();
86          sendTransformer.initialise();
87      }
88  
89      @Override
90      protected void doConnect() throws Exception
91      {
92          if (client == null)
93          {
94              client = connector.doClientConnect();
95          }
96      }
97  
98      @Override
99      protected void doDisconnect() throws Exception
100     {
101         client = null;
102     }
103 
104     @Override
105     protected void doDispatch(MuleEvent event) throws Exception
106     {
107         HttpMethod httpMethod = getMethod(event);
108         connector.setupClientAuthorization(event, httpMethod, client, endpoint);
109 
110         try
111         {
112             execute(event, httpMethod);
113 
114             if (returnException(event, httpMethod))
115             {
116                 logger.error(httpMethod.getResponseBodyAsString());
117 
118                 Exception cause = new Exception(String.format("Http call returned a status of: %1d %1s",
119                     httpMethod.getStatusCode(), httpMethod.getStatusText()));
120                 throw new DispatchException(event, getEndpoint(), cause);
121             }
122             else if (httpMethod.getStatusCode() >= REDIRECT_STATUS_CODE_RANGE_START)
123             {
124                 if (logger.isInfoEnabled())
125                 {
126                     logger.info("Received a redirect response code: " + httpMethod.getStatusCode() + " " + httpMethod.getStatusText());
127                 }
128             }
129         }
130         finally
131         {
132             httpMethod.releaseConnection();
133         }
134     }
135 
136     protected HttpMethod execute(MuleEvent event, HttpMethod httpMethod) throws Exception
137     {
138         // TODO set connection timeout buffer etc
139         try
140         {
141             URI uri = endpoint.getEndpointURI().getUri();
142 
143             this.processCookies(event);
144             this.processMuleSession(event, httpMethod);
145 
146             // TODO can we use the return code for better reporting?
147             client.executeMethod(getHostConfig(uri), httpMethod);
148 
149             return httpMethod;
150         }
151         catch (IOException e)
152         {
153             // TODO employ dispatcher reconnection strategy at this point
154             throw new DispatchException(event, getEndpoint(), e);
155         }
156         catch (Exception e)
157         {
158             throw new DispatchException(event, getEndpoint(), e);
159         }
160 
161     }
162 
163     private void processMuleSession(MuleEvent event, HttpMethod httpMethod)
164     {
165         httpMethod.setRequestHeader(new Header(HttpConstants.HEADER_MULE_SESSION, event.getMessage().<String>getOutboundProperty(MuleProperties.MULE_SESSION_PROPERTY)));
166     }
167 
168     protected void processCookies(MuleEvent event)
169     {
170         MuleMessage msg = event.getMessage();
171 
172         Object cookiesProperty = msg.getInboundProperty(HttpConnector.HTTP_COOKIES_PROPERTY);
173         String cookieSpecProperty = (String) msg.getInboundProperty(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY);
174         processCookies(cookiesProperty, cookieSpecProperty, event);
175 
176         cookiesProperty = msg.getOutboundProperty(HttpConnector.HTTP_COOKIES_PROPERTY);
177         cookieSpecProperty = (String) msg.getOutboundProperty(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY);
178         processCookies(cookiesProperty, cookieSpecProperty, event);
179 
180         cookiesProperty = endpoint.getProperty(HttpConnector.HTTP_COOKIES_PROPERTY);
181         cookieSpecProperty = (String) endpoint.getProperty(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY);
182         processCookies(cookiesProperty, cookieSpecProperty, event);
183     }
184 
185     private void processCookies(Object cookieObject, String policy, MuleEvent event)
186     {
187         URI uri = this.getEndpoint().getEndpointURI().getUri();
188         CookieHelper.addCookiesToClient(this.client, cookieObject, policy, event, uri);
189     }
190 
191     protected HttpMethod getMethod(MuleEvent event) throws TransformerException
192     {
193         // Configure timeout. This is done here because MuleEvent.getTimeout() takes
194         // precedence and is not available before send/dispatch.
195         // Given that dispatchers are borrowed from a thread pool mutating client
196         // here is ok even though it is not ideal.
197         client.getHttpConnectionManager().getParams().setConnectionTimeout(endpoint.getResponseTimeout());
198         client.getHttpConnectionManager().getParams().setSoTimeout(endpoint.getResponseTimeout());
199         
200         MuleMessage msg = event.getMessage();
201         setPropertyFromEndpoint(event, msg, HttpConnector.HTTP_CUSTOM_HEADERS_MAP_PROPERTY);
202 
203         HttpMethod httpMethod;
204         Object body = event.getMessage().getPayload();
205 
206         if (body instanceof HttpMethod)
207         {
208             httpMethod = (HttpMethod) body;
209         }
210         else
211         {
212             httpMethod = (HttpMethod) sendTransformer.transform(msg);
213         }
214 
215         httpMethod.setFollowRedirects("true".equalsIgnoreCase((String)endpoint.getProperty("followRedirects")));
216         return httpMethod;
217     }
218 
219     protected void setPropertyFromEndpoint(MuleEvent event, MuleMessage msg, String prop)
220     {
221         Object o = msg.getOutboundProperty(prop);
222         if (o == null)
223         {
224             o = endpoint.getProperty(prop);
225             if (o != null)
226             {
227                 msg.setOutboundProperty(prop, o);
228             }
229         }
230     }
231 
232     protected HttpMethod createEntityMethod(MuleEvent event, Object body, EntityEnclosingMethod postMethod) throws TransformerException
233     {
234         HttpMethod httpMethod;
235         if (body instanceof String)
236         {
237             httpMethod = (HttpMethod) sendTransformer.transform(body.toString());
238         }
239         else if (body instanceof byte[])
240         {
241             byte[] buffer = event.transformMessage(DataType.BYTE_ARRAY_DATA_TYPE);
242             postMethod.setRequestEntity(new ByteArrayRequestEntity(buffer, event.getEncoding()));
243             httpMethod = postMethod;
244         }
245         else
246         {
247             if (!(body instanceof OutputHandler))
248             {
249                 body = event.transformMessage(DataTypeFactory.create(OutputHandler.class));
250             }
251 
252             OutputHandler outputHandler = (OutputHandler) body;
253             postMethod.setRequestEntity(new StreamPayloadRequestEntity(outputHandler, event));
254             postMethod.setContentChunked(true);
255             httpMethod = postMethod;
256         }
257 
258         return httpMethod;
259     }
260 
261     @Override
262     protected MuleMessage doSend(MuleEvent event) throws Exception
263     {
264         HttpMethod httpMethod = getMethod(event);
265         connector.setupClientAuthorization(event, httpMethod, client, endpoint);
266 
267         httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new MuleHttpMethodRetryHandler());
268         boolean releaseConn = false;
269         try
270         {
271             httpMethod = execute(event, httpMethod);
272 
273             DefaultExceptionPayload ep = null;
274 
275             if (returnException(event, httpMethod))
276             {
277                 ep = new DefaultExceptionPayload(new DispatchException(event, getEndpoint(),
278                         new HttpResponseException(httpMethod.getStatusText(), httpMethod.getStatusCode())));
279             }
280             else if (httpMethod.getStatusCode() >= REDIRECT_STATUS_CODE_RANGE_START)
281             {
282                 try
283                 {
284                     return handleRedirect(httpMethod, event);
285                 }
286                 catch (Exception e)
287                 {
288                     ep = new DefaultExceptionPayload(new DispatchException(event, getEndpoint(), e));
289                     return getResponseFromMethod(httpMethod, ep);
290                 }
291             }
292             releaseConn = httpMethod.getResponseBodyAsStream() == null;
293             return getResponseFromMethod(httpMethod, ep);
294         }
295         catch (Exception e)
296         {
297             releaseConn = true;
298             if (e instanceof DispatchException)
299             {
300                 throw (DispatchException) e;
301             }
302             throw new DispatchException(event, getEndpoint(), e);
303         }
304         finally
305         {
306             if (releaseConn)
307             {
308                 httpMethod.releaseConnection();
309             }
310         }
311     }
312 
313     protected MuleMessage handleRedirect(HttpMethod method, MuleEvent event) throws HttpResponseException, MuleException, IOException
314     {
315         String followRedirects = (String)endpoint.getProperty("followRedirects");
316         if (followRedirects==null || "false".equalsIgnoreCase(followRedirects))
317         {
318             if (logger.isInfoEnabled())
319             {
320                 logger.info("Received a redirect, but followRedirects=false. Response code: " + method.getStatusCode() + " " + method.getStatusText());
321             }
322             return getResponseFromMethod(method, null);
323         }
324         Header locationHeader = method.getResponseHeader(HttpConstants.HEADER_LOCATION);
325         if (locationHeader == null)
326         {
327             throw new HttpResponseException(method.getStatusText(), method.getStatusCode());
328         }
329         OutboundEndpoint out = new EndpointURIEndpointBuilder(locationHeader.getValue(),
330             connector.getMuleContext()).buildOutboundEndpoint();
331         MuleEvent result = out.process(event);
332         if (result != null)
333         {
334             return result.getMessage();
335         }
336         else
337         {
338             return null;
339         }
340     }
341 
342     protected MuleMessage getResponseFromMethod(HttpMethod httpMethod, ExceptionPayload ep)
343         throws IOException, MuleException
344     {
345         MuleMessage message = createMuleMessage(httpMethod);
346 
347         if (logger.isDebugEnabled())
348         {
349             logger.debug("Http response is: " + message.getOutboundProperty(HttpConnector.HTTP_STATUS_PROPERTY));
350         }
351 
352         message.setExceptionPayload(ep);
353         return message;
354     }
355 
356     protected boolean returnException(MuleEvent event, HttpMethod httpMethod)
357     {
358         String disableCheck = event.getMessage().getInvocationProperty(HttpConnector.HTTP_DISABLE_STATUS_CODE_EXCEPTION_CHECK);
359         if (disableCheck == null)
360         {
361             disableCheck = event.getMessage().getOutboundProperty(HttpConnector.HTTP_DISABLE_STATUS_CODE_EXCEPTION_CHECK); 
362         }
363         return httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START
364                 && !BooleanUtils.toBoolean(disableCheck);
365     }
366 
367     protected HostConfiguration getHostConfig(URI uri) throws Exception
368     {
369         Protocol protocol = Protocol.getProtocol(uri.getScheme().toLowerCase());
370 
371         String host = uri.getHost();
372         int port = uri.getPort();
373         HostConfiguration config = new HostConfiguration();
374         config.setHost(host, port, protocol);
375         if (StringUtils.isNotBlank(connector.getProxyHostname()))
376         {
377             // add proxy support
378             config.setProxy(connector.getProxyHostname(), connector.getProxyPort());
379         }
380         return config;
381     }
382 
383     @Override
384     protected void doDispose()
385     {
386         // template method
387     }
388 
389 }