View Javadoc

1   package com.atlassian.sal.core.net;
2   
3   import com.atlassian.sal.api.net.Request;
4   import com.atlassian.sal.api.net.RequestFilePart;
5   import com.atlassian.sal.api.net.ResponseException;
6   import com.atlassian.sal.api.net.ResponseHandler;
7   import com.atlassian.sal.api.net.ResponseProtocolException;
8   import com.atlassian.sal.api.net.ResponseStatusException;
9   import com.atlassian.sal.api.net.ReturningResponseHandler;
10  import com.google.common.base.Preconditions;
11  import org.apache.http.HttpHost;
12  import org.apache.http.NameValuePair;
13  import org.apache.http.auth.AuthScope;
14  import org.apache.http.auth.UsernamePasswordCredentials;
15  import org.apache.http.client.ClientProtocolException;
16  import org.apache.http.client.config.RequestConfig;
17  import org.apache.http.client.entity.UrlEncodedFormEntity;
18  import org.apache.http.client.methods.CloseableHttpResponse;
19  import org.apache.http.client.methods.HttpPost;
20  import org.apache.http.client.methods.HttpPut;
21  import org.apache.http.client.methods.HttpUriRequest;
22  import org.apache.http.client.methods.RequestBuilder;
23  import org.apache.http.client.protocol.HttpClientContext;
24  import org.apache.http.entity.ContentType;
25  import org.apache.http.entity.StringEntity;
26  import org.apache.http.entity.mime.MultipartEntityBuilder;
27  import org.apache.http.impl.auth.BasicScheme;
28  import org.apache.http.impl.client.CloseableHttpClient;
29  import org.apache.http.message.BasicNameValuePair;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  import java.io.IOException;
34  import java.nio.charset.Charset;
35  import java.nio.charset.StandardCharsets;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collections;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  
43  /**
44   * HttpClient implementation of Request interface
45   */
46  public class HttpClientRequest implements Request<HttpClientRequest, HttpClientResponse>
47  {
48      private static final Logger log = LoggerFactory.getLogger(HttpClientRequest.class);
49  
50      private final CloseableHttpClient httpClient;
51      protected final HttpClientContext httpClientContext;
52      final RequestBuilder requestBuilder;
53      final RequestConfig.Builder requestConfigBuilder;
54  
55      // Unfortunately we need to keep a list of the headers
56      private final Map<String, List<String>> headers = new HashMap<>();
57  
58      public HttpClientRequest(CloseableHttpClient httpClient, HttpClientContext httpClientContext, MethodType initialMethodType, String initialUrl)
59      {
60          this.httpClient = httpClient;
61          this.httpClientContext = httpClientContext;
62          this.requestBuilder = RequestBuilder.create(initialMethodType.toString()).setUri(initialUrl);
63  
64          final ConnectionConfig connectionConfig = new SystemPropertiesConnectionConfig();
65          this.requestConfigBuilder = RequestConfig.custom()
66                  .setConnectTimeout(connectionConfig.getConnectionTimeout())
67                  .setSocketTimeout(connectionConfig.getSocketTimeout())
68                  .setMaxRedirects(connectionConfig.getMaxRedirects());
69      }
70  
71      @Override
72      public String execute() throws ResponseException
73      {
74          return executeAndReturn(new ReturningResponseHandler<HttpClientResponse, String>()
75          {
76              public String handle(final HttpClientResponse response) throws ResponseException
77              {
78                  if (!response.isSuccessful())
79                  {
80                      throw new ResponseStatusException("Unexpected response received. Status code: " + response.getStatusCode(),
81                              response);
82                  }
83                  return response.getResponseBodyAsString();
84              }
85          });
86      }
87  
88      @Override
89      public void execute(final ResponseHandler<? super HttpClientResponse> responseHandler) throws ResponseException
90      {
91          executeAndReturn(new ReturningResponseHandler<HttpClientResponse, Void>()
92          {
93              public Void handle(final HttpClientResponse response) throws ResponseException
94              {
95                  responseHandler.handle(response);
96                  return null;
97              }
98          });
99      }
100 
101     @Override
102     public <RET> RET executeAndReturn(final ReturningResponseHandler<? super HttpClientResponse, RET> responseHandler)
103             throws ResponseException
104     {
105         final HttpUriRequest request = requestBuilder.setConfig(requestConfigBuilder.build()).build();
106         log.debug("Executing request:{}", request);
107 
108         try (final CloseableHttpResponse response = httpClient.execute(request, httpClientContext))
109         {
110             return responseHandler.handle(new HttpClientResponse(response));
111         }
112         catch (ClientProtocolException cpe)
113         {
114             throw new ResponseProtocolException(cpe);
115         }
116         catch (IOException e)
117         {
118             throw new ResponseException(e);
119         }
120     }
121 
122     @Override
123     public Map<String, List<String>> getHeaders()
124     {
125         return Collections.unmodifiableMap(headers);
126     }
127 
128     @Override
129     public HttpClientRequest addBasicAuthentication(final String hostname, final String username, final String password)
130     {
131         httpClientContext.getCredentialsProvider().setCredentials(
132                 new AuthScope(hostname, AuthScope.ANY_PORT),
133                 new UsernamePasswordCredentials(username, password));
134         httpClientContext.getAuthCache().put(new HttpHost(hostname), new BasicScheme());
135         return this;
136     }
137 
138     @Override
139     public HttpClientRequest setConnectionTimeout(final int connectionTimeout)
140     {
141         requestConfigBuilder.setConnectionRequestTimeout(connectionTimeout);
142         return this;
143     }
144 
145     @Override
146     public HttpClientRequest setSoTimeout(final int soTimeout)
147     {
148         requestConfigBuilder.setSocketTimeout(soTimeout);
149         return this;
150     }
151 
152     @Override
153     public HttpClientRequest setUrl(final String url)
154     {
155         requestBuilder.setUri(url);
156         return this;
157     }
158 
159     @Override
160     public HttpClientRequest setRequestBody(final String requestBody)
161     {
162         return setRequestBody(requestBody, ContentType.TEXT_PLAIN.getMimeType());
163     }
164 
165     @Override
166     public HttpClientRequest setRequestBody(final String requestBodyString, final String contentTypeString)
167     {
168         Preconditions.checkNotNull(requestBodyString);
169         Preconditions.checkNotNull(contentTypeString);
170         Preconditions.checkState(isRequestBodyMethod(), "Only PUT or POST methods accept a request body.");
171 
172         requestBuilder.setEntity(new StringEntity(requestBodyString, ContentType.create(contentTypeString, StandardCharsets.UTF_8)));
173         return this;
174     }
175 
176     @Override
177     public HttpClientRequest setFiles(final List<RequestFilePart> requestBodyFiles)
178     {
179         Preconditions.checkNotNull(requestBodyFiles);
180         Preconditions.checkState(isRequestBodyMethod(), "Only PUT or POST methods accept a request body.");
181 
182         final MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
183 
184         for (RequestFilePart requestBodyFile : requestBodyFiles)
185         {
186             final ContentType fileContentType = ContentType.create(requestBodyFile.getContentType());
187             multipartEntityBuilder.addBinaryBody(requestBodyFile.getParameterName(), requestBodyFile.getFile(), fileContentType, requestBodyFile.getFileName());
188         }
189 
190         requestBuilder.setEntity(multipartEntityBuilder.build());
191         return this;
192     }
193 
194     @Override
195     public HttpClientRequest addRequestParameters(final String... params)
196     {
197         Preconditions.checkNotNull(params);
198         Preconditions.checkState(isRequestBodyMethod(), "Only PUT or POST methods accept a request body.");
199 
200         if (params.length % 2 != 0)
201         {
202             throw new IllegalArgumentException("You must enter an even number of arguments.");
203         }
204 
205         for (int i = 0; i < params.length; i += 2)
206         {
207             final String name = params[i];
208             final String value = params[i + 1];
209             requestBuilder.addParameter(name, value);
210         }
211 
212         return this;
213     }
214 
215     private boolean isRequestBodyMethod()
216     {
217         final String methodType = requestBuilder.getMethod();
218         return HttpPost.METHOD_NAME.equals(methodType) || HttpPut.METHOD_NAME.equals(methodType);
219     }
220 
221     @Override
222     public HttpClientRequest addHeader(final String headerName, final String headerValue)
223     {
224         List<String> list = headers.get(headerName);
225         if (list == null)
226         {
227             list = new ArrayList<>();
228             headers.put(headerName, list);
229         }
230         list.add(headerValue);
231         requestBuilder.addHeader(headerName, headerValue);
232         return this;
233     }
234 
235     @Override
236     public HttpClientRequest setHeader(final String headerName, final String headerValue)
237     {
238         headers.put(headerName, new ArrayList<>(Arrays.asList(headerValue)));
239         requestBuilder.setHeader(headerName, headerValue);
240         return this;
241     }
242 
243     @Override
244     public HttpClientRequest setFollowRedirects(final boolean follow)
245     {
246         requestConfigBuilder.setRedirectsEnabled(follow);
247         return this;
248     }
249 
250     @Override
251     public HttpClientRequest setEntity(final Object entity)
252     {
253         throw new UnsupportedOperationException("This SAL request does not support object marshalling. Use the RequestFactory component instead.");
254     }
255 
256 }