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