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