View Javadoc

1   /*
2    * $Id: HttpClientMessageDispatcher.java 8426 2007-09-15 01:31:06Z aperepel $
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.providers.http;
12  
13  import org.mule.impl.MuleMessage;
14  import org.mule.impl.message.ExceptionPayload;
15  import org.mule.providers.AbstractMessageDispatcher;
16  import org.mule.providers.http.i18n.HttpMessages;
17  import org.mule.providers.http.transformers.HttpClientMethodResponseToObject;
18  import org.mule.providers.http.transformers.ObjectToHttpClientMethodRequest;
19  import org.mule.providers.streaming.StreamMessageAdapter;
20  import org.mule.umo.UMOEvent;
21  import org.mule.umo.UMOMessage;
22  import org.mule.umo.endpoint.UMOImmutableEndpoint;
23  import org.mule.umo.provider.DispatchException;
24  import org.mule.umo.provider.ReceiveException;
25  import org.mule.umo.provider.UMOMessageAdapter;
26  import org.mule.umo.provider.UMOStreamMessageAdapter;
27  import org.mule.umo.transformer.TransformerException;
28  import org.mule.umo.transformer.UMOTransformer;
29  import org.mule.util.StringUtils;
30  
31  import java.io.IOException;
32  import java.io.OutputStream;
33  import java.net.URI;
34  import java.net.URISyntaxException;
35  import java.util.Iterator;
36  import java.util.Map;
37  import java.util.Properties;
38  
39  import org.apache.commons.codec.binary.Base64;
40  import org.apache.commons.httpclient.Cookie;
41  import org.apache.commons.httpclient.Header;
42  import org.apache.commons.httpclient.HostConfiguration;
43  import org.apache.commons.httpclient.HttpClient;
44  import org.apache.commons.httpclient.HttpMethod;
45  import org.apache.commons.httpclient.HttpState;
46  import org.apache.commons.httpclient.HttpStatus;
47  import org.apache.commons.httpclient.UsernamePasswordCredentials;
48  import org.apache.commons.httpclient.auth.AuthScope;
49  import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
50  import org.apache.commons.httpclient.methods.DeleteMethod;
51  import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
52  import org.apache.commons.httpclient.methods.GetMethod;
53  import org.apache.commons.httpclient.methods.HeadMethod;
54  import org.apache.commons.httpclient.methods.OptionsMethod;
55  import org.apache.commons.httpclient.methods.PostMethod;
56  import org.apache.commons.httpclient.methods.PutMethod;
57  import org.apache.commons.httpclient.methods.RequestEntity;
58  import org.apache.commons.httpclient.methods.TraceMethod;
59  import org.apache.commons.httpclient.params.HttpMethodParams;
60  import org.apache.commons.httpclient.protocol.Protocol;
61  import org.apache.commons.io.IOUtils;
62  
63  /**
64   * <code>HttpClientMessageDispatcher</code> dispatches Mule events over HTTP.
65   */
66  public class HttpClientMessageDispatcher extends AbstractMessageDispatcher
67  {
68      /**
69       * Range start for http error status codes.
70       */
71      public static final int ERROR_STATUS_CODE_RANGE_START = 400;
72      private final HttpConnector connector;
73      private volatile HttpClient client = null;
74      private final UMOTransformer receiveTransformer;
75  
76      public HttpClientMessageDispatcher(UMOImmutableEndpoint endpoint)
77      {
78          super(endpoint);
79          this.connector = (HttpConnector) endpoint.getConnector();
80          this.receiveTransformer = new HttpClientMethodResponseToObject();
81      }
82  
83      protected void doConnect() throws Exception
84      {
85          if (client == null)
86          {
87              HttpState state = new HttpState();
88  
89              if (connector.getProxyUsername() != null)
90              {
91                  state.setProxyCredentials(new AuthScope(null, -1, null, null),
92                      new UsernamePasswordCredentials(connector.getProxyUsername(),
93                          connector.getProxyPassword()));
94              }
95  
96              client = new HttpClient();
97              client.setState(state);
98              client.setHttpConnectionManager(connector.getClientConnectionManager());
99          }
100 
101     }
102 
103     protected void doDisconnect() throws Exception
104     {
105         client = null;
106     }
107 
108     protected void doDispatch(UMOEvent event) throws Exception
109     {
110         HttpMethod httpMethod = getMethod(event);
111         execute(event, httpMethod, true);
112         if (httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START)
113         {
114             logger.error(httpMethod.getResponseBodyAsString());
115             throw new DispatchException(event.getMessage(), event.getEndpoint(), new Exception(
116                 "Http call returned a status of: " + httpMethod.getStatusCode() + " "
117                                 + httpMethod.getStatusText()));
118         }
119     }
120 
121     /**
122      * Make a specific request to the underlying transport
123      * 
124      * @param timeout the maximum time the operation should block before returning.
125      *            The call should return immediately if there is data available. If
126      *            no data becomes available before the timeout elapses, null will be
127      *            returned
128      * @return the result of the request wrapped in a UMOMessage object. Null will be
129      *         returned if no data was avaialable
130      * @throws Exception if the call to the underlying protocal cuases an exception
131      */
132     protected UMOMessage doReceive(long timeout) throws Exception
133     {
134         HttpMethod httpMethod = new GetMethod(endpoint.getEndpointURI().getAddress());
135         httpMethod.setDoAuthentication(true);
136         if (endpoint.getEndpointURI().getUserInfo() != null
137             && endpoint.getProperty(HttpConstants.HEADER_AUTHORIZATION) == null)
138         {
139             // Add User Creds
140             StringBuffer header = new StringBuffer(128);
141             header.append("Basic ");
142             header.append(new String(Base64.encodeBase64(endpoint.getEndpointURI().getUserInfo().getBytes(
143                 endpoint.getEncoding()))));
144             httpMethod.addRequestHeader(HttpConstants.HEADER_AUTHORIZATION, header.toString());
145         }
146         try
147         {
148             HttpClient client = new HttpClient();
149             client.executeMethod(httpMethod);
150 
151             if (httpMethod.getStatusCode() == HttpStatus.SC_OK)
152             {
153                 return (UMOMessage) receiveTransformer.transform(httpMethod);
154             }
155             else
156             {
157                 throw new ReceiveException(
158                     HttpMessages.requestFailedWithStatus(httpMethod.getStatusLine().toString()),
159                     endpoint, timeout);
160             }
161         }
162         catch (ReceiveException e)
163         {
164             throw e;
165         }
166         catch (Exception e)
167         {
168             throw new ReceiveException(endpoint, timeout, e);
169         }
170         finally
171         {
172             httpMethod.releaseConnection();
173         }
174     }
175 
176     protected HttpMethod execute(UMOEvent event, HttpMethod httpMethod, boolean closeConnection)
177         throws Exception
178     {
179         // TODO set connection timeout buffer etc
180         try
181         {
182             URI uri = event.getEndpoint().getEndpointURI().getUri();
183 
184             this.processCookies(event);
185 
186             // TODO can we use the return code for better reporting?
187             client.executeMethod(getHostConfig(uri), httpMethod);
188 
189             return httpMethod;
190         }
191         catch (IOException e)
192         {
193             // TODO employ dispatcher reconnection strategy at this point
194             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
195         }
196         catch (Exception e)
197         {
198             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
199         }
200         finally
201         {
202             if (httpMethod != null && closeConnection)
203             {
204                 httpMethod.releaseConnection();
205             }
206         }
207     }
208 
209     protected void processCookies(UMOEvent event)
210     {
211         UMOMessage msg = event.getMessage();
212         Cookie[] cookies = (Cookie[]) msg.removeProperty(HttpConnector.HTTP_COOKIES_PROPERTY);
213         if (cookies != null && cookies.length > 0)
214         {
215             String policy = (String) msg.removeProperty(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY);
216             client.getParams().setCookiePolicy(CookieHelper.getCookiePolicy(policy));
217             client.getState().addCookies(cookies);
218         }
219     }
220 
221     protected HttpMethod getMethod(UMOEvent event) throws TransformerException
222     {
223         UMOMessage msg = event.getMessage();
224         String method = msg.getStringProperty(HttpConnector.HTTP_METHOD_PROPERTY, HttpConstants.METHOD_POST);
225         URI uri = event.getEndpoint().getEndpointURI().getUri();
226         HttpMethod httpMethod;
227         Object body = event.getTransformedMessage();
228 
229         if (body instanceof HttpMethod)
230         {
231             httpMethod = (HttpMethod)body;
232         }
233         else if (HttpConstants.METHOD_GET.equalsIgnoreCase(method))
234         {
235             httpMethod = new GetMethod(uri.toString());
236         }
237         else if (HttpConstants.METHOD_PUT.equalsIgnoreCase(method))
238         {
239             PutMethod postMethod = new PutMethod(uri.toString());
240 
241             httpMethod = createEntityMethod(event, body, postMethod);
242         }
243         else if (HttpConstants.METHOD_POST.equalsIgnoreCase(method))
244         {
245             PostMethod postMethod = new PostMethod(uri.toString());
246 
247             httpMethod = createEntityMethod(event, body, postMethod);
248         }
249         else if (HttpConstants.METHOD_DELETE.equalsIgnoreCase(method))
250         {
251             httpMethod = new DeleteMethod(uri.toString());
252         }
253         else if (HttpConstants.METHOD_HEAD.equalsIgnoreCase(method))
254         {
255             httpMethod = new HeadMethod(uri.toString());
256         }
257         else if (HttpConstants.METHOD_OPTIONS.equalsIgnoreCase(method))
258         {
259             httpMethod = new OptionsMethod(uri.toString());
260         }
261         else if (HttpConstants.METHOD_TRACE.equalsIgnoreCase(method))
262         {
263             httpMethod = new TraceMethod(uri.toString());
264         }
265         else
266         {
267             throw new TransformerException(HttpMessages.unsupportedMethod(method));
268         }
269         
270         httpMethod.setDoAuthentication(true);
271         if (event.getCredentials() != null)
272         {
273             String authScopeHost = msg.getStringProperty("http.auth.scope.host", null);
274             int authScopePort = msg.getIntProperty("http.auth.scope.port", -1);
275             String authScopeRealm = msg.getStringProperty("http.auth.scope.realm", null);
276             String authScopeScheme = msg.getStringProperty("http.auth.scope.scheme", null);
277             client.getState().setCredentials(
278                 new AuthScope(authScopeHost, authScopePort, authScopeRealm, authScopeScheme),
279                 new UsernamePasswordCredentials(event.getCredentials().getUsername(), new String(
280                     event.getCredentials().getPassword())));
281             client.getParams().setAuthenticationPreemptive(true);
282         }
283         else
284         {
285             // don't use preemptive if there are no credentials to send
286             client.getParams().setAuthenticationPreemptive(false);
287         }
288         return httpMethod;
289     }
290 
291     protected HttpMethod createEntityMethod(UMOEvent event, Object body, EntityEnclosingMethod postMethod)
292         throws TransformerException
293     {
294         HttpMethod httpMethod;
295         if (body instanceof String)
296         {
297             ObjectToHttpClientMethodRequest trans = new ObjectToHttpClientMethodRequest();
298             httpMethod = (HttpMethod)trans.transform(body.toString());
299         }
300         else if (body instanceof UMOStreamMessageAdapter)
301         {
302             UMOStreamMessageAdapter sma = (UMOStreamMessageAdapter)body;
303             Map headers = sma.getOutputHandler().getHeaders(event);
304             for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();)
305             {
306                 Map.Entry entry = (Map.Entry)iterator.next();
307                 postMethod.addRequestHeader((String)entry.getKey(), (String)entry.getValue());
308             }
309             postMethod.setRequestEntity(new StreamPayloadRequestEntity((StreamMessageAdapter)body, event));
310             postMethod.setContentChunked(true);
311             httpMethod = postMethod;
312         }
313         else
314         {
315             byte[] buffer = event.getTransformedMessageAsBytes();
316             postMethod.setRequestEntity(new ByteArrayRequestEntity(buffer, event.getEncoding()));
317             httpMethod = postMethod;
318         }
319         return httpMethod;
320     }
321 
322     /*
323      * (non-Javadoc)
324      * 
325      * @see org.mule.umo.provider.UMOConnector#send(org.mule.umo.UMOEvent)
326      */
327     protected UMOMessage doSend(UMOEvent event) throws Exception
328     {        
329         HttpMethod httpMethod = getMethod(event);
330         httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new MuleHttpMethodRetryHandler());
331 
332         httpMethod = execute(event, httpMethod, false);
333         
334         try
335         {
336             Properties h = new Properties();
337             Header[] headers = httpMethod.getResponseHeaders();
338             for (int i = 0; i < headers.length; i++)
339             {
340                 h.setProperty(headers[i].getName(), headers[i].getValue());
341             }
342 
343             String status = String.valueOf(httpMethod.getStatusCode());
344 
345             h.setProperty(HttpConnector.HTTP_STATUS_PROPERTY, status);
346             if (logger.isDebugEnabled())
347             {
348                 logger.debug("Http response is: " + status);
349             }
350             ExceptionPayload ep = null;
351             if (httpMethod.getStatusCode() >= ERROR_STATUS_CODE_RANGE_START)
352             {
353                 ep = new ExceptionPayload(new DispatchException(event.getMessage(), event.getEndpoint(),
354                     new Exception("Http call returned a status of: " + httpMethod.getStatusCode() + " "
355                                   + httpMethod.getStatusText())));
356             }
357             UMOMessage m;
358             // text or binary content?
359             Header header = httpMethod.getResponseHeader(HttpConstants.HEADER_CONTENT_TYPE);
360             if ((header != null) && event.isStreaming())
361             {
362                 HttpStreamMessageAdapter sp = (HttpStreamMessageAdapter)connector.getStreamMessageAdapter(
363                     httpMethod.getResponseBodyAsStream(), null);
364                 sp.setHttpMethod(httpMethod);
365                 m = new MuleMessage(sp, h);
366             }
367             else
368             {
369                 Object body = IOUtils.toByteArray(httpMethod.getResponseBodyAsStream());
370                 if (body == null)
371                 {
372                     body = StringUtils.EMPTY;
373                 }
374                 UMOMessageAdapter adapter = connector.getMessageAdapter(new Object[]{body, h});
375                 m = new MuleMessage(adapter);
376             }
377             m.setExceptionPayload(ep);
378             return m;
379         }
380         catch (Exception e)
381         {
382             throw new DispatchException(event.getMessage(), event.getEndpoint(), e);
383         }
384         finally
385         {
386             if (httpMethod != null && !event.isStreaming())
387             {
388                 httpMethod.releaseConnection();
389             }
390         }
391     }
392 
393     protected HostConfiguration getHostConfig(URI uri) throws URISyntaxException
394     {
395         Protocol protocol = Protocol.getProtocol(uri.getScheme().toLowerCase());
396 
397         String host = uri.getHost();
398         int port = uri.getPort();
399         HostConfiguration config = new HostConfiguration();
400         config.setHost(host, port, protocol);
401         if (StringUtils.isNotBlank(connector.getProxyHostname()))
402         {
403             // add proxy support
404             config.setProxy(connector.getProxyHostname(), connector.getProxyPort());
405         }
406         return config;
407     }
408 
409     protected void doDispose()
410     {
411         // template method
412     }
413 
414     protected class StreamPayloadRequestEntity implements RequestEntity
415     {
416         private UMOStreamMessageAdapter messageAdapter;
417         private UMOEvent event;
418 
419         public StreamPayloadRequestEntity(UMOStreamMessageAdapter messageAdapter, UMOEvent event)
420         {
421             this.messageAdapter = messageAdapter;
422             this.event = event;
423         }
424 
425         public boolean isRepeatable()
426         {
427             return true;
428         }
429 
430         public void writeRequest(OutputStream outputStream) throws IOException
431         {
432             messageAdapter.getOutputHandler().write(event, outputStream);
433         }
434 
435         public long getContentLength()
436         {
437             return -1L;
438         }
439 
440         public String getContentType()
441         {
442             return event.getMessage().getStringProperty(HttpConstants.HEADER_CONTENT_TYPE, null);
443         }
444     }
445 
446 }