com.tinypass.client.common.ApacheApiInvoker Maven / Gradle / Ivy
package com.tinypass.client.common;
import com.tinypass.client.TinypassClientApi;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.config.TlsConfig;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.entity.mime.FileBody;
import org.apache.hc.client5.http.entity.mime.InputStreamBody;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.net.URIBuilder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class ApacheApiInvoker extends ApiInvoker {
private JsonParser jsonParser;
private CloseableHttpClient httpClient;
private boolean closed = false;
/**
* Create instance of ApacheApiInvoker and initialize apache http client
*
* @param jsonParser Implementation of JsonParser
* @param maxConnections Max connection in pool
* @param timeout Timeout in ms
*/
public ApacheApiInvoker(JsonParser jsonParser, int maxConnections, int timeout) {
this.jsonParser = jsonParser;
if (jsonParser == null || maxConnections < 1 || timeout < 100) {
throw new RuntimeException("incorrect parameter");
}
ConnectionConfig connectionConfig = ConnectionConfig.custom().setConnectTimeout(timeout, TimeUnit.MILLISECONDS).build();
PoolingHttpClientConnectionManager manager = PoolingHttpClientConnectionManagerBuilder.create()
.setDefaultTlsConfig(TlsConfig.custom()
.setSupportedProtocols(TLS.V_1_2,
TLS.V_1_3).build())
.setConnectionConfigResolver(httpRoute -> {
if (httpRoute.isSecure()) {
return connectionConfig;
} else {
return ConnectionConfig.custom().setConnectTimeout(timeout, TimeUnit.MILLISECONDS).build();
}
})
.build();
httpClient = HttpClients.custom()
.setConnectionManager(manager)
.setUserAgent("Piano/VX API JVM SDK v." + TinypassClientApi.VERSION)
.build();
}
@Override
public T invokeAPI(String host, String path, String method, String token,
Map> queryParams, Object body, Map headerParams,
Map> formParams, String resultContainerType, Class> resultClass)
throws ApiException {
try {
//Always add the api_token to each request
headerParams.put("api_token", token);
HttpUriRequestBase request = getHttpRequest(host, path, method, queryParams, body, headerParams,
formParams);
HttpContext httpContext = new BasicHttpContext();
//Send the request; It will immediately return the response in HttpResponse object if any
try (ClassicHttpResponse response = httpClient.execute(request, httpContext)) {
int statusCode = response.getCode();
if (statusCode != 200) {
throw new ApiException(statusCode, "Error sending REST Request. Status code: " +
statusCode);
}
return jsonParser.deserialize(EntityUtils.toString(response.getEntity()), resultContainerType, resultClass);
}
} catch (ApiException ae) {
throw ae;
} catch (Exception e) {
throw new ApiException(500, e.getMessage(), e);
}
}
/**
* Helper method to convert our parameter map into NameValuePairs list
* that can be added to the request
*/
private List convertToPairs(Map> params) {
return params.entrySet().stream()
.flatMap(
e -> e.getValue().stream().map(v -> new BasicNameValuePair(e.getKey(), v))
)
.collect(Collectors.toList());
}
/**
* Build the actual HTTP request. Thanks to httpclient this
* method is more then ugly.
*
* We have to differenciate between the different requests GET vs POST
* but also different POST parameter options.
*
* POSTs can be:
*
* multi-part
* form-encoded
* body content
*
* By default, POST will do a standard form-urlencoded request.
*
* If a file is one of the body parameters then instead
* the request will become a multipart
*
* NOTE: Because of the lameness of jersey, a multipart
* request will add the non file based parameters to the URL query string
*/
private HttpUriRequestBase getHttpRequest(String host, String path, String method,
Map> queryParams, Object body, Map headerParams,
Map> formParams) throws UnsupportedEncodingException,
URISyntaxException, ApiException {
HttpUriRequestBase request;
if (method.equals("POST")) {
URIBuilder uriBuilder = new URIBuilder(host + path);
HttpPost postRequest = new HttpPost(host + path);
request = postRequest;
// Combine all outstanding form / query params and attach them
// to the query string
List nameValuePairs = convertToPairs(queryParams);
if (body != null) {
nameValuePairs.addAll(convertToPairs(formParams));
}
for (BasicNameValuePair nameValuePair : nameValuePairs) {
uriBuilder.addParameter(nameValuePair.getName(), nameValuePair.getValue());
}
postRequest.setUri(uriBuilder.build().normalize());
if (body == null) {
if (!formParams.isEmpty()) {
//urlencoded
List formNameValuePairs = convertToPairs(formParams);
postRequest.setEntity(new UrlEncodedFormEntity(formNameValuePairs, StandardCharsets.UTF_8));
request.addHeader("content-type", "application/x-www-form-urlencoded");
}
} else if (body instanceof InputStream || body instanceof File) {
//Multi-part request
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
if (body instanceof InputStream) {
InputStreamBody bin = new InputStreamBody((InputStream) body, "file");
builder.addPart("file", bin);
} else {
FileBody bin = new FileBody((File) body);
builder.addPart("file", bin);
}
postRequest.setEntity(builder.build());
} else {
//Set the request post body
StringEntity userEntity = new StringEntity(jsonParser.serialize(body),
ContentType.APPLICATION_JSON);
postRequest.setEntity(userEntity);
request.addHeader("content-type", "application/json");
}
} else {
URIBuilder builder = new URIBuilder(host + path);
//builder.setScheme(host.substring(0, host.indexOf("://")))
// .setHost(host.substring(host.indexOf("://") + 3)).setPath(path);
for (String name : queryParams.keySet()) {
for (String value : queryParams.get(name)) {
builder.addParameter(name, value);
}
}
URI uri = builder.build().normalize();
request = new HttpGet(uri);
request.addHeader("content-type", "application/json");
}
for (String name : headerParams.keySet()) {
request.addHeader(name, headerParams.get(name));
}
request.addHeader("cv", "java-");
return request;
}
@Override
public void close() throws IOException {
closed = true;
httpClient.close();
System.out.println(" connection pool of http client has been closed ");
}
public boolean isClosed() {
return closed;
}
}