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
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
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 }