View Javadoc

1   /*
2    * Copyright 2002-2009 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.transport.http;
18  
19  import java.io.IOException;
20  import java.net.URI;
21  import java.util.Iterator;
22  import java.util.Properties;
23  
24  import org.apache.commons.httpclient.Credentials;
25  import org.apache.commons.httpclient.HostConfiguration;
26  import org.apache.commons.httpclient.HttpClient;
27  import org.apache.commons.httpclient.HttpConnectionManager;
28  import org.apache.commons.httpclient.HttpURL;
29  import org.apache.commons.httpclient.HttpsURL;
30  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
31  import org.apache.commons.httpclient.NTCredentials;
32  import org.apache.commons.httpclient.URIException;
33  import org.apache.commons.httpclient.UsernamePasswordCredentials;
34  import org.apache.commons.httpclient.auth.AuthScope;
35  import org.apache.commons.httpclient.methods.PostMethod;
36  
37  import org.springframework.beans.factory.DisposableBean;
38  import org.springframework.beans.factory.InitializingBean;
39  import org.springframework.util.Assert;
40  import org.springframework.ws.transport.WebServiceConnection;
41  
42  /**
43   * <code>WebServiceMessageSender</code> implementation that uses <a href="http://jakarta.apache.org/commons/httpclient">Jakarta
44   * Commons HttpClient</a> to execute POST requests.
45   * <p/>
46   * Allows to use a preconfigured HttpClient instance, potentially with authentication, HTTP connection pooling, etc.
47   * Authentication can also be set by injecting a {@link Credentials} instance (such as the {@link
48   * UsernamePasswordCredentials}).
49   *
50   * @author Arjen Poutsma
51   * @see HttpUrlConnectionMessageSender
52   * @see HttpClient
53   * @see #setCredentials(Credentials)
54   * @since 1.0.0
55   */
56  public class CommonsHttpMessageSender extends AbstractHttpWebServiceMessageSender
57          implements InitializingBean, DisposableBean {
58  
59      private static final int DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS = (60 * 1000);
60  
61      private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
62  
63      private HttpClient httpClient;
64  
65      private Credentials credentials;
66  
67      private AuthScope authScope;
68  
69      /**
70       * Create a new instance of the <code>CommonsHttpMessageSender</code> with a default {@link HttpClient} that uses a
71       * default {@link MultiThreadedHttpConnectionManager}.
72       */
73      public CommonsHttpMessageSender() {
74          httpClient = new HttpClient(new MultiThreadedHttpConnectionManager());
75          setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS);
76          setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
77      }
78  
79      /**
80       * Create a new instance of the <code>CommonsHttpMessageSender</code> with the given  {@link HttpClient} instance.
81       *
82       * @param httpClient the HttpClient instance to use for this sender
83       */
84      public CommonsHttpMessageSender(HttpClient httpClient) {
85          Assert.notNull(httpClient, "httpClient must not be null");
86          this.httpClient = httpClient;
87      }
88  
89      /** Returns the <code>HttpClient</code> used by this message sender. */
90      public HttpClient getHttpClient() {
91          return httpClient;
92      }
93  
94      /** Set the <code>HttpClient</code> used by this message sender. */
95      public void setHttpClient(HttpClient httpClient) {
96          this.httpClient = httpClient;
97      }
98  
99      /** Returns the credentials to be used. */
100     public Credentials getCredentials() {
101         return credentials;
102     }
103 
104     /**
105      * Sets the credentials to be used. If not set, no authentication is done.
106      *
107      * @see UsernamePasswordCredentials
108      * @see NTCredentials
109      */
110     public void setCredentials(Credentials credentials) {
111         this.credentials = credentials;
112     }
113 
114     /**
115      * Sets the timeout until a connection is etablished. A value of 0 means <em>never</em> timeout.
116      *
117      * @param timeout the timeout value in milliseconds
118      * @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setConnectionTimeout(int)
119      */
120     public void setConnectionTimeout(int timeout) {
121         if (timeout < 0) {
122             throw new IllegalArgumentException("timeout must be a non-negative value");
123         }
124         getHttpClient().getHttpConnectionManager().getParams().setConnectionTimeout(timeout);
125     }
126 
127     /**
128      * Set the socket read timeout for the underlying HttpClient. A value of 0 means <em>never</em> timeout.
129      *
130      * @param timeout the timeout value in milliseconds
131      * @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setSoTimeout(int)
132      */
133     public void setReadTimeout(int timeout) {
134         if (timeout < 0) {
135             throw new IllegalArgumentException("timeout must be a non-negative value");
136         }
137         getHttpClient().getHttpConnectionManager().getParams().setSoTimeout(timeout);
138     }
139 
140     /**
141      * Sets the maximum number of connections allowed for the underlying HttpClient.
142      *
143      * @param maxTotalConnections the maximum number of connections allowed
144      * @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setMaxTotalConnections(int)
145      */
146     public void setMaxTotalConnections(int maxTotalConnections) {
147         if (maxTotalConnections <= 0) {
148             throw new IllegalArgumentException("maxTotalConnections must be a positive value");
149         }
150         getHttpClient().getHttpConnectionManager().getParams().setMaxTotalConnections(maxTotalConnections);
151     }
152 
153     /**
154      * Sets the maximum number of connections per host for the underlying HttpClient. The maximum number of connections
155      * per host can be set in a form accepted by the {@code java.util.Properties} class, like as follows:
156      * <pre>
157      * https://www.example.com=1
158      * http://www.example.com:8080=7
159      * www.springframework.org=10
160      * *=5
161      * </pre>
162      * The host can be specified as hostname, or as URI (with scheme and port). The special host name {@code *} can be
163      * used to specify {@link org.apache.commons.httpclient.HostConfiguration#ANY_HOST_CONFIGURATION}.
164      *
165      * @param maxConnectionsPerHost a properties object specifying the maximum number of connection
166      * @see org.apache.commons.httpclient.params.HttpConnectionManagerParams#setMaxConnectionsPerHost(org.apache.commons.httpclient.HostConfiguration,
167      *      int)
168      */
169     public void setMaxConnectionsPerHost(Properties maxConnectionsPerHost) throws URIException {
170         for (Iterator iterator = maxConnectionsPerHost.keySet().iterator(); iterator.hasNext();) {
171             String host = (String) iterator.next();
172             HostConfiguration hostConfiguration = new HostConfiguration();
173             if ("*".equals(host)) {
174                 hostConfiguration = HostConfiguration.ANY_HOST_CONFIGURATION;
175             }
176             else if (host.startsWith("http://")) {
177                 HttpURL httpURL = new HttpURL(host);
178                 hostConfiguration.setHost(httpURL);
179             }
180             else if (host.startsWith("https://")) {
181                 HttpsURL httpsURL = new HttpsURL(host);
182                 hostConfiguration.setHost(httpsURL);
183             }
184             else {
185                 hostConfiguration.setHost(host);
186             }
187             int maxHostConnections = Integer.parseInt(maxConnectionsPerHost.getProperty(host));
188             getHttpClient().getHttpConnectionManager().getParams()
189                     .setMaxConnectionsPerHost(hostConfiguration, maxHostConnections);
190         }
191     }
192 
193     /**
194      * Returns the authentication scope to be used. Only used when the <code>credentials</code> property has been set.
195      * <p/>
196      * By default, the {@link AuthScope#ANY} is returned.
197      */
198     public AuthScope getAuthScope() {
199         return authScope != null ? authScope : AuthScope.ANY;
200     }
201 
202     /**
203      * Sets the authentication scope to be used. Only used when the <code>credentials</code> property has been set.
204      * <p/>
205      * By default, the {@link AuthScope#ANY} is used.
206      *
207      * @see #setCredentials(Credentials)
208      */
209     public void setAuthScope(AuthScope authScope) {
210         this.authScope = authScope;
211     }
212 
213     public void afterPropertiesSet() throws Exception {
214         if (getCredentials() != null) {
215             getHttpClient().getState().setCredentials(getAuthScope(), getCredentials());
216             getHttpClient().getParams().setAuthenticationPreemptive(true);
217         }
218     }
219 
220     public void destroy() throws Exception {
221         HttpConnectionManager connectionManager = getHttpClient().getHttpConnectionManager();
222         if (connectionManager instanceof MultiThreadedHttpConnectionManager) {
223             ((MultiThreadedHttpConnectionManager) connectionManager).shutdown();
224         }
225     }
226 
227     public WebServiceConnection createConnection(URI uri) throws IOException {
228         PostMethod postMethod = new PostMethod(uri.toString());
229         if (isAcceptGzipEncoding()) {
230             postMethod.addRequestHeader(HttpTransportConstants.HEADER_ACCEPT_ENCODING,
231                     HttpTransportConstants.CONTENT_ENCODING_GZIP);
232         }
233         return new CommonsHttpConnection(getHttpClient(), postMethod);
234     }
235 
236 }
237