View Javadoc

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