Jagger
 All Classes Namespaces Files Functions Variables Enumerator Groups Pages
SpringBasedHttpClient.java
Go to the documentation of this file.
1 package com.griddynamics.jagger.invoker.v2;
2 
3 import org.apache.http.conn.ssl.NoopHostnameVerifier;
4 import org.apache.http.impl.client.CloseableHttpClient;
5 import org.apache.http.impl.client.HttpClientBuilder;
6 import org.apache.http.impl.client.HttpClients;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 import org.springframework.http.HttpHeaders;
10 import org.springframework.http.HttpMethod;
11 import org.springframework.http.MediaType;
12 import org.springframework.http.RequestEntity;
13 import org.springframework.http.ResponseEntity;
14 import org.springframework.http.client.ClientHttpRequestFactory;
15 import org.springframework.http.client.ClientHttpRequestInterceptor;
16 import org.springframework.http.client.ClientHttpResponse;
17 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
18 import org.springframework.http.converter.HttpMessageConverter;
19 import org.springframework.util.FileCopyUtils;
20 import org.springframework.web.client.ResponseErrorHandler;
21 import org.springframework.web.client.RestTemplate;
22 import org.springframework.web.client.UnknownHttpStatusCodeException;
23 import org.springframework.web.util.UriTemplateHandler;
24 
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.net.URI;
28 import java.nio.charset.Charset;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 
33 import static com.google.common.collect.Maps.newHashMap;
34 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.CONNECT_TIMEOUT_IN_MS;
35 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.DEFAULT_URI_VARIABLES;
36 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.ERROR_HANDLER;
37 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.INTERCEPTORS;
38 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.MAX_CONN_PER_ROUTE;
39 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.MAX_CONN_TOTAL;
40 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.MESSAGE_CONVERTERS;
41 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.REQUEST_FACTORY;
42 import static com.griddynamics.jagger.invoker.v2.SpringBasedHttpClient.JSpringBasedHttpClientParameters.URI_TEMPLATE_HANDLER;
43 
54 @SuppressWarnings({"unused", "unchecked"})
55 public class SpringBasedHttpClient implements JHttpClient {
56  private static final Logger log = LoggerFactory.getLogger(SpringBasedHttpClient.class);
57  private static final int DEFAULT_MAX_CONN_TOTAL = Integer.MAX_VALUE;
58  private static final int DEFAULT_MAX_CONN_PER_ROUTE = Integer.MAX_VALUE;
59  private static final int DEFAULT_CONNECT_TIMEOUT_IN_MS = 60000;
60 
67  DEFAULT_URI_VARIABLES("default_uri_variables"),
68  ERROR_HANDLER("error_handler"),
69  MESSAGE_CONVERTERS("message_converters"),
70  URI_TEMPLATE_HANDLER("uri_template_handler"),
71  INTERCEPTORS("interceptors"),
72  REQUEST_FACTORY("request_factory"),
73  MAX_CONN_TOTAL("max_conn_total"),
74  MAX_CONN_PER_ROUTE("max_conn_per_route"),
75  CONNECT_TIMEOUT_IN_MS("connect_timeout");
76 
77  private String value;
78 
80  this.value = value;
81  }
82 
83  public String getValue() {
84  return value;
85  }
86  }
87 
103  private final Map<String, Object> clientParams;
104 
105  private RestTemplate restTemplate;
106 
108  clientParams = new HashMap<>();
109  restTemplate = new RestTemplate();
110  restTemplate.setRequestFactory(getRequestFactory());
111  restTemplate.setErrorHandler(new AllowAllCodesResponseErrorHandler());
112  }
113 
114  public SpringBasedHttpClient(Map<String, Object> clientParams) {
115  this();
116  this.clientParams.putAll(clientParams);
117  setRestTemplateParams(this.clientParams);
118  }
119 
120  @Override
121  public JHttpResponse execute(JHttpEndpoint endpoint, JHttpQuery query) {
122  if (query == JHttpQuery.EMPTY_QUERY)
123  return execute(endpoint);
124 
125  URI endpointURI = endpoint.getURI(query.getPath(), query.getQueryParams());
126  RequestEntity requestEntity = mapToRequestEntity(query, endpointURI);
127  ResponseEntity responseEntity;
128  if (query.getResponseBodyType() != null) {
129  responseEntity = restTemplate.exchange(endpointURI, query.getMethod(), requestEntity, query.getResponseBodyType());
130  } else {
131  responseEntity = restTemplate.exchange(endpointURI, query.getMethod(), requestEntity, byte[].class);
132  }
133  return mapToJHttpResponse(responseEntity);
134  }
135 
136  public JHttpResponse execute(JHttpEndpoint endpoint) {
137  URI endpointURI = endpoint.getURI();
138  RequestEntity requestEntity = mapToRequestEntity(endpointURI);
139  ResponseEntity responseEntity = restTemplate.exchange(endpointURI, HttpMethod.GET, requestEntity, byte[].class);
140  return mapToJHttpResponse(responseEntity);
141  }
142 
143  private void setRestTemplateParams(Map<String, Object> clientParams) {
144  int maxConnPerRoute = DEFAULT_MAX_CONN_PER_ROUTE;
145  int maxConnTotal = DEFAULT_MAX_CONN_TOTAL;
146  int connectTimeoutInMs = DEFAULT_CONNECT_TIMEOUT_IN_MS;
147 
148  if (clientParams.containsKey(DEFAULT_URI_VARIABLES.value)) {
149  restTemplate.setDefaultUriVariables((Map<String, ?>) clientParams.get(DEFAULT_URI_VARIABLES.value));
150  }
151  if (clientParams.containsKey(ERROR_HANDLER.value)) {
152  restTemplate.setErrorHandler((ResponseErrorHandler) clientParams.get(ERROR_HANDLER.value));
153  }
154  if (clientParams.containsKey(MESSAGE_CONVERTERS.value)) {
155  restTemplate.setMessageConverters((List<HttpMessageConverter<?>>) clientParams.get(MESSAGE_CONVERTERS.value));
156  }
157  if (clientParams.containsKey(URI_TEMPLATE_HANDLER.value)) {
158  restTemplate.setUriTemplateHandler((UriTemplateHandler) clientParams.get(URI_TEMPLATE_HANDLER.value));
159  }
160  if (clientParams.containsKey(INTERCEPTORS.value)) {
161  restTemplate.setInterceptors((List<ClientHttpRequestInterceptor>) clientParams.get(INTERCEPTORS.value));
162  }
163  if (clientParams.containsKey(MAX_CONN_PER_ROUTE.value)) {
164  Object value = clientParams.get(MAX_CONN_PER_ROUTE.value);
165  if (value instanceof String)
166  maxConnPerRoute = Integer.parseInt((String) value);
167  else
168  maxConnPerRoute = (int) value;
169  }
170  if (clientParams.containsKey(MAX_CONN_TOTAL.value)) {
171  Object value = clientParams.get(MAX_CONN_TOTAL.value);
172  if (value instanceof String)
173  maxConnTotal = Integer.parseInt((String) value);
174  else
175  maxConnTotal = (int) value;
176  }
177  if (clientParams.containsKey(CONNECT_TIMEOUT_IN_MS.value)) {
178  Object value = clientParams.get(CONNECT_TIMEOUT_IN_MS.value);
179  if (value instanceof String)
180  connectTimeoutInMs = Integer.parseInt((String) value);
181  else
182  connectTimeoutInMs = (int) value;
183  }
184 
185  if (clientParams.containsKey(REQUEST_FACTORY.value)) {
186  restTemplate.setRequestFactory((ClientHttpRequestFactory) clientParams.get(REQUEST_FACTORY.value));
187  }
188  if (!clientParams.containsKey(REQUEST_FACTORY.value) && containsAnyRequestFactoryParam(clientParams)) {
189  restTemplate.setRequestFactory(getRequestFactory(maxConnPerRoute, maxConnTotal, connectTimeoutInMs));
190  } else if (clientParams.containsKey(REQUEST_FACTORY.value) && containsAnyRequestFactoryParam(clientParams)) {
191  throw new IllegalArgumentException("Parameters max_conn_total, max_conn_per_route and connect_timeout cannot be set if " +
192  "request_factory parameter presents. You must configure these parameters in your request_factory entity.");
193  }
194  }
195 
196  private boolean containsAnyRequestFactoryParam(Map<String, Object> clientParams) {
197  return clientParams.containsKey(MAX_CONN_PER_ROUTE.value) ||
198  clientParams.containsKey(MAX_CONN_TOTAL.value) ||
199  clientParams.containsKey(CONNECT_TIMEOUT_IN_MS.value);
200  }
201 
202  private HttpComponentsClientHttpRequestFactory getRequestFactory() {
203  return getRequestFactory(DEFAULT_MAX_CONN_PER_ROUTE, DEFAULT_MAX_CONN_TOTAL, DEFAULT_CONNECT_TIMEOUT_IN_MS);
204  }
205 
206  private HttpComponentsClientHttpRequestFactory getRequestFactory(int maxConnPerRoute, int maxConnTotal, int connectTimeoutInMs) {
207  HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
208  CloseableHttpClient httpClient = HttpClients.custom()
209  .setSSLHostnameVerifier(new NoopHostnameVerifier())
210  .setMaxConnPerRoute(maxConnPerRoute)
211  .setMaxConnTotal(maxConnTotal)
212  .build();
213  requestFactory.setHttpClient(httpClient);
214  requestFactory.setConnectTimeout(connectTimeoutInMs);
215  return requestFactory;
216  }
217 
218  private <T> RequestEntity<T> mapToRequestEntity(JHttpQuery<T> query, URI endpointURI) {
219  return new RequestEntity<>(query.getBody(), query.getHeaders(), query.getMethod(), endpointURI);
220  }
221 
222  private <T> RequestEntity<T> mapToRequestEntity(URI endpointURI) {
223  return new RequestEntity<>(HttpMethod.GET, endpointURI);
224  }
225 
226  private <T> JHttpResponse<T> mapToJHttpResponse(ResponseEntity<T> responseEntity) {
227  return new JHttpResponse<>(responseEntity.getStatusCode(), responseEntity.getBody(), responseEntity.getHeaders());
228  }
229 
230  public Map<String, Object> getClientParams() {
231  return newHashMap(clientParams);
232  }
233 
234  public static class AllowAllCodesResponseErrorHandler implements ResponseErrorHandler {
235  @Override
236  public boolean hasError(ClientHttpResponse response) throws IOException {
237  return false;
238  }
239 
240  @Override
241  public void handleError(ClientHttpResponse response) throws IOException {
242  try {
243  response.getStatusCode();
244  } catch (IllegalArgumentException ex) {
245  throw new UnknownHttpStatusCodeException(response.getRawStatusCode(),
246  response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
247  }
248  }
249 
250  private byte[] getResponseBody(ClientHttpResponse response) {
251  try {
252  InputStream responseBody = response.getBody();
253  if (responseBody != null) {
254  return FileCopyUtils.copyToByteArray(responseBody);
255  }
256  } catch (IOException ex) {
257  // ignore
258  }
259  return new byte[0];
260  }
261 
262  private Charset getCharset(ClientHttpResponse response) {
263  HttpHeaders headers = response.getHeaders();
264  MediaType contentType = headers.getContentType();
265  return contentType != null ? contentType.getCharset() : null;
266  }
267  }
268 }