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.api.MuleContext;
10  import org.mule.api.MuleEvent;
11  import org.mule.api.MuleMessage;
12  import org.mule.api.construct.FlowConstruct;
13  import org.mule.api.endpoint.ImmutableEndpoint;
14  import org.mule.api.endpoint.InboundEndpoint;
15  import org.mule.api.lifecycle.InitialisationException;
16  import org.mule.api.processor.MessageProcessor;
17  import org.mule.config.i18n.CoreMessages;
18  import org.mule.transport.http.ntlm.NTLMScheme;
19  import org.mule.transport.tcp.TcpConnector;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.codec.binary.Base64;
29  import org.apache.commons.httpclient.Credentials;
30  import org.apache.commons.httpclient.HttpClient;
31  import org.apache.commons.httpclient.HttpConnectionManager;
32  import org.apache.commons.httpclient.HttpMethod;
33  import org.apache.commons.httpclient.HttpState;
34  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
35  import org.apache.commons.httpclient.NTCredentials;
36  import org.apache.commons.httpclient.UsernamePasswordCredentials;
37  import org.apache.commons.httpclient.auth.AuthPolicy;
38  import org.apache.commons.httpclient.auth.AuthScope;
39  import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
40  import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread;
41  
42  /**
43   * <code>HttpConnector</code> provides a way of receiving and sending http requests
44   * and responses. The Connector itself handles dispatching http requests. The
45   * <code>HttpMessageReceiver</code> handles the receiving requests and processing
46   * of headers This endpoint recognises the following properties - <p/>
47   * <ul>
48   * <li>hostname - The hostname to send and receive http requests</li>
49   * <li>port - The port to listen on. The industry standard is 80 and if this propert
50   * is not set it will default to 80</li>
51   * <li>proxyHostname - If you access the web through a proxy, this holds the server
52   * address</li>
53   * <li>proxyPort - The port the proxy is configured on</li>
54   * <li>proxyUsername - If the proxy requires authentication supply a username</li>
55   * <li>proxyPassword - If the proxy requires authentication supply a password</li>
56   * </ul>
57   * 
58   */
59  
60  public class HttpConnector extends TcpConnector
61  {
62  
63      public static final String HTTP = "http";
64      public static final String HTTP_PREFIX = "http.";
65      
66      /**
67       * MuleEvent property to pass back the status for the response
68       */
69      public static final String HTTP_STATUS_PROPERTY = HTTP_PREFIX + "status";
70      public static final String HTTP_VERSION_PROPERTY = HTTP_PREFIX + "version";
71      
72      /**
73       * @deprecated Instead users can now add properties to the outgoing request using the OUTBOUND property scope on the message.
74       */
75      @Deprecated
76      public static final String HTTP_CUSTOM_HEADERS_MAP_PROPERTY = HTTP_PREFIX + "custom.headers";
77  
78      public static final String HTTP_METHOD_PROPERTY = HTTP_PREFIX + "method";
79      
80      /**
81       * The path and query portions of the URL being accessed. 
82       */
83      public static final String HTTP_REQUEST_PROPERTY = HTTP_PREFIX + "request";
84      
85      /**
86       * The path portion of the URL being accessed. No query string is included.
87       */
88      public static final String HTTP_REQUEST_PATH_PROPERTY = HTTP_PREFIX + "request.path";
89      
90      /**
91       * The context path of the endpoint being accessed. This is the path that the 
92       * HTTP endpoint is listening on.
93       */
94      public static final String HTTP_CONTEXT_PATH_PROPERTY = HTTP_PREFIX + "context.path";
95  
96      /**
97       * Allows the user to set a {@link org.apache.commons.httpclient.params.HttpMethodParams} object in the client
98       * request to be set on the HttpMethod request object
99       */
100     public static final String HTTP_PARAMS_PROPERTY = HTTP_PREFIX + "params";
101     public static final String HTTP_GET_BODY_PARAM_PROPERTY = HTTP_PREFIX + "get.body.param";
102     public static final String DEFAULT_HTTP_GET_BODY_PARAM_PROPERTY = "body";
103     public static final String HTTP_POST_BODY_PARAM_PROPERTY = HTTP_PREFIX + "post.body.param";
104 
105     public static final String HTTP_DISABLE_STATUS_CODE_EXCEPTION_CHECK = HTTP_PREFIX + "disable.status.code.exception.check";
106     public static final String HTTP_ENCODE_PARAMVALUE = HTTP_PREFIX + "encode.paramvalue";
107     
108     public static final Set<String> HTTP_INBOUND_PROPERTIES;
109     
110     static 
111     {
112         Set<String> props = new HashSet<String>();
113         props.add(HTTP_CONTEXT_PATH_PROPERTY);
114         props.add(HTTP_GET_BODY_PARAM_PROPERTY);
115         props.add(HTTP_METHOD_PROPERTY);
116         props.add(HTTP_PARAMS_PROPERTY);
117         props.add(HTTP_POST_BODY_PARAM_PROPERTY);
118         props.add(HTTP_REQUEST_PROPERTY);
119         props.add(HTTP_REQUEST_PATH_PROPERTY);
120         props.add(HTTP_STATUS_PROPERTY);
121         props.add(HTTP_VERSION_PROPERTY);
122         props.add(HTTP_ENCODE_PARAMVALUE);
123         HTTP_INBOUND_PROPERTIES = props;
124 
125         AuthPolicy.registerAuthScheme(AuthPolicy.NTLM, NTLMScheme.class);
126     }
127     
128     public static final String HTTP_COOKIE_SPEC_PROPERTY = "cookieSpec";
129     public static final String HTTP_COOKIES_PROPERTY = "cookies";
130     public static final String HTTP_ENABLE_COOKIES_PROPERTY = "enableCookies";
131 
132     public static final String COOKIE_SPEC_NETSCAPE = "netscape";
133     public static final String COOKIE_SPEC_RFC2109 = "rfc2109";
134 
135     private String proxyHostname = null;
136 
137     private int proxyPort = HttpConstants.DEFAULT_HTTP_PORT;
138 
139     private String proxyUsername = null;
140 
141     private String proxyPassword = null;
142 
143     private boolean proxyNtlmAuthentication;
144 
145     private String cookieSpec;
146 
147     private boolean enableCookies = false;
148 
149     protected HttpConnectionManager clientConnectionManager;
150 
151     private IdleConnectionTimeoutThread connectionCleaner;
152 
153     private boolean disableCleanupThread;
154 
155     public HttpConnector(MuleContext context)
156     {
157         super(context);
158     }
159     
160     @Override
161     protected void doInitialise() throws InitialisationException
162     {
163         super.doInitialise();
164         if (clientConnectionManager == null)
165         {
166             clientConnectionManager = new MultiThreadedHttpConnectionManager();
167             String prop = System.getProperty("mule.http.disableCleanupThread");
168             disableCleanupThread = prop != null && prop.equals("true");
169             if (!disableCleanupThread)
170             {
171                 connectionCleaner = new IdleConnectionTimeoutThread();
172                 connectionCleaner.setName("HttpClient-connection-cleaner-" + getName());
173                 connectionCleaner.addConnectionManager(clientConnectionManager);
174                 connectionCleaner.start();
175             }
176 
177             HttpConnectionManagerParams params = new HttpConnectionManagerParams();
178             if (getSendBufferSize() != INT_VALUE_NOT_SET)
179             {
180                 params.setSendBufferSize(getSendBufferSize());
181             }
182             if (getReceiveBufferSize() != INT_VALUE_NOT_SET)
183             {
184                 params.setReceiveBufferSize(getReceiveBufferSize());
185             }
186             if (getClientSoTimeout() != INT_VALUE_NOT_SET)
187             {
188                 params.setSoTimeout(getClientSoTimeout());
189             }
190             if (getSocketSoLinger() != INT_VALUE_NOT_SET)
191             {
192                 params.setLinger(getSocketSoLinger());
193             }
194 
195             params.setTcpNoDelay(isSendTcpNoDelay());
196             params.setMaxTotalConnections(dispatchers.getMaxTotal());
197             params.setDefaultMaxConnectionsPerHost(dispatchers.getMaxTotal());
198             clientConnectionManager.setParams(params);
199         }
200     }
201 
202     @Override
203     protected void doDispose()
204     {
205         if (!disableCleanupThread)
206         {
207             connectionCleaner.shutdown();
208 
209             if (!muleContext.getConfiguration().isStandalone())
210             {
211                 MultiThreadedHttpConnectionManager.shutdownAll();
212             }
213         }
214         super.doDispose();
215     }
216 
217     @Override
218     public void registerListener(InboundEndpoint endpoint, MessageProcessor listener, FlowConstruct flowConstruct) throws Exception
219     {
220         if (endpoint != null)
221         {
222             Map endpointProperties = endpoint.getProperties();
223             if (endpointProperties != null)
224             {
225                 // normalize properties for HTTP
226                 Map newProperties = new HashMap(endpointProperties.size());
227                 for (Iterator entries = endpointProperties.entrySet().iterator(); entries.hasNext();)
228                 {
229                     Map.Entry entry = (Map.Entry) entries.next();
230                     Object key = entry.getKey();
231                     Object normalizedKey = HttpConstants.ALL_HEADER_NAMES.get(key);
232                     if (normalizedKey != null)
233                     {
234                         // normalized property exists
235                         key = normalizedKey;
236                     }
237                     newProperties.put(key, entry.getValue());
238                 }
239                 // set normalized properties back on the endpoint
240                 endpoint.getProperties().clear();
241                 endpoint.getProperties().putAll(newProperties);
242             }
243         }
244         // proceed as usual
245         super.registerListener(endpoint, listener, flowConstruct);
246     }
247 
248     /**
249      * The method determines the key used to store the receiver against.
250      *
251      * @param endpoint the endpoint being registered for the service
252      * @return the key to store the newly created receiver against
253      */
254     @Override
255     protected Object getReceiverKey(FlowConstruct flowConstruct, InboundEndpoint endpoint)
256     {
257         String key = endpoint.getEndpointURI().toString();
258         int i = key.indexOf('?');
259         if (i > -1)
260         {
261             key = key.substring(0, i);
262         }
263         return key;
264     }
265 
266     /**
267      * @see org.mule.api.transport.Connector#getProtocol()
268      */
269     @Override
270     public String getProtocol()
271     {
272         return HTTP;
273     }
274 
275     public String getProxyHostname()
276     {
277         return proxyHostname;
278     }
279 
280     public String getProxyPassword()
281     {
282         return proxyPassword;
283     }
284 
285     public int getProxyPort()
286     {
287         return proxyPort;
288     }
289 
290     public String getProxyUsername()
291     {
292         return proxyUsername;
293     }
294 
295     public void setProxyHostname(String host)
296     {
297         proxyHostname = host;
298     }
299 
300     public void setProxyPassword(String string)
301     {
302         proxyPassword = string;
303     }
304 
305     public void setProxyPort(int port)
306     {
307         proxyPort = port;
308     }
309 
310     public void setProxyUsername(String string)
311     {
312         proxyUsername = string;
313     }
314 
315     @Override
316     public Map getReceivers()
317     {
318         return this.receivers;
319     }
320 
321     public String getCookieSpec()
322     {
323         return cookieSpec;
324     }
325 
326     public void setCookieSpec(String cookieSpec)
327     {
328         if (!(COOKIE_SPEC_NETSCAPE.equalsIgnoreCase(cookieSpec) || COOKIE_SPEC_RFC2109.equalsIgnoreCase(cookieSpec)))
329         {
330             throw new IllegalArgumentException(
331                 CoreMessages.propertyHasInvalidValue("cookieSpec", cookieSpec).toString());
332         }
333         this.cookieSpec = cookieSpec;
334     }
335 
336     public boolean isEnableCookies()
337     {
338         return enableCookies;
339     }
340 
341     public void setEnableCookies(boolean enableCookies)
342     {
343         this.enableCookies = enableCookies;
344     }
345 
346 
347     public HttpConnectionManager getClientConnectionManager()
348     {
349         return clientConnectionManager;
350     }
351 
352     public void setClientConnectionManager(HttpConnectionManager clientConnectionManager)
353     {
354         this.clientConnectionManager = clientConnectionManager;
355     }
356 
357     protected HttpClient doClientConnect() throws Exception
358     {
359         HttpState state = new HttpState();
360 
361         if (getProxyUsername() != null)
362         {
363             Credentials credentials;
364             if (isProxyNtlmAuthentication())
365             {
366                 credentials = new NTCredentials(getProxyUsername(), getProxyPassword(), getProxyHostname(), "");
367             }
368             else
369             {
370                 credentials = new UsernamePasswordCredentials(getProxyUsername(), getProxyPassword());
371             }
372 
373             AuthScope authscope = new AuthScope(getProxyHostname(), getProxyPort());
374 
375             state.setProxyCredentials(authscope, credentials);
376         }
377 
378         HttpClient client = new HttpClient();
379         client.setState(state);
380         client.setHttpConnectionManager(getClientConnectionManager());
381 
382         return client;
383     }
384 
385     protected void setupClientAuthorization(MuleEvent event, HttpMethod httpMethod,
386                                             HttpClient client, ImmutableEndpoint endpoint)
387             throws UnsupportedEncodingException
388     {
389         httpMethod.setDoAuthentication(true);
390         client.getParams().setAuthenticationPreemptive(true);
391 
392         if (event != null && event.getCredentials() != null)
393         {
394             MuleMessage msg = event.getMessage();
395             String authScopeHost = msg.getOutboundProperty(HTTP_PREFIX + "auth.scope.host", event.getEndpoint().getEndpointURI().getHost());
396             int authScopePort = msg.getOutboundProperty(HTTP_PREFIX + "auth.scope.port", event.getEndpoint().getEndpointURI().getPort());
397             String authScopeRealm = msg.getOutboundProperty(HTTP_PREFIX + "auth.scope.realm", AuthScope.ANY_REALM);
398             String authScopeScheme = msg.getOutboundProperty(HTTP_PREFIX + "auth.scope.scheme", AuthScope.ANY_SCHEME);
399             client.getState().setCredentials(
400                 new AuthScope(authScopeHost, authScopePort, authScopeRealm, authScopeScheme),
401                 new UsernamePasswordCredentials(event.getCredentials().getUsername(), new String(
402                     event.getCredentials().getPassword())));
403         }
404         else if (endpoint.getEndpointURI().getUserInfo() != null
405             && endpoint.getProperty(HttpConstants.HEADER_AUTHORIZATION) == null)
406         {
407             // Add User Creds
408             StringBuffer header = new StringBuffer(128);
409             header.append("Basic ");
410             header.append(new String(Base64.encodeBase64(endpoint.getEndpointURI().getUserInfo().getBytes(
411                 endpoint.getEncoding()))));
412             httpMethod.addRequestHeader(HttpConstants.HEADER_AUTHORIZATION, header.toString());
413         }
414         //TODO MULE-4501 this sohuld be removed and handled only in the ObjectToHttpRequest transformer
415         else if (event!=null && event.getMessage().getOutboundProperty(HttpConstants.HEADER_AUTHORIZATION) != null &&
416                 httpMethod.getRequestHeader(HttpConstants.HEADER_AUTHORIZATION)==null)
417         {
418             String auth = event.getMessage().getOutboundProperty(HttpConstants.HEADER_AUTHORIZATION);
419             httpMethod.addRequestHeader(HttpConstants.HEADER_AUTHORIZATION, auth);
420         }
421         else
422         {
423             // don't use preemptive if there are no credentials to send
424             client.getParams().setAuthenticationPreemptive(false);
425         }
426     }
427 
428     /**
429      * Ensures that the supplied URL starts with a '/'.
430      */
431     public static String normalizeUrl(String url)
432     {
433         if (url == null) 
434         {
435             url = "/";
436         } 
437         else if (!url.startsWith("/")) 
438         {
439             url = "/" + url;
440         }
441         return url;
442     }
443 
444     public boolean isProxyNtlmAuthentication()
445     {
446         return proxyNtlmAuthentication;
447     }
448 
449     public void setProxyNtlmAuthentication(boolean proxyNtlmAuthentication)
450     {
451         this.proxyNtlmAuthentication = proxyNtlmAuthentication;
452     }
453 }