From 21ee86063d4b502a9d18cdd0d4b78b3baae9dd1b Mon Sep 17 00:00:00 2001 From: Ryan Amari Date: Fri, 1 Nov 2024 11:52:20 -0400 Subject: [PATCH] ATtempt to fix connetion pool issues --- .../dbmi/avillach/service/ProxyWebClient.java | 44 ++++++- .../avillach/service/ResourceWebClient.java | 124 ++++++++++++++---- .../dbmi/avillach/util/HttpClientUtil.java | 1 + 3 files changed, 141 insertions(+), 28 deletions(-) diff --git a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java index f5025233..c644fc20 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java +++ b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ProxyWebClient.java @@ -1,7 +1,6 @@ package edu.harvard.dbmi.avillach.service; import edu.harvard.dbmi.avillach.data.repository.ResourceRepository; -import edu.harvard.dbmi.avillach.util.HttpClientUtil; import edu.harvard.dbmi.avillach.util.Utilities; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; @@ -10,19 +9,28 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; +import org.apache.http.ssl.SSLContexts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; +import javax.net.ssl.SSLContext; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.LinkedList; +import java.util.List; @ApplicationScoped public class ProxyWebClient { @@ -33,7 +41,39 @@ public class ProxyWebClient { ResourceRepository resourceRepository; public ProxyWebClient() { - client = HttpClientUtil.getConfiguredHttpClient(); + PoolingHttpClientConnectionManager connectionManager; + + connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(100); // Maximum total connections + connectionManager.setDefaultMaxPerRoute(20); // Maximum connections per route + + try { + SSLConnectionSocketFactory.getSocketFactory(); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, null, null); + String[] defaultCiphers = sslContext.getServerSocketFactory().getDefaultCipherSuites(); + + List limited = new LinkedList(); + for(String suite : defaultCiphers) + { + //filter out Diffie-Hellman ciphers + if( ! (suite.contains("_DHE_") || suite.contains("_DH_"))) + { + limited.add(suite); + } + } + + client = HttpClients.custom() + .setSSLSocketFactory(new SSLConnectionSocketFactory( + SSLContexts.createSystemDefault(), + new String[]{"TLSv1.2"}, + limited.toArray(new String[limited.size()]), + SSLConnectionSocketFactory.getDefaultHostnameVerifier())) + .setConnectionManager(connectionManager) + .build(); + } catch(NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } } public Response postProxy( diff --git a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java index 09567c41..bf7f3c06 100644 --- a/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java +++ b/pic-sure-resources/pic-sure-resource-api/src/main/java/edu/harvard/dbmi/avillach/service/ResourceWebClient.java @@ -4,25 +4,35 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import edu.harvard.dbmi.avillach.domain.*; +import edu.harvard.dbmi.avillach.util.HttpClientUtil; import edu.harvard.dbmi.avillach.util.exception.ApplicationException; import edu.harvard.dbmi.avillach.util.exception.ProtocolException; import edu.harvard.dbmi.avillach.util.exception.ResourceInterfaceException; import edu.harvard.dbmi.avillach.util.exception.NotAuthorizedException; import org.apache.http.Header; import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; +import org.apache.http.ssl.SSLContexts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.context.ApplicationScoped; +import javax.net.ssl.SSLContext; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; -import java.util.Map; - -import static edu.harvard.dbmi.avillach.util.HttpClientUtil.*; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.*; /** @@ -40,8 +50,70 @@ public class ResourceWebClient { public static final String BEARER_STRING = "Bearer "; public static final String BEARER_TOKEN_KEY = "BEARER_TOKEN"; public static final String QUERY_METADATA_FIELD = "queryMetadata"; + + private HttpClient httpClient; - public ResourceWebClient() { } + public ResourceWebClient() { + PoolingHttpClientConnectionManager connectionManager; + + connectionManager = new PoolingHttpClientConnectionManager(); + connectionManager.setMaxTotal(100); // Maximum total connections + connectionManager.setDefaultMaxPerRoute(20); // Maximum connections per route + + try { + SSLConnectionSocketFactory.getSocketFactory(); + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, null, null); + String[] defaultCiphers = sslContext.getServerSocketFactory().getDefaultCipherSuites(); + + List limited = new LinkedList(); + for(String suite : defaultCiphers) + { + //filter out Diffie-Hellman ciphers + if( ! (suite.contains("_DHE_") || suite.contains("_DH_"))) + { + limited.add(suite); + } + } + + httpClient = HttpClients.custom() + .setSSLSocketFactory(new SSLConnectionSocketFactory( + SSLContexts.createSystemDefault(), + new String[]{"TLSv1.2"}, + limited.toArray(new String[limited.size()]), + SSLConnectionSocketFactory.getDefaultHostnameVerifier())) + .setConnectionManager(connectionManager) + .build(); + } catch(NoSuchAlgorithmException | KeyManagementException e) { + throw new RuntimeException(e); + } + } + + private HttpResponse retrievePostResponse(String uri, Header[] headers, String body) { + try { + logger.debug("HttpClientUtil retrievePostResponse()"); + + List
headerList = new ArrayList<>(); + + if (headers != null) + headerList = new ArrayList<>(Arrays.asList(headers)); + headerList.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)); + + return HttpClientUtil.simplePost(uri, httpClient, new StringEntity(body), headerList.toArray(new Header[headerList.size()])); + } catch (ApplicationException | UnsupportedEncodingException e) { + throw new ResourceInterfaceException(uri, e); + } + } + + public HttpResponse retrieveGetResponse(String uri, Header[] headers) { + try { + logger.debug("HttpClientUtil retrieveGetResponse()"); + + return HttpClientUtil.simpleGet(httpClient, uri, headers); + } catch (ApplicationException e) { + throw new ResourceInterfaceException(uri, e); + } + } public ResourceInfo info(String rsURL, QueryRequest queryRequest){ logger.debug("Calling ResourceWebClient info()"); @@ -58,12 +130,12 @@ public ResourceInfo info(String rsURL, QueryRequest queryRequest){ logger.debug("Calling /info at ResourceURL: {}", rsURL); String pathName = "/info"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } - return readObjectFromResponse(resourcesResponse, ResourceInfo.class); + return HttpClientUtil.readObjectFromResponse(resourcesResponse, ResourceInfo.class); } catch (JsonProcessingException e){ throw new NotAuthorizedException("Unable to encode resource credentials", e); } @@ -87,9 +159,9 @@ public PaginatedSearchResult searchConceptValues(String rsURL, QueryRequest q HttpResponse resourcesResponse = retrieveGetResponse(uriBuilder.build().toString(), createHeaders(resourceCredentials)); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } - return readObjectFromResponse(resourcesResponse, PaginatedSearchResult.class); + return HttpClientUtil.readObjectFromResponse(resourcesResponse, PaginatedSearchResult.class); } catch (URISyntaxException e) { throw new ApplicationException("rsURL invalid : " + rsURL, e); } @@ -111,12 +183,12 @@ public SearchResults search(String rsURL, QueryRequest searchQueryRequest){ String pathName = "/search"; String body = json.writeValueAsString(searchQueryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(searchQueryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(searchQueryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } - return readObjectFromResponse(resourcesResponse, SearchResults.class); + return HttpClientUtil.readObjectFromResponse(resourcesResponse, SearchResults.class); } catch (JsonProcessingException e){ logger.error("Unable to serialize search query"); //TODO Write custom exception @@ -138,12 +210,12 @@ public QueryStatus query(String rsURL, QueryRequest dataQueryRequest){ } String pathName = "/query"; String body = json.writeValueAsString(dataQueryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(dataQueryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(dataQueryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } - return readObjectFromResponse(resourcesResponse, QueryStatus.class); + return HttpClientUtil.readObjectFromResponse(resourcesResponse, QueryStatus.class); } catch (JsonProcessingException e){ logger.error("Unable to encode data query"); throw new ProtocolException("Unable to encode data query", e); @@ -167,14 +239,14 @@ public QueryStatus queryStatus(String rsURL, String queryId, QueryRequest queryR } String pathName = "/query/" + queryId + "/status"; String body = json.writeValueAsString(queryRequest); - logger.debug(composeURL(rsURL, pathName)); + logger.debug(HttpClientUtil.composeURL(rsURL, pathName)); logger.debug(body); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } - return readObjectFromResponse(resourcesResponse, QueryStatus.class); + return HttpClientUtil.readObjectFromResponse(resourcesResponse, QueryStatus.class); } catch (JsonProcessingException e){ logger.error("Unable to encode resource credentials"); throw new ProtocolException("Unable to encode resource credentials", e); @@ -198,10 +270,10 @@ public Response queryResult(String rsURL, String queryId, QueryRequest queryRequ } String pathName = "/query/" + queryId + "/result"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } return Response.ok(resourcesResponse.getEntity().getContent()).build(); } catch (JsonProcessingException e){ @@ -229,10 +301,10 @@ public Response queryResultSignedUrl(String rsURL, String queryId, QueryRequest } String pathName = "/query/" + queryId + "/signed-url"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { logger.error("ResourceRS did not return a 200"); - throwResponseError(resourcesResponse, rsURL); + HttpClientUtil.throwResponseError(resourcesResponse, rsURL); } return Response.ok(resourcesResponse.getEntity().getContent()).build(); } catch (JsonProcessingException e){ @@ -258,7 +330,7 @@ public Response queryFormat(String rsURL, QueryRequest queryRequest){ } String pathName = "/query/format"; String body = json.writeValueAsString(queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), createHeaders(queryRequest.getResourceCredentials()), body); int status = resourcesResponse.getStatusLine().getStatusCode(); if (status != 200) { logger.error("Query format request did not return a 200: " + resourcesResponse.getStatusLine().getStatusCode()); @@ -301,7 +373,7 @@ public Response querySync(String rsURL, QueryRequest queryRequest, String reques headers = newHeaders; } - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), headers, body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), headers, body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { throwError(resourcesResponse, rsURL); } @@ -357,7 +429,7 @@ public Response queryContinuous(String rsURL, QueryRequest queryRequest, String } logger.debug("Calling ResourceWebClient queryContinuous() with body: " + body + " and headers: " + queryRequest); - HttpResponse resourcesResponse = retrievePostResponse(composeURL(rsURL, pathName), headers, body); + HttpResponse resourcesResponse = retrievePostResponse(HttpClientUtil.composeURL(rsURL, pathName), headers, body); if (resourcesResponse.getStatusLine().getStatusCode() != 200) { throwError(resourcesResponse, rsURL); } diff --git a/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java b/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java index 79d8c52f..b21376bb 100644 --- a/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java +++ b/pic-sure-util/src/main/java/edu/harvard/dbmi/avillach/util/HttpClientUtil.java @@ -31,6 +31,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.apache.http.ssl.SSLContexts; import org.slf4j.Logger;