View Javadoc

1   /*
2    * $Id: HttpMuleMessageFactory.java 19519 2010-09-09 21:36:07Z dzapata $
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.DefaultMuleMessage;
14  import org.mule.MessageExchangePattern;
15  import org.mule.api.MuleContext;
16  import org.mule.api.MuleMessage;
17  import org.mule.api.transport.MessageTypeNotSupportedException;
18  import org.mule.transport.AbstractMuleMessageFactory;
19  import org.mule.transport.http.multipart.MultiPartInputStream;
20  import org.mule.transport.http.multipart.Part;
21  import org.mule.transport.http.multipart.PartDataSource;
22  import org.mule.util.CaseInsensitiveHashMap;
23  import org.mule.util.IOUtils;
24  import org.mule.util.PropertiesUtils;
25  import org.mule.util.StringUtils;
26  
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.net.URI;
30  import java.net.URISyntaxException;
31  import java.util.Collection;
32  import java.util.HashMap;
33  import java.util.Map;
34  
35  import javax.activation.DataHandler;
36  
37  import org.apache.commons.httpclient.Cookie;
38  import org.apache.commons.httpclient.Header;
39  import org.apache.commons.httpclient.HeaderElement;
40  import org.apache.commons.httpclient.HttpMethod;
41  import org.apache.commons.httpclient.HttpVersion;
42  import org.apache.commons.httpclient.NameValuePair;
43  import org.apache.commons.httpclient.cookie.MalformedCookieException;
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  public class HttpMuleMessageFactory extends AbstractMuleMessageFactory
48  {
49      private static Log log = LogFactory.getLog(HttpMuleMessageFactory.class);
50  
51      private boolean enableCookies = false;
52      private String cookieSpec;
53      private MessageExchangePattern exchangePattern = MessageExchangePattern.REQUEST_RESPONSE;
54      private Collection<Part> parts;
55  
56      public HttpMuleMessageFactory(MuleContext context)
57      {
58          super(context);
59      }
60  
61      @Override
62      protected Class<?>[] getSupportedTransportMessageTypes()
63      {
64          return new Class[]{HttpRequest.class, HttpMethod.class};
65      }
66  
67      @Override
68      protected Object extractPayload(Object transportMessage, String encoding) throws Exception
69      {
70          if (transportMessage instanceof HttpRequest)
71          {
72              return extractPayloadFromHttpRequest((HttpRequest) transportMessage);
73          }
74          else if (transportMessage instanceof HttpMethod)
75          {
76              return extractPayloadFromHttpMethod((HttpMethod) transportMessage);
77          }
78          else
79          {
80              // This should never happen because of the supported type checking
81              throw new MessageTypeNotSupportedException(transportMessage, getClass());
82          }
83      }
84  
85      protected Object extractPayloadFromHttpRequest(HttpRequest httpRequest) throws IOException
86      {
87          Object body = null;
88  
89          if (httpRequest.getContentType().contains("multipart/form-data"))
90          {
91              MultiPartInputStream in = new MultiPartInputStream(httpRequest.getBody(), httpRequest.getContentType(), null);
92  
93              //We need to store this so that the headers for the part can be read
94              parts = in.getParts();
95              for (Part part : parts)
96              {
97                  if (part.getName().equals("payload"))
98                  {
99                      body = part.getInputStream();
100                     break;
101                 }
102             }
103             if (body == null)
104             {
105                 throw new IllegalArgumentException("todo");
106             }
107         }
108         else
109         {
110 
111             body = httpRequest.getBody();
112 
113             // If http method is GET we use the request uri as the payload.
114             if (body == null)
115             {
116                 body = httpRequest.getRequestLine().getUri();
117             }
118             else
119             {
120                 // If we are running async we need to read stream into a byte[].
121                 // Passing along the InputStream doesn't work because the
122                 // HttpConnection gets closed and closes the InputStream, often
123                 // before it can be read.
124                 if (!exchangePattern.hasResponse())
125                 {
126                     log.debug("Reading HTTP POST InputStream into byte[] for asynchronous messaging.");
127                     body = IOUtils.toByteArray((InputStream) body);
128                 }
129             }
130         }
131 
132         return body;
133     }
134 
135     @Override
136     protected void addAttachments(DefaultMuleMessage message, Object transportMessage) throws Exception
137     {
138         if (parts != null)
139         {
140             try
141             {
142                 for (Part part : parts)
143                 {
144                     if (!part.getName().equals("payload"))
145                     {
146                         message.addInboundAttachment(part.getName(), new DataHandler(new PartDataSource(part)));
147                     }
148                 }
149             }
150             finally
151             {
152                 //Attachments are the last thing to get processed
153                 parts.clear();
154                 parts = null;
155             }
156         }
157     }
158 
159     protected Object extractPayloadFromHttpMethod(HttpMethod httpMethod) throws IOException
160     {
161         InputStream body = httpMethod.getResponseBodyAsStream();
162         if (body != null)
163         {
164             return new ReleasingInputStream(body, httpMethod);
165         }
166         else
167         {
168             return StringUtils.EMPTY;
169         }
170     }
171 
172     @Override
173     protected void addProperties(DefaultMuleMessage message, Object transportMessage) throws Exception
174     {
175         String method;
176         HttpVersion httpVersion;
177         String uri;
178         String statusCode = null;
179         Map<String, Object> headers;
180 
181         if (transportMessage instanceof HttpRequest)
182         {
183             HttpRequest httpRequest = (HttpRequest) transportMessage;
184             method = httpRequest.getRequestLine().getMethod();
185             httpVersion = httpRequest.getRequestLine().getHttpVersion();
186             uri = httpRequest.getRequestLine().getUri();
187             headers = convertHeadersToMap(httpRequest.getHeaders(), uri);
188             convertMultiPartHeaders(headers);
189         }
190         else if (transportMessage instanceof HttpMethod)
191         {
192             HttpMethod httpMethod = (HttpMethod) transportMessage;
193             method = httpMethod.getName();
194             httpVersion = HttpVersion.parse(httpMethod.getStatusLine().getHttpVersion());
195             uri = httpMethod.getURI().toString();
196             statusCode = String.valueOf(httpMethod.getStatusCode());
197             headers = convertHeadersToMap(httpMethod.getResponseHeaders(), uri);
198         }
199         else
200         {
201             // This should never happen because of the supported type checking in our superclass
202             throw new MessageTypeNotSupportedException(transportMessage, getClass());
203         }
204 
205         rewriteConnectionAndKeepAliveHeaders(headers);
206 
207         headers = processIncomingHeaders(headers);
208 
209         //Make any URI params available ans inbound message headers
210         addUriParamsAsHeaders(headers, uri);
211 
212         headers.put(HttpConnector.HTTP_METHOD_PROPERTY, method);
213         headers.put(HttpConnector.HTTP_REQUEST_PROPERTY, uri);
214         headers.put(HttpConnector.HTTP_VERSION_PROPERTY, httpVersion.toString());
215         if (enableCookies)
216         {
217             headers.put(HttpConnector.HTTP_COOKIE_SPEC_PROPERTY, cookieSpec);
218         }
219 
220         if (statusCode != null)
221         {
222             headers.put(HttpConnector.HTTP_STATUS_PROPERTY, statusCode);
223         }
224 
225         message.addInboundProperties(headers);
226 
227         // The encoding is stored as message property. To avoid overriding it from the message
228         // properties, it must be initialized last
229         initEncoding(message, headers);
230     }
231 
232     protected Map<String, Object> processIncomingHeaders(Map<String, Object> headers) throws Exception
233     {
234         Map<String, Object> outHeaders = new HashMap<String, Object>();
235 
236         for (String headerName : headers.keySet())
237         {
238             Object headerValue = headers.get(headerName);
239 
240             // fix Mule headers?
241             if (headerName.startsWith("X-MULE"))
242             {
243                 headerName = headerName.substring(2);
244             }
245 
246             // accept header & value
247             outHeaders.put(headerName, headerValue);
248         }
249 
250         return outHeaders;
251     }
252 
253     Map<String, Object> convertHeadersToMap(Header[] headersArray, String uri)
254         throws URISyntaxException
255     {
256         Map<String, Object> headersMap = new CaseInsensitiveHashMap();
257         for (int i = 0; i < headersArray.length; i++)
258         {
259             final Header header = headersArray[i];
260             // Cookies are a special case because there may be more than one
261             // cookie.
262             if (HttpConnector.HTTP_COOKIES_PROPERTY.equals(header.getName())
263                 || HttpConstants.HEADER_COOKIE.equals(header.getName()))
264             {
265                 putCookieHeaderInMapAsAServer(headersMap, header, uri);
266             }
267             else if (HttpConstants.HEADER_COOKIE_SET.equals(header.getName()))
268             {
269                 putCookieHeaderInMapAsAClient(headersMap, header, uri);
270             }
271             else
272             {
273                 if (headersMap.containsKey(header.getName()))
274                 {
275                     if (headersMap.get(header.getName()) instanceof String)
276                     {
277                         // concat
278                         headersMap.put(header.getName(),
279                             headersMap.get(header.getName()) + "," + header.getValue());
280                     }
281                     else
282                     {
283                         // override
284                         headersMap.put(header.getName(), header.getValue());
285                     }
286                 }
287                 else
288                 {
289                     headersMap.put(header.getName(), header.getValue());
290                 }
291             }
292         }
293         return headersMap;
294     }
295 
296     private void putCookieHeaderInMapAsAClient(Map<String, Object> headersMap, final Header header, String uri)
297         throws URISyntaxException
298     {
299         try
300         {
301             final Cookie[] newCookies = CookieHelper.parseCookiesAsAClient(header.getValue(), cookieSpec,
302                 new URI(uri));
303             final Object preExistentCookies = headersMap.get(HttpConstants.HEADER_COOKIE_SET);
304             final Object mergedCookie = CookieHelper.putAndMergeCookie(preExistentCookies, newCookies);
305             headersMap.put(HttpConstants.HEADER_COOKIE_SET, mergedCookie);
306         }
307         catch (MalformedCookieException e)
308         {
309             log.warn("Received an invalid cookie: " + header, e);
310         }
311     }
312 
313     private void putCookieHeaderInMapAsAServer(Map<String, Object> headersMap, final Header header, String uri)
314         throws URISyntaxException
315     {
316         if (enableCookies)
317         {
318             Cookie[] newCookies = CookieHelper.parseCookiesAsAServer(header.getValue(), new URI(uri));
319             if (newCookies.length > 0)
320             {
321                 Object oldCookies = headersMap.get(HttpConnector.HTTP_COOKIES_PROPERTY);
322                 Object mergedCookies = CookieHelper.putAndMergeCookie(oldCookies, newCookies);
323                 headersMap.put(HttpConnector.HTTP_COOKIES_PROPERTY, mergedCookies);
324             }
325         }
326     }
327 
328     private void initEncoding(MuleMessage message, Map<String, Object> headers)
329     {
330         Object contentType = headers.get(HttpConstants.HEADER_CONTENT_TYPE);
331         if (contentType != null)
332         {
333             // use HttpClient classes to parse the charset part from the Content-Type
334             // header (e.g. "text/html; charset=UTF-16BE")
335             Header contentTypeHeader = new Header(HttpConstants.HEADER_CONTENT_TYPE,
336                     contentType.toString());
337             HeaderElement values[] = contentTypeHeader.getElements();
338             if (values.length == 1)
339             {
340                 NameValuePair param = values[0].getParameterByName("charset");
341                 if (param != null)
342                 {
343                     message.setEncoding(param.getValue());
344                 }
345             }
346         }
347     }
348 
349     private void rewriteConnectionAndKeepAliveHeaders(Map<String, Object> headers)
350     {
351         // rewrite Connection and Keep-Alive headers based on HTTP version
352         String headerValue;
353         if (!isHttp11(headers))
354         {
355             String connection = (String) headers.get(HttpConstants.HEADER_CONNECTION);
356             if ((connection != null) && connection.equalsIgnoreCase("close"))
357             {
358                 headerValue = Boolean.FALSE.toString();
359             }
360             else
361             {
362                 headerValue = Boolean.TRUE.toString();
363             }
364         }
365         else
366         {
367             headerValue = (headers.get(HttpConstants.HEADER_CONNECTION) != null
368                     ? Boolean.TRUE.toString()
369                     : Boolean.FALSE.toString());
370         }
371 
372         headers.put(HttpConstants.HEADER_CONNECTION, headerValue);
373         headers.put(HttpConstants.HEADER_KEEP_ALIVE, headerValue);
374     }
375 
376     private boolean isHttp11(Map<String, Object> headers)
377     {
378         String httpVersion = (String) headers.get(HttpConnector.HTTP_VERSION_PROPERTY);
379         return !HttpConstants.HTTP10.equalsIgnoreCase(httpVersion);
380     }
381 
382     protected void addUriParamsAsHeaders(Map headers, String uri)
383     {
384         int i = uri.indexOf("?");
385         if (i > -1)
386         {
387             headers.putAll(PropertiesUtils.getPropertiesFromQueryString(uri.substring(i + 1)));
388         }
389     }
390 
391     protected void convertMultiPartHeaders(Map headers)
392     {
393         if (parts != null)
394         {
395             for (Part part : parts)
396             {
397                 if (part.getName().equals("payload"))
398                 {
399                     for (String name : part.getHeaderNames())
400                     {
401                         headers.put(name, part.getHeader(name));
402                     }
403                     break;
404                 }
405             }
406 
407         }
408 
409     }
410 
411     public void setEnableCookies(boolean enableCookies)
412     {
413         this.enableCookies = enableCookies;
414     }
415 
416     public void setCookieSpec(String cookieSpec)
417     {
418         this.cookieSpec = cookieSpec;
419     }
420 
421     public void setExchangePattern(MessageExchangePattern mep)
422     {
423         exchangePattern = mep;
424     }
425 }