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         ensureRequestBodyMethod();
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         ensureRequestBodyMethod();
180 
181         final MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
182 
183         for (RequestFilePart requestBodyFile : requestBodyFiles)
184         {
185             final ContentType fileContentType = ContentType.create(requestBodyFile.getContentType());
186             multipartEntityBuilder.addBinaryBody(requestBodyFile.getParameterName(), requestBodyFile.getFile(), fileContentType, requestBodyFile.getFileName());
187         }
188 
189         requestBuilder.setEntity(multipartEntityBuilder.build());
190         return this;
191     }
192 
193     @Override
194     public HttpClientRequest addRequestParameters(final String... params)
195     {
196         ensureRequestBodyMethod();
197 
198         if (params.length % 2 != 0)
199         {
200             throw new IllegalArgumentException("You must enter an even number of arguments.");
201         }
202 
203         final List<NameValuePair> requestBodyParameters = new ArrayList<>();
204         for (int i = 0; i < params.length; i += 2)
205         {
206             final String name = params[i];
207             final String value = params[i + 1];
208             final BasicNameValuePair nameValuePair = new BasicNameValuePair(name, value);
209             requestBodyParameters.add(nameValuePair);
210         }
211 
212         requestBuilder.setEntity(new UrlEncodedFormEntity(requestBodyParameters, StandardCharsets.UTF_8));
213         return this;
214     }
215 
216     private void ensureRequestBodyMethod()
217     {
218         if (!isRequestBodyMethod())
219         {
220             throw new IllegalStateException("Only PUT or POST methods accept a request body.");
221         }
222     }
223 
224     private boolean isRequestBodyMethod()
225     {
226         final String methodType = requestBuilder.getMethod();
227         return HttpPost.METHOD_NAME.equals(methodType) || HttpPut.METHOD_NAME.equals(methodType);
228     }
229 
230     @Override
231     public HttpClientRequest addHeader(final String headerName, final String headerValue)
232     {
233         List<String> list = headers.get(headerName);
234         if (list == null)
235         {
236             list = new ArrayList<>();
237             headers.put(headerName, list);
238         }
239         list.add(headerValue);
240         requestBuilder.addHeader(headerName, headerValue);
241         return this;
242     }
243 
244     @Override
245     public HttpClientRequest setHeader(final String headerName, final String headerValue)
246     {
247         headers.put(headerName, new ArrayList<>(Arrays.asList(headerValue)));
248         requestBuilder.setHeader(headerName, headerValue);
249         return this;
250     }
251 
252     @Override
253     public HttpClientRequest setFollowRedirects(final boolean follow)
254     {
255         requestConfigBuilder.setRedirectsEnabled(follow);
256         return this;
257     }
258 
259     @Override
260     public HttpClientRequest setEntity(final Object entity)
261     {
262         throw new UnsupportedOperationException("This SAL request does not support object marshalling. Use the RequestFactory component instead.");
263     }
264 
265 }