View Javadoc

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