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