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