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