View Javadoc

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