View Javadoc

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