View Javadoc

1   /*
2    * $Id: HttpClientMessageDispatcher.java 10963 2008-02-22 19:18:44Z dirk.olmes $
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.transport.http;
12  
13  import org.mule.DefaultMuleMessage;
14  import org.mule.api.MuleEvent;
15  import org.mule.api.MuleMessage;
16  import org.mule.api.endpoint.OutboundEndpoint;
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.message.DefaultExceptionPayload;
22  import org.mule.transport.AbstractMessageDispatcher;
23  import org.mule.transport.http.transformers.ObjectToHttpClientMethodRequest;
24  import org.mule.util.StringUtils;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.net.URI;
29  import java.net.URISyntaxException;
30  import java.util.Iterator;
31  import java.util.Map;
32  
33  import org.apache.commons.httpclient.Cookie;
34  import org.apache.commons.httpclient.Header;
35  import org.apache.commons.httpclient.HostConfiguration;
36  import org.apache.commons.httpclient.HttpClient;
37  import org.apache.commons.httpclient.HttpMethod;
38  import org.apache.commons.httpclient.cookie.CookiePolicy;
39  import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
40  import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
41  import org.apache.commons.httpclient.params.HttpMethodParams;
42  import org.apache.commons.httpclient.protocol.Protocol;
43  
44  /**
45   * <code>HttpClientMessageDispatcher</code> dispatches Mule events over HTTP.
46   */
47  public class HttpClientMessageDispatcher extends AbstractMessageDispatcher
48  {
49      /**
50       * Range start for http error status codes.
51       */
52      public static final int ERROR_STATUS_CODE_RANGE_START = 400;
53      private final HttpConnector connector;
54      private volatile HttpClient client = null;
55      private final Transformer sendTransformer;
56  
57      public HttpClientMessageDispatcher(OutboundEndpoint endpoint)
58      {
59          super(endpoint);
60          this.connector = (HttpConnector) endpoint.getConnector();
61          this.sendTransformer = new ObjectToHttpClientMethodRequest();
62      }
63      
64      protected void doConnect() throws Exception
65      {
66          if (client == null)
67          {
68              client = connector.doClientConnect();
69          }
70      }
71  
72      protected void doDisconnect() throws Exception
73      {
74          client = null;
75      }
76  
77      protected void doDispatch(MuleEvent event) throws Exception
78      {
79          HttpMethod httpMethod = getMethod(event);
80          try
81          {
82              execute(event, httpMethod);
83              
84              if (httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START)
85              {
86                  logger.error(httpMethod.getResponseBodyAsString());
87                  throw new DispatchException(event.getMessage(), event.getEndpoint(), new Exception(
88                      "Http call returned a status of: " + httpMethod.getStatusCode() + " "
89                                      + httpMethod.getStatusText()));
90              }
91          }
92          finally
93          {
94              httpMethod.releaseConnection();
95          }
96      }
97  
98      protected HttpMethod execute(MuleEvent event, HttpMethod httpMethod)
99          throws Exception
100     {
101         // TODO set connection timeout buffer etc
102         try
103         {
104             URI uri = event.getEndpoint().getEndpointURI().getUri();
105 
106             this.processCookies(event);
107 
108             // TODO can we use the return code for better reporting?
109             client.executeMethod(getHostConfig(uri), httpMethod);
110 
111             return httpMethod;
112         }
113         catch (IOException e)
114         {
115             // TODO employ dispatcher reconnection strategy at this point
116             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
117         }
118         catch (Exception e)
119         {
120             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
121         }
122         
123     }
124 
125     protected void processCookies(MuleEvent event)
126     {
127         MuleMessage msg = event.getMessage();
128         Object cookieObject = msg.removeProperty(HttpConnector.HTTP_COOKIES_PROPERTY);
129         if (cookieObject instanceof Cookie[])
130         {
131             // cookies came in via a regular HTTP request
132             Cookie[] cookies = (Cookie[]) cookieObject;
133             if (cookies != null && cookies.length > 0)
134             {
135                 String policy = (String) msg.removeProperty(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY);
136                 client.getParams().setCookiePolicy(CookieHelper.getCookiePolicy(policy));
137                 client.getState().addCookies(cookies);
138             }
139         }
140         else if (cookieObject instanceof Map)
141         {
142             // cookies were configured on the endpoint
143             client.getParams().setCookiePolicy(CookiePolicy.RFC_2109);
144 
145             String host = this.getEndpoint().getEndpointURI().getHost();
146             String path = this.getEndpoint().getEndpointURI().getPath();
147             Map cookieMap = (Map) cookieObject;
148             Iterator keyIter = cookieMap.keySet().iterator();
149             while (keyIter.hasNext())
150             {
151                 String key = (String) keyIter.next();
152                 String value = (String) cookieMap.get(key);
153                 Cookie cookie = new Cookie(host, key, value, path, null, false);
154                 client.getState().addCookie(cookie);
155             }
156         }
157         else if (cookieObject != null)
158         {
159             throw new IllegalArgumentException("Invalid cookies " + cookieObject);
160         }
161     }
162 
163     protected HttpMethod getMethod(MuleEvent event) throws TransformerException
164     {
165         MuleMessage msg = event.getMessage();
166         setPropertyFromEndpoint(event, msg, HttpConnector.HTTP_CUSTOM_HEADERS_MAP_PROPERTY);
167         
168         HttpMethod httpMethod;
169         Object body = event.transformMessage();
170 
171         if (body instanceof HttpMethod)
172         {
173             httpMethod = (HttpMethod)body;
174         }
175         else 
176         {
177             httpMethod = (HttpMethod) sendTransformer.transform(msg);
178         }
179         
180         
181         return httpMethod;
182     }
183 
184     protected void setPropertyFromEndpoint(MuleEvent event, MuleMessage msg, String prop)
185     {
186         Object o = msg.getProperty(prop, null);
187         if (o == null) {
188             
189             o = event.getEndpoint().getProperty(prop);
190             if (o != null) {
191                 msg.setProperty(prop, o);
192             }
193         }
194     }
195 
196     protected HttpMethod createEntityMethod(MuleEvent event, Object body, EntityEnclosingMethod postMethod)
197         throws TransformerException
198     {
199         HttpMethod httpMethod;
200         if (body instanceof String)
201         {
202             ObjectToHttpClientMethodRequest trans = new ObjectToHttpClientMethodRequest();
203             httpMethod = (HttpMethod)trans.transform(body.toString());
204         }
205         else if (body instanceof byte[])
206         {
207             byte[] buffer = event.transformMessageToBytes();
208             postMethod.setRequestEntity(new ByteArrayRequestEntity(buffer, event.getEncoding()));
209             httpMethod = postMethod;
210         }
211         else 
212         {
213             if (!(body instanceof OutputHandler)) 
214             {
215                 body = event.transformMessage(OutputHandler.class);
216             }
217             
218             OutputHandler outputHandler = (OutputHandler)body;
219             postMethod.setRequestEntity(new StreamPayloadRequestEntity(outputHandler, event));
220             postMethod.setContentChunked(true);
221             httpMethod = postMethod;
222         }
223         
224         return httpMethod;
225     }
226 
227     /*
228      * (non-Javadoc)
229      * 
230      * @see org.mule.api.transport.Connector#send(org.mule.api.MuleEvent)
231      */
232     protected MuleMessage doSend(MuleEvent event) throws Exception
233     {        
234         HttpMethod httpMethod = getMethod(event);
235         connector.setupClientAuthorization(event, httpMethod, client, endpoint);
236         
237         httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new MuleHttpMethodRetryHandler());
238 
239         Object body = null;
240         boolean releaseConn = false;
241         try
242         {
243             httpMethod = execute(event, httpMethod);
244 
245             DefaultExceptionPayload ep = null;
246             if (httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START)
247             {
248                 ep = new DefaultExceptionPayload(new DispatchException(event.getMessage(), event.getEndpoint(),
249                     new Exception("Http call returned a status of: " + httpMethod.getStatusCode() + " "
250                                   + httpMethod.getStatusText())));
251             }
252             
253             
254             InputStream is = httpMethod.getResponseBodyAsStream();
255             if (is == null)
256             {
257                 body = StringUtils.EMPTY;
258                 releaseConn = true;
259             }            
260             else
261             {
262                 is = new ReleasingInputStream(is, httpMethod);
263                 body = is;
264             }
265             
266             Header[] headers = httpMethod.getResponseHeaders();
267             HttpMessageAdapter adapter = new HttpMessageAdapter(new Object[]{body, headers});
268 
269             String status = String.valueOf(httpMethod.getStatusCode());
270 
271             adapter.setProperty(HttpConnector.HTTP_STATUS_PROPERTY, status);
272             if (logger.isDebugEnabled())
273             {
274                 logger.debug("Http response is: " + status);
275             }
276             
277             MuleMessage m = new DefaultMuleMessage(adapter);
278           
279             m.setExceptionPayload(ep);
280             return m;
281         }
282         catch (Exception e)
283         {
284             releaseConn = true;
285             if (e instanceof DispatchException)
286             {
287                 throw (DispatchException) e;
288             }
289             
290             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
291         }
292         finally
293         {
294             if (releaseConn) 
295             {
296                 httpMethod.releaseConnection();
297             }
298         }
299     }
300 
301     protected HostConfiguration getHostConfig(URI uri) throws URISyntaxException
302     {
303         Protocol protocol = Protocol.getProtocol(uri.getScheme().toLowerCase());
304 
305         String host = uri.getHost();
306         int port = uri.getPort();
307         HostConfiguration config = new HostConfiguration();
308         config.setHost(host, port, protocol);
309         if (StringUtils.isNotBlank(connector.getProxyHostname()))
310         {
311             // add proxy support
312             config.setProxy(connector.getProxyHostname(), connector.getProxyPort());
313         }
314         return config;
315     }
316 
317     protected void doDispose()
318     {
319         // template method
320     }
321 }