View Javadoc

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