From 397d9b3c7430cdec1e073039150376945a7343ac Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 12:49:42 +0300 Subject: [PATCH 01/12] Configurable http client --- CHANGELOG.md | 3 + .../java/com/perimeterx/api/PerimeterX.java | 1 - .../com/perimeterx/http/IPXHttpClient.java | 13 ++ .../perimeterx/http/IPXIncomingResponse.java | 10 + .../perimeterx/http/IPXOutgoingRequest.java | 15 ++ .../perimeterx/http/PXApacheHttpClient.java | 169 ++++++++++++++ .../http/PXApacheIncomingResponse.java | 46 ++++ .../com/perimeterx/http/PXHttpClient.java | 218 +++++------------- .../com/perimeterx/http/PXHttpHeader.java | 11 + .../com/perimeterx/http/PXHttpMethod.java | 6 + .../com/perimeterx/http/PXHttpStatus.java | 11 + .../http/PXOutgoingRequestImpl.java | 24 ++ .../models/configuration/PXConfiguration.java | 3 + 13 files changed, 372 insertions(+), 158 deletions(-) create mode 100644 src/main/java/com/perimeterx/http/IPXHttpClient.java create mode 100644 src/main/java/com/perimeterx/http/IPXIncomingResponse.java create mode 100644 src/main/java/com/perimeterx/http/IPXOutgoingRequest.java create mode 100644 src/main/java/com/perimeterx/http/PXApacheHttpClient.java create mode 100644 src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java create mode 100644 src/main/java/com/perimeterx/http/PXHttpHeader.java create mode 100644 src/main/java/com/perimeterx/http/PXHttpMethod.java create mode 100644 src/main/java/com/perimeterx/http/PXHttpStatus.java create mode 100644 src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac8f513..40eb6b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Change Log +## [vX.X.X](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.7.1...HEAD) (YYYY-MM-DD) +- configurable HttpClient + ## [v6.7.1](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.7.1...HEAD) (2023-09-05) - Added logs for timeouts - Running async activities via ExecutorService diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index e85a9eee..d76b04a7 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -53,7 +53,6 @@ import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.configuration.PXDynamicConfiguration; import com.perimeterx.models.exceptions.PXException; -import com.perimeterx.models.risk.PassReason; import com.perimeterx.utils.EnforcerErrorUtils; import com.perimeterx.utils.HMACUtils; import com.perimeterx.utils.PXLogger; diff --git a/src/main/java/com/perimeterx/http/IPXHttpClient.java b/src/main/java/com/perimeterx/http/IPXHttpClient.java new file mode 100644 index 00000000..62e65dff --- /dev/null +++ b/src/main/java/com/perimeterx/http/IPXHttpClient.java @@ -0,0 +1,13 @@ +package com.perimeterx.http; + +import java.io.Closeable; +import java.io.IOException; + +public interface IPXHttpClient extends Closeable { + default void init() throws IOException { + } + + IPXIncomingResponse send(IPXOutgoingRequest request) throws IOException; + + void sendAsync(IPXOutgoingRequest request) throws IOException; +} diff --git a/src/main/java/com/perimeterx/http/IPXIncomingResponse.java b/src/main/java/com/perimeterx/http/IPXIncomingResponse.java new file mode 100644 index 00000000..71b2101d --- /dev/null +++ b/src/main/java/com/perimeterx/http/IPXIncomingResponse.java @@ -0,0 +1,10 @@ +package com.perimeterx.http; + +import java.io.Closeable; +import java.io.IOException; + +public interface IPXIncomingResponse extends Closeable { + String body() throws IOException; + PXHttpStatus status(); + PXHttpHeader[] headers(); +} diff --git a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java new file mode 100644 index 00000000..8f489f58 --- /dev/null +++ b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java @@ -0,0 +1,15 @@ +package com.perimeterx.http; + +import java.util.List; + +public interface IPXOutgoingRequest { + String getUrl(); + + PXHttpMethod getHttpMethod(); + + String getBody(); + + List getHeaders(); + + +} \ No newline at end of file diff --git a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java new file mode 100644 index 00000000..6cc4eb8c --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java @@ -0,0 +1,169 @@ +package com.perimeterx.http; + +import com.perimeterx.http.async.PxClientAsyncHandler; +import com.perimeterx.models.configuration.PXConfiguration; +import com.perimeterx.utils.PXCommonUtils; +import com.perimeterx.utils.PXLogger; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; +import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; +import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; +import org.apache.http.nio.client.methods.HttpAsyncMethods; +import org.apache.http.nio.protocol.BasicAsyncResponseConsumer; +import org.apache.http.nio.protocol.HttpAsyncRequestProducer; +import org.apache.http.nio.reactor.IOReactorException; +import org.apache.http.nio.reactor.IOReactorExceptionHandler; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class PXApacheHttpClient implements IPXHttpClient { + private static final PXLogger logger = PXLogger.getLogger(PXApacheHttpClient.class); + private static final int INACTIVITY_PERIOD_TIME_MS = 1000; + private static final long MAX_IDLE_TIME_SEC = 30L; + private final PXConfiguration pxConfiguration; + private CloseableHttpClient httpClient; + private CloseableHttpAsyncClient asyncHttpClient; + private TimerValidateRequestsQueue timerConfigUpdater; + + public PXApacheHttpClient(PXConfiguration pxConfiguration) { + this.pxConfiguration = pxConfiguration; + } + + @Override + public void init() throws IOException { + initHttpClient(); + initAsyncHttpClient(); + } + + @Override + public IPXIncomingResponse send(IPXOutgoingRequest request) throws IOException { + HttpUriRequest apacheRequest = createRequest(request); + CloseableHttpResponse response = httpClient.execute(apacheRequest); + return new PXApacheIncomingResponse(response); + } + + @Override + public void sendAsync(IPXOutgoingRequest request) throws IOException { + HttpAsyncRequestProducer producer = null; + BasicAsyncResponseConsumer basicAsyncResponseConsumer = null; + try { + HttpUriRequest apacheRequest = createRequest(request); + producer = HttpAsyncMethods.create(apacheRequest); + basicAsyncResponseConsumer = new BasicAsyncResponseConsumer(); + asyncHttpClient.execute(producer, basicAsyncResponseConsumer, new PxClientAsyncHandler()); + } catch (Exception e) { + logger.debug("Sending batch activities failed. Error: {}", e.getMessage()); + } finally { + if (producer != null) { + producer.close(); + } + if (basicAsyncResponseConsumer != null) { + basicAsyncResponseConsumer.close(); + } + } + } + + @Override + public void close() throws IOException { + if (this.timerConfigUpdater != null) { + this.timerConfigUpdater.close(); + } + + if (this.asyncHttpClient != null) { + this.asyncHttpClient.close(); + } + + if (this.httpClient != null) { + this.httpClient.close(); + } + } + + private void initHttpClient() { + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); + cm.setMaxTotal(this.pxConfiguration.getMaxConnections()); + cm.setDefaultMaxPerRoute(this.pxConfiguration.getMaxConnectionsPerRoute()); + cm.setValidateAfterInactivity(INACTIVITY_PERIOD_TIME_MS); + + this.httpClient = HttpClients.custom() + .evictExpiredConnections() + .evictIdleConnections(MAX_IDLE_TIME_SEC, TimeUnit.SECONDS) + .setConnectionManager(cm) + .setDefaultHeaders(PXCommonUtils.getDefaultHeaders(pxConfiguration.getAuthToken())) + .build(); + } + + private void initAsyncHttpClient() throws IOReactorException { + DefaultConnectingIOReactor ioReactor = getDefaultConnectingIOReactor(); + + PoolingNHttpClientConnectionManager nHttpConnectionManager = new PoolingNHttpClientConnectionManager(ioReactor); + CloseableHttpAsyncClient closeableHttpAsyncClient = HttpAsyncClients.custom() + .setConnectionManager(nHttpConnectionManager) + .build(); + closeableHttpAsyncClient.start(); + asyncHttpClient = closeableHttpAsyncClient; + this.timerConfigUpdater = new TimerValidateRequestsQueue(nHttpConnectionManager, pxConfiguration); + timerConfigUpdater.schedule(); + } + + private static DefaultConnectingIOReactor getDefaultConnectingIOReactor() throws IOReactorException { + DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(); + + ioReactor.setExceptionHandler(new IOReactorExceptionHandler() { + @Override + public boolean handle(IOException ex) { + logger.error("IO Reactor encountered an IOException, shutting down reactor. {}", ex); + return false; + } + + @Override + public boolean handle(RuntimeException ex) { + logger.error("IO Reactor encountered a RuntimeException, shutting down reactor. {}", ex); + return false; + } + }); + return ioReactor; + } + + + private HttpRequestBase createRequest(IPXOutgoingRequest request) { + HttpRequestBase req = buildBaseRequest(request); + + + for (PXHttpHeader header : request.getHeaders()) { + req.addHeader(header.getName(), header.getValue()); + } + req.setConfig(PXCommonUtils.getRequestConfig(pxConfiguration)); + return req; + } + + private HttpRequestBase createGetRequest(IPXOutgoingRequest request) { + return new HttpGet(request.getUrl()); + + } + + private HttpRequestBase createPostRequest(IPXOutgoingRequest request) { + HttpPost post = new HttpPost(request.getUrl()); + post.setEntity(new StringEntity(request.getBody(), UTF_8)); + return post; + } + + private HttpRequestBase buildBaseRequest(IPXOutgoingRequest request) { + switch (request.getHttpMethod()) { + case POST: + return createPostRequest(request); + case GET: + return createGetRequest(request); + default: + throw new IllegalArgumentException("unsupported method " + request.getHttpMethod()); + } + } + +} diff --git a/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java b/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java new file mode 100644 index 00000000..a7a19cc9 --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java @@ -0,0 +1,46 @@ +package com.perimeterx.http; + +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; + +import java.io.IOException; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class PXApacheIncomingResponse implements IPXIncomingResponse { + private final CloseableHttpResponse response; + + public PXApacheIncomingResponse(CloseableHttpResponse response) { + this.response = response; + } + + @Override + public String body() throws IOException { + return IOUtils.toString(response.getEntity().getContent(), UTF_8); + } + + @Override + public PXHttpStatus status() { + StatusLine statusLine = response.getStatusLine(); + return new PXHttpStatus(statusLine.getStatusCode(), statusLine.getReasonPhrase()); + } + + @Override + public PXHttpHeader[] headers() { + Header[] allHeaders = response.getAllHeaders(); + PXHttpHeader[] headers = new PXHttpHeader[allHeaders.length]; + for (int i = 0; i < allHeaders.length; i++) { + headers[i] = new PXHttpHeader(allHeaders[i].getName(), allHeaders[i].getValue()); + } + return headers; + } + + @Override + public void close() throws IOException { + if (response != null) { + response.close(); + } + } +} diff --git a/src/main/java/com/perimeterx/http/PXHttpClient.java b/src/main/java/com/perimeterx/http/PXHttpClient.java index 51262e06..aed131ae 100644 --- a/src/main/java/com/perimeterx/http/PXHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXHttpClient.java @@ -1,7 +1,6 @@ package com.perimeterx.http; import com.fasterxml.jackson.core.JsonProcessingException; -import com.perimeterx.http.async.PxClientAsyncHandler; import com.perimeterx.models.PXContext; import com.perimeterx.models.activities.Activity; import com.perimeterx.models.activities.EnforcerTelemetry; @@ -14,37 +13,16 @@ import com.perimeterx.models.risk.S2SErrorReasonInfo; import com.perimeterx.utils.Constants; import com.perimeterx.utils.JsonUtils; -import com.perimeterx.utils.PXCommonUtils; import com.perimeterx.utils.PXLogger; -import org.apache.commons.io.IOUtils; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ConnectTimeoutException; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; -import org.apache.http.impl.nio.client.HttpAsyncClients; -import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager; -import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; -import org.apache.http.nio.client.methods.HttpAsyncMethods; -import org.apache.http.nio.protocol.BasicAsyncResponseConsumer; -import org.apache.http.nio.protocol.HttpAsyncRequestProducer; -import org.apache.http.nio.reactor.IOReactorException; -import org.apache.http.nio.reactor.IOReactorExceptionHandler; -import org.apache.http.util.EntityUtils; import java.io.Closeable; import java.io.IOException; import java.net.SocketTimeoutException; -import java.nio.charset.Charset; import java.util.List; -import java.util.concurrent.TimeUnit; + /** * Low level HTTP client @@ -52,74 +30,30 @@ * Created by shikloshi on 04/07/2016. */ public class PXHttpClient implements PXClient, Closeable { - private static final int INACTIVITY_PERIOD_TIME_MS = 1000; - private static final long MAX_IDLE_TIME_SEC = 30L; private static final PXLogger logger = PXLogger.getLogger(PXHttpClient.class); + private final IPXHttpClient client; + private final PXConfiguration pxConfiguration; - private static final Charset UTF_8 = Charset.forName("utf-8"); - - private CloseableHttpClient httpClient; - private CloseableHttpAsyncClient asyncHttpClient; - private PoolingNHttpClientConnectionManager nHttpConnectionManager; - private final TimerValidateRequestsQueue timerConfigUpdater; - private PXConfiguration pxConfiguration; public PXHttpClient(PXConfiguration pxConfiguration) throws PXException { - this.pxConfiguration = pxConfiguration; - initHttpClient(); try { - initAsyncHttpClient(); - } catch (IOReactorException e) { + this.pxConfiguration = pxConfiguration; + if (pxConfiguration.getHttpClient() == null) { + this.client = new PXApacheHttpClient(pxConfiguration); + } else { + this.client = pxConfiguration.getHttpClient(); + } + client.init(); + } catch (Exception e) { throw new PXException(e); } - - this.timerConfigUpdater = new TimerValidateRequestsQueue(nHttpConnectionManager, pxConfiguration); - timerConfigUpdater.schedule(); - } - - private void initHttpClient() { - PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); - cm.setMaxTotal(this.pxConfiguration.getMaxConnections()); - cm.setDefaultMaxPerRoute(this.pxConfiguration.getMaxConnectionsPerRoute()); - cm.setValidateAfterInactivity(INACTIVITY_PERIOD_TIME_MS); - - httpClient = HttpClients.custom() - .evictExpiredConnections() - .evictIdleConnections(MAX_IDLE_TIME_SEC, TimeUnit.SECONDS) - .setConnectionManager(cm) - .setDefaultHeaders(PXCommonUtils.getDefaultHeaders(pxConfiguration.getAuthToken())) - .build(); } - private void initAsyncHttpClient() throws IOReactorException { - DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(); - - ioReactor.setExceptionHandler(new IOReactorExceptionHandler() { - @Override - public boolean handle(IOException ex) { - logger.error("IO Reactor encountered an IOException, shutting down reactor. {}", ex); - return false; - } - - @Override - public boolean handle(RuntimeException ex) { - logger.error("IO Reactor encountered a RuntimeException, shutting down reactor. {}", ex); - return false; - } - }); - - nHttpConnectionManager = new PoolingNHttpClientConnectionManager(ioReactor); - CloseableHttpAsyncClient closeableHttpAsyncClient = HttpAsyncClients.custom() - .setConnectionManager(nHttpConnectionManager) - .build(); - closeableHttpAsyncClient.start(); - asyncHttpClient = closeableHttpAsyncClient; - } @Override public RiskResponse riskApiCall(PXContext pxContext) throws IOException { - CloseableHttpResponse httpResponse = null; + IPXIncomingResponse httpResponse = null; try { String requestBody = createRequestBody(pxContext); if (requestBody == null) { @@ -152,13 +86,15 @@ private String createRequestBody(PXContext pxContext) { } } - private CloseableHttpResponse executeRiskAPICall(String requestBody, PXContext pxContext) throws ConnectTimeoutException { - HttpPost post = new HttpPost(this.pxConfiguration.getServerURL() + Constants.API_RISK); - post.setEntity(new StringEntity(requestBody, UTF_8)); - post.setConfig(PXCommonUtils.getRequestConfig(pxConfiguration)); + private IPXIncomingResponse executeRiskAPICall(String requestBody, PXContext pxContext) throws ConnectTimeoutException { + IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() + .url(this.pxConfiguration.getServerURL() + Constants.API_RISK) + .httpMethod(PXHttpMethod.POST) + .body(requestBody) + .build(); try { - return httpClient.execute(post); + return client.send(request); } catch (ConnectTimeoutException e) { logger.debug("ConnectTimeoutException", e); @@ -172,8 +108,8 @@ private CloseableHttpResponse executeRiskAPICall(String requestBody, PXContext p return null; } - private RiskResponse validateRiskAPIResponse(CloseableHttpResponse httpResponse, PXContext pxContext) { - StatusLine httpStatus = httpResponse.getStatusLine(); + private RiskResponse validateRiskAPIResponse(IPXIncomingResponse httpResponse, PXContext pxContext) { + PXHttpStatus httpStatus = httpResponse.status(); if (httpStatus.getStatusCode() != 200) { handleUnexpectedHttpStatusError(pxContext, httpStatus); @@ -181,19 +117,19 @@ private RiskResponse validateRiskAPIResponse(CloseableHttpResponse httpResponse, } try { - String s = IOUtils.toString(httpResponse.getEntity().getContent(), UTF_8); + String s = httpResponse.body(); if (s.equals("null")) { throw new PXException("Risk API returned null JSON"); } logger.debug("Risk API Response: {}", s); return JsonUtils.riskResponseReader.readValue(s); } catch (Exception e) { - handleException(pxContext, e, S2SErrorReason.INVALID_RESPONSE, httpResponse.getStatusLine()); + handleException(pxContext, e, S2SErrorReason.INVALID_RESPONSE, httpResponse.status()); } return null; } - private void handleUnexpectedHttpStatusError(PXContext pxContext, StatusLine httpStatus) { + private void handleUnexpectedHttpStatusError(PXContext pxContext, PXHttpStatus httpStatus) { S2SErrorReason errorReason = S2SErrorReason.UNKNOWN_ERROR; int statusCode = httpStatus.getStatusCode(); @@ -208,7 +144,7 @@ private void handleUnexpectedHttpStatusError(PXContext pxContext, StatusLine htt pxContext.setS2sErrorReasonInfo(new S2SErrorReasonInfo(errorReason, message, statusCode, statusMessage)); } - private void handleException(PXContext pxContext, Exception e, S2SErrorReason errorReason, StatusLine httpStatusLine) { + private void handleException(PXContext pxContext, Exception e, S2SErrorReason errorReason, PXHttpStatus httpStatusLine) { S2SErrorReasonInfo errorReasonInfo = httpStatusLine == null ? new S2SErrorReasonInfo(errorReason, e.toString()) : new S2SErrorReasonInfo(errorReason, e.toString(), httpStatusLine.getStatusCode(), httpStatusLine.getReasonPhrase()); pxContext.setS2sErrorReasonInfo(errorReasonInfo); @@ -217,16 +153,15 @@ private void handleException(PXContext pxContext, Exception e, S2SErrorReason er @Override public void sendActivity(Activity activity) throws IOException { - CloseableHttpResponse httpResponse = null; + IPXIncomingResponse httpResponse = null; try { String requestBody = JsonUtils.writer.writeValueAsString(activity); logger.debug("Sending Activity: {}", requestBody); - HttpPost post = new HttpPost(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES); - post.setEntity(new StringEntity(requestBody, UTF_8)); - post.setConfig(PXCommonUtils.getRequestConfig(pxConfiguration)); - - httpResponse = httpClient.execute(post); - EntityUtils.consume(httpResponse.getEntity()); + IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() + .body(requestBody) + .url(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES) + .build(); + httpResponse = client.send(request); } catch (Exception e) { logger.debug("Sending activity failed. Error: {}", e.getMessage()); } finally { @@ -238,29 +173,16 @@ public void sendActivity(Activity activity) throws IOException { @Override public void sendBatchActivities(List activities) throws IOException { - HttpAsyncRequestProducer producer = null; - BasicAsyncResponseConsumer basicAsyncResponseConsumer = null; - try { - String requestBody = JsonUtils.writer.writeValueAsString(activities); - logger.debug("Sending Activities: {}", requestBody); - HttpPost post = new HttpPost(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES); - post.setEntity(new StringEntity(requestBody, UTF_8)); - post.setConfig(PXCommonUtils.getRequestConfig(pxConfiguration)); - post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - post.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken()); - producer = HttpAsyncMethods.create(post); - basicAsyncResponseConsumer = new BasicAsyncResponseConsumer(); - asyncHttpClient.execute(producer, basicAsyncResponseConsumer, new PxClientAsyncHandler()); - } catch (Exception e) { - logger.debug("Sending batch activities failed. Error: {}", e.getMessage()); - } finally { - if (producer != null) { - producer.close(); - } - if (basicAsyncResponseConsumer != null) { - basicAsyncResponseConsumer.close(); - } - } + String requestBody = JsonUtils.writer.writeValueAsString(activities); + logger.debug("Sending Activities: {}", requestBody); + IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() + .url(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES) + .httpMethod(PXHttpMethod.POST) + .body(requestBody) + .header(new PXHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json")) + .header(new PXHttpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken())) + .build(); + client.sendAsync(request); } @Override @@ -272,15 +194,17 @@ public PXDynamicConfiguration getConfigurationFromServer() { queryParams = "?checksum=" + pxConfiguration.getChecksum(); } PXDynamicConfiguration stub = null; - HttpGet get = new HttpGet(pxConfiguration.getRemoteConfigurationUrl() + Constants.API_REMOTE_CONFIGURATION + queryParams); - - try (CloseableHttpResponse httpResponse = httpClient.execute(get)) { - int httpCode = httpResponse.getStatusLine().getStatusCode(); + IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() + .url(pxConfiguration.getRemoteConfigurationUrl() + Constants.API_REMOTE_CONFIGURATION + queryParams) + .httpMethod(PXHttpMethod.GET) + .build(); + try (IPXIncomingResponse httpResponse = client.send(request)) { + int httpCode = httpResponse.status().getStatusCode(); if (httpCode == HttpStatus.SC_OK) { - String bodyContent = IOUtils.toString(httpResponse.getEntity().getContent(), UTF_8); + String bodyContent = httpResponse.body(); stub = JsonUtils.pxConfigurationStubReader.readValue(bodyContent); logger.debug("[getConfiguration] GET request successfully executed"); - } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT) { + } else if (httpResponse.status().getStatusCode() == HttpStatus.SC_NO_CONTENT) { logger.debug("[getConfiguration] No updates found"); } else { logger.debug("[getConfiguration] Failed to get remote configuration, status code {}", httpCode); @@ -294,42 +218,22 @@ public PXDynamicConfiguration getConfigurationFromServer() { @Override public void sendEnforcerTelemetry(EnforcerTelemetry enforcerTelemetry) throws IOException { - HttpAsyncRequestProducer producer = null; - BasicAsyncResponseConsumer basicAsyncResponseConsumer = null; - try { - String requestBody = JsonUtils.writer.writeValueAsString(enforcerTelemetry); - logger.debug("Sending enforcer telemetry: {}", requestBody); - HttpPost post = new HttpPost(this.pxConfiguration.getServerURL() + Constants.API_ENFORCER_TELEMETRY); - post.setEntity(new StringEntity(requestBody, UTF_8)); - PXCommonUtils.getDefaultHeaders(pxConfiguration.getAuthToken()); - post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - post.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken()); - post.setConfig(PXCommonUtils.getRequestConfig(pxConfiguration)); - producer = HttpAsyncMethods.create(post); - basicAsyncResponseConsumer = new BasicAsyncResponseConsumer(); - asyncHttpClient.execute(producer, basicAsyncResponseConsumer, new PxClientAsyncHandler()); - } catch (Exception e) { - logger.debug("Sending telemetry failed. Error: {}", e.getMessage()); - } finally { - if (producer != null) { - producer.close(); - } - if (basicAsyncResponseConsumer != null) { - basicAsyncResponseConsumer.close(); - } - } + String requestBody = JsonUtils.writer.writeValueAsString(enforcerTelemetry); + logger.debug("Sending enforcer telemetry: {}", requestBody); + IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() + .url(this.pxConfiguration.getServerURL() + Constants.API_ENFORCER_TELEMETRY) + .httpMethod(PXHttpMethod.POST) + .body(requestBody) + .header(new PXHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json")) + .header((new PXHttpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken()))) + .build(); + client.send(request); } @Override public void close() throws IOException { - this.timerConfigUpdater.close(); - - if (this.asyncHttpClient != null) { - this.asyncHttpClient.close(); - } - - if (this.httpClient != null) { - this.httpClient.close(); + if (this.client != null) { + this.client.close(); } } } diff --git a/src/main/java/com/perimeterx/http/PXHttpHeader.java b/src/main/java/com/perimeterx/http/PXHttpHeader.java new file mode 100644 index 00000000..c2543adc --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXHttpHeader.java @@ -0,0 +1,11 @@ +package com.perimeterx.http; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class PXHttpHeader { + private final String name; + private final String value; +} diff --git a/src/main/java/com/perimeterx/http/PXHttpMethod.java b/src/main/java/com/perimeterx/http/PXHttpMethod.java new file mode 100644 index 00000000..8d2d306b --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXHttpMethod.java @@ -0,0 +1,6 @@ +package com.perimeterx.http; + +public enum PXHttpMethod { + POST, + GET +} diff --git a/src/main/java/com/perimeterx/http/PXHttpStatus.java b/src/main/java/com/perimeterx/http/PXHttpStatus.java new file mode 100644 index 00000000..a6d988aa --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXHttpStatus.java @@ -0,0 +1,11 @@ +package com.perimeterx.http; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class PXHttpStatus { + private int statusCode; + private String reasonPhrase; +} \ No newline at end of file diff --git a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java new file mode 100644 index 00000000..dfc8a6f0 --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java @@ -0,0 +1,24 @@ +package com.perimeterx.http; + +import lombok.*; + +import java.util.List; + +@Getter +@Builder(toBuilder = true) +@AllArgsConstructor +@ToString +public class PXOutgoingRequestImpl implements IPXOutgoingRequest { + + private final String url; + + @Builder.Default + private final PXHttpMethod httpMethod = PXHttpMethod.GET; + + @Builder.Default + private final String body = null; + + @Singular + private final List headers; + +} diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 79da768c..6b4d65da 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -11,6 +11,7 @@ import com.perimeterx.api.blockhandler.DefaultBlockHandler; import com.perimeterx.api.providers.CustomParametersProvider; import com.perimeterx.api.providers.DefaultCustomParametersProvider; +import com.perimeterx.http.IPXHttpClient; import com.perimeterx.models.configuration.credentialsIntelligenceconfig.CILoginMap; import com.perimeterx.models.risk.CustomParameters; import com.perimeterx.utils.Constants; @@ -272,6 +273,8 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Builder.Default private String customCookieHeader = "x-px-cookies"; + @Builder.Default + private IPXHttpClient httpClient = null; /** * @return Configuration Object clone without cookieKey and authToken **/ From 340828c3536dc8c5f4c0011d2682b54fa1f67771 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 13:37:24 +0300 Subject: [PATCH 02/12] Configurable PXClient --- CHANGELOG.md | 3 +- .../java/com/perimeterx/api/PerimeterX.java | 13 ++++- .../java/com/perimeterx/http/PXClient.java | 9 +++- .../http/mock/MockPXClientFactory.java | 21 +++++++++ .../perimeterx/http/mock/MockPxClient.java | 47 +++++++++++++++++++ .../models/configuration/PXConfiguration.java | 4 ++ .../models/httpmodels/RiskResponse.java | 2 + 7 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java create mode 100644 src/main/java/com/perimeterx/http/mock/MockPxClient.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 40eb6b19..28d40209 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [vX.X.X](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.7.1...HEAD) (YYYY-MM-DD) -- configurable HttpClient +- configurable IPXHttpClient +- configurable PXClient ## [v6.7.1](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.7.1...HEAD) (2023-09-05) - Added logs for timeouts diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index d76b04a7..9fcc1de6 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -42,6 +42,7 @@ import com.perimeterx.api.verificationhandler.DefaultVerificationHandler; import com.perimeterx.api.verificationhandler.TestVerificationHandler; import com.perimeterx.api.verificationhandler.VerificationHandler; +import com.perimeterx.http.PXClient; import com.perimeterx.http.PXHttpClient; import com.perimeterx.http.RequestWrapper; import com.perimeterx.http.ResponseWrapper; @@ -88,7 +89,7 @@ public class PerimeterX implements Closeable { private HostnameProvider hostnameProvider; private VerificationHandler verificationHandler; private ReverseProxy reverseProxy; - private PXHttpClient pxClient = null; + private PXClient pxClient = null; private void init(PXConfiguration configuration) throws PXException { logger.debug(PXLogger.LogReason.DEBUG_INITIALIZING_MODULE); @@ -96,7 +97,7 @@ private void init(PXConfiguration configuration) throws PXException { this.configuration = configuration; hostnameProvider = new DefaultHostnameProvider(); ipProvider = new CombinedIPProvider(configuration); - this.pxClient = new PXHttpClient(configuration); + setPxClient(configuration); this.activityHandler = new BufferedActivityHandler(pxClient, this.configuration); if (configuration.isRemoteConfigurationEnabled()) { @@ -117,6 +118,14 @@ private void init(PXConfiguration configuration) throws PXException { this.reverseProxy = new DefaultReverseProxy(configuration, ipProvider); } + private void setPxClient(PXConfiguration configuration) throws PXException { + if(configuration.getPxClient() == null) { + this.pxClient = new PXHttpClient(configuration); + } else { + this.pxClient = configuration.getPxClient(); + } + } + private void setVerificationHandler() { if (this.configuration.isTestingMode()) { this.verificationHandler = new TestVerificationHandler(this.configuration, this.activityHandler); diff --git a/src/main/java/com/perimeterx/http/PXClient.java b/src/main/java/com/perimeterx/http/PXClient.java index bec19123..573783bc 100644 --- a/src/main/java/com/perimeterx/http/PXClient.java +++ b/src/main/java/com/perimeterx/http/PXClient.java @@ -5,9 +5,9 @@ import com.perimeterx.models.activities.EnforcerTelemetry; import com.perimeterx.models.configuration.PXDynamicConfiguration; import com.perimeterx.models.exceptions.PXException; -import com.perimeterx.models.httpmodels.RiskRequest; import com.perimeterx.models.httpmodels.RiskResponse; +import java.io.Closeable; import java.io.IOException; import java.util.List; @@ -16,7 +16,7 @@ *

* Created by Shikloshi on 03/07/2016. */ -public interface PXClient { +public interface PXClient extends Closeable { /** * Calling PX Server with Risk API call @@ -60,4 +60,9 @@ public interface PXClient { * @param enforcerTelemetry */ void sendEnforcerTelemetry(EnforcerTelemetry enforcerTelemetry) throws IOException; + + @Override + default void close() throws IOException { + // by default do nothing + } } diff --git a/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java b/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java new file mode 100644 index 00000000..182dc5ff --- /dev/null +++ b/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java @@ -0,0 +1,21 @@ +package com.perimeterx.http.mock; + +import com.perimeterx.http.PXClient; +import com.perimeterx.models.httpmodels.RiskResponse; + +public final class MockPXClientFactory { + private MockPXClientFactory() { + } + + public static PXClient createPassAllPXClient() { + return MockPxClient.builder() + .riskResponse(new RiskResponse("uuid", 0, 0, "c", null, null, "", "")) + .build(); + } + + public static PXClient createBlockAllPXClient() { + return MockPxClient.builder() + .riskResponse(new RiskResponse("uuid", 0, 100, "c", null, null, "", "")) + .build(); + } +} diff --git a/src/main/java/com/perimeterx/http/mock/MockPxClient.java b/src/main/java/com/perimeterx/http/mock/MockPxClient.java new file mode 100644 index 00000000..03ca5621 --- /dev/null +++ b/src/main/java/com/perimeterx/http/mock/MockPxClient.java @@ -0,0 +1,47 @@ +package com.perimeterx.http.mock; + +import com.perimeterx.http.PXClient; +import com.perimeterx.models.PXContext; +import com.perimeterx.models.activities.Activity; +import com.perimeterx.models.activities.EnforcerTelemetry; +import com.perimeterx.models.configuration.PXDynamicConfiguration; +import com.perimeterx.models.httpmodels.RiskResponse; +import com.perimeterx.utils.PXLogger; +import lombok.AllArgsConstructor; +import lombok.Builder; +import java.util.List; + +@Builder +@AllArgsConstructor +public class MockPxClient implements PXClient { + private static final PXLogger logger = PXLogger.getLogger(MockPxClient.class); + protected RiskResponse riskResponse; + protected PXDynamicConfiguration pxDynamicConfiguration; + @Override + public final RiskResponse riskApiCall(PXContext pxContext) { + logger.debug("Mocking riskApiCall - {}", riskResponse); + return riskResponse; + } + + @Override + public void sendActivity(Activity activity) { + logger.debug("Mocking sendActivity"); + } + + @Override + public void sendBatchActivities(List activities) { + logger.debug("Mocking sendBatchActivities"); + + } + + @Override + public final PXDynamicConfiguration getConfigurationFromServer() { + logger.debug("Mocking sendBatchActivities - {}", pxDynamicConfiguration); + return pxDynamicConfiguration; + } + + @Override + public void sendEnforcerTelemetry(EnforcerTelemetry enforcerTelemetry) { + logger.debug("Mocking sendEnforcerTelemetry"); + } +} diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 6b4d65da..d9a36e8c 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -12,6 +12,7 @@ import com.perimeterx.api.providers.CustomParametersProvider; import com.perimeterx.api.providers.DefaultCustomParametersProvider; import com.perimeterx.http.IPXHttpClient; +import com.perimeterx.http.PXClient; import com.perimeterx.models.configuration.credentialsIntelligenceconfig.CILoginMap; import com.perimeterx.models.risk.CustomParameters; import com.perimeterx.utils.Constants; @@ -275,6 +276,9 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Builder.Default private IPXHttpClient httpClient = null; + + @Builder.Default + private PXClient pxClient = null; /** * @return Configuration Object clone without cookieKey and authToken **/ diff --git a/src/main/java/com/perimeterx/models/httpmodels/RiskResponse.java b/src/main/java/com/perimeterx/models/httpmodels/RiskResponse.java index 27f06fd9..f4c2c696 100644 --- a/src/main/java/com/perimeterx/models/httpmodels/RiskResponse.java +++ b/src/main/java/com/perimeterx/models/httpmodels/RiskResponse.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.ToString; /** * Risk API server Response POJO @@ -14,6 +15,7 @@ */ @Data @AllArgsConstructor +@ToString @JsonIgnoreProperties(ignoreUnknown = true) public class RiskResponse { private String uuid; From 15f377c3d4658b0b07414544c4871d0e9a52d7d1 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 16:56:15 +0300 Subject: [PATCH 03/12] First reverse proxy with custom http client --- .../java/com/perimeterx/api/PerimeterX.java | 10 +- .../api/proxy/DefaultReverseProxy.java | 24 ++--- .../perimeterx/api/proxy/RemoteServer.java | 97 ++++++++----------- .../proxy/mock/MockReverseProxyFactory.java | 12 +++ .../api/proxy/mock/NeverReverseProxy.java | 38 ++++++++ .../com/perimeterx/http/IPXHttpClient.java | 3 - .../perimeterx/http/IPXIncomingResponse.java | 3 +- .../perimeterx/http/IPXOutgoingRequest.java | 3 +- .../perimeterx/http/PXApacheHttpClient.java | 87 ++++++++++------- .../http/PXApacheIncomingResponse.java | 11 ++- .../com/perimeterx/http/PXHttpClient.java | 17 ++-- .../com/perimeterx/http/PXHttpMethod.java | 7 +- .../http/PXOutgoingRequestImpl.java | 3 +- .../models/configuration/PXConfiguration.java | 4 + .../java/com/perimeterx/utils/PXIOUtils.java | 20 ++++ .../com/perimeterx/api/ReverseProxyTest.java | 14 +-- 16 files changed, 225 insertions(+), 128 deletions(-) create mode 100644 src/main/java/com/perimeterx/api/proxy/mock/MockReverseProxyFactory.java create mode 100644 src/main/java/com/perimeterx/api/proxy/mock/NeverReverseProxy.java create mode 100644 src/main/java/com/perimeterx/utils/PXIOUtils.java diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index 9fcc1de6..9ecb2028 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -115,7 +115,15 @@ private void init(PXConfiguration configuration) throws PXException { this.serverValidator = new PXS2SValidator(pxClient, this.configuration); this.cookieValidator = new PXCookieValidator(this.configuration); setVerificationHandler(); - this.reverseProxy = new DefaultReverseProxy(configuration, ipProvider); + setReverseProxy(configuration); + } + + private void setReverseProxy(PXConfiguration configuration) { + if(this.configuration.getPxReverseProxy() == null) { + this.reverseProxy = new DefaultReverseProxy(configuration, ipProvider); + } else { + this.reverseProxy = configuration.getPxReverseProxy(); + } } private void setPxClient(PXConfiguration configuration) throws PXException { diff --git a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java index e2843792..4ce765cc 100644 --- a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java +++ b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java @@ -1,13 +1,13 @@ package com.perimeterx.api.proxy; import com.perimeterx.api.providers.IPProvider; +import com.perimeterx.http.IPXHttpClient; +import com.perimeterx.http.IPXOutgoingRequest; +import com.perimeterx.http.PXApacheHttpClient; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.proxy.PredefinedResponse; import com.perimeterx.utils.Constants; import com.perimeterx.utils.PXLogger; -import org.apache.http.HttpRequest; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import javax.servlet.http.HttpServletRequest; @@ -43,7 +43,7 @@ public class DefaultReverseProxy implements ReverseProxy { private String xhrReversePrefix; private String captchaReversePrefix; private String collectorUrl; - private CloseableHttpClient proxyClient; + private IPXHttpClient proxyClient; private PredefinedResponseHelper predefinedResponseHelper; private PXConfiguration pxConfiguration; @@ -62,9 +62,11 @@ public DefaultReverseProxy(PXConfiguration pxConfiguration, IPProvider ipProvide PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(pxConfiguration.getMaxConnections()); cm.setDefaultMaxPerRoute(pxConfiguration.getMaxConnectionsPerRoute()); - this.proxyClient = HttpClients.custom() - .setConnectionManager(cm) - .build(); + if (pxConfiguration.getHttpClient() == null) { + this.proxyClient = new PXApacheHttpClient(pxConfiguration); + } else { + this.proxyClient = pxConfiguration.getHttpClient(); + } } public boolean reversePxClient(HttpServletRequest req, HttpServletResponse res) throws URISyntaxException, IOException { @@ -82,7 +84,7 @@ public boolean reversePxClient(HttpServletRequest req, HttpServletResponse res) String url = "https://" + pxConfiguration.getClientHost(); RemoteServer remoteServer = new RemoteServer(url, clientPath, req, res, ipProvider, proxyClient, null, null, pxConfiguration); - HttpRequest proxyRequest = remoteServer.prepareProxyRequest(); + IPXOutgoingRequest proxyRequest = remoteServer.prepareProxyRequest(); remoteServer.handleResponse(proxyRequest, false); return true; } @@ -112,7 +114,7 @@ public boolean reversePxXhr(HttpServletRequest req, HttpServletResponse res) thr String originalUrl = req.getRequestURI().substring(xhrReversePrefix.length()); RemoteServer remoteServer = new RemoteServer(collectorUrl, originalUrl, req, res, ipProvider, proxyClient, predefinedResponse, predefinedResponseHelper, pxConfiguration); - HttpRequest proxyRequest = remoteServer.prepareProxyRequest(); + IPXOutgoingRequest proxyRequest = remoteServer.prepareProxyRequest(); remoteServer.handleResponse(proxyRequest, true); return true; @@ -135,7 +137,7 @@ public boolean reverseCaptcha(HttpServletRequest req, HttpServletResponseWrapper logger.debug("Forwarding request from " + captchaReversePrefix + "/" + originalRequest + "to xhr at " + url); RemoteServer remoteServer = new RemoteServer("", url, req, res, ipProvider, proxyClient, null, predefinedResponseHelper, pxConfiguration); - HttpRequest proxyRequest = remoteServer.prepareProxyRequest(); + IPXOutgoingRequest proxyRequest = remoteServer.prepareProxyRequest(); remoteServer.handleResponse(proxyRequest, true); return true; @@ -149,7 +151,7 @@ public void setPredefinedResponseHelper(PredefinedResponseHelper predefinedRespo this.predefinedResponseHelper = predefinedResponseHelper; } - public void setProxyClient(CloseableHttpClient proxyClient) { + public void setProxyClient(IPXHttpClient proxyClient) { this.proxyClient = proxyClient; } diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index dcbef8ea..803f6a20 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -1,29 +1,29 @@ package com.perimeterx.api.proxy; import com.perimeterx.api.providers.IPProvider; +import com.perimeterx.http.*; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.proxy.PredefinedResponse; import com.perimeterx.utils.PXLogger; import org.apache.http.*; -import org.apache.http.client.HttpClient; import org.apache.http.client.utils.URIUtils; -import org.apache.http.entity.InputStreamEntity; import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicHttpEntityEnclosingRequest; -import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.HeaderGroup; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.io.OutputStream; +import java.io.InputStream; import java.net.HttpCookie; import java.net.URI; import java.net.URISyntaxException; import java.util.BitSet; import java.util.Enumeration; import java.util.Formatter; +import java.util.Objects; + +import static com.perimeterx.utils.PXIOUtils.copy; /** * Created by nitzangoldfeder on 14/05/2018. @@ -35,7 +35,7 @@ public class RemoteServer { private HttpServletResponse res; private HttpServletRequest req; - private HttpClient proxyClient; + private IPXHttpClient proxyClient; private IPProvider ipProvider; private int maxUrlLength = 1000; private PredefinedResponse predefinedResponse; @@ -65,7 +65,7 @@ public class RemoteServer { } public RemoteServer(String serverUrl, String uri, HttpServletRequest req, HttpServletResponse res, - IPProvider ipProvider, HttpClient httpClient, PredefinedResponse predefinedResponse, + IPProvider ipProvider, IPXHttpClient httpClient, PredefinedResponse predefinedResponse, PredefinedResponseHelper predefinedResponseHelper, PXConfiguration pxConfiguration) throws URISyntaxException { this.req = req; this.res = res; @@ -79,36 +79,37 @@ public RemoteServer(String serverUrl, String uri, HttpServletRequest req, HttpSe this.pxConfiguration = pxConfiguration; } - public HttpRequest prepareProxyRequest() throws IOException { + public IPXOutgoingRequest prepareProxyRequest() throws IOException { logger.debug("Preparing proxy request"); String method = req.getMethod(); String proxyRequestUri = rewriteUrlFromRequest(req); - HttpRequest proxyRequest; + PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder requestBuilder = PXOutgoingRequestImpl.builder(); // Copy the body if content-length exists or transfer encoding if (req.getHeader(HttpHeaders.CONTENT_LENGTH) != null || req.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) { - proxyRequest = newProxyRequestWithEntity(method, proxyRequestUri, req); - } else { - // case not, BasicHttpRequest - proxyRequest = new BasicHttpRequest(method, proxyRequestUri); + requestBuilder.body(req.getInputStream()); } + if (!Objects.equals(method, "")) { + requestBuilder.httpMethod(PXHttpMethod.valueOf(method)); + } + requestBuilder.url(proxyRequestUri); // Reverse proxy - copyRequestHeaders(req, proxyRequest); - handleXForwardedForHeader(req, proxyRequest); + copyRequestHeaders(req, requestBuilder); + handleXForwardedForHeader(req, requestBuilder); // PX Logic - handlePXHeaders(proxyRequest); + handlePXHeaders(requestBuilder); - return proxyRequest; + return requestBuilder.build(); } - public HttpResponse handleResponse(HttpRequest proxyRequest, boolean allowPredefinedHandler) { - HttpResponse proxyResponse = null; + public IPXIncomingResponse handleResponse(IPXOutgoingRequest proxyRequest, boolean allowPredefinedHandler) { + IPXIncomingResponse proxyResponse = null; try { // Execute the request proxyResponse = doExecute(proxyRequest); - int statusCode = proxyResponse.getStatusLine().getStatusCode(); + int statusCode = proxyResponse.status().getStatusCode(); // In failure we can check if we enable predefined request or proxy the original response if (allowPredefinedHandler && statusCode >= HttpStatus.SC_BAD_REQUEST) { @@ -144,20 +145,19 @@ public HttpResponse handleResponse(HttpRequest proxyRequest, boolean allowPredef /** * Copy response body data (the entity) from the proxy to the servlet client. */ - protected void copyResponseEntity(HttpResponse proxyResponse) throws IOException { - HttpEntity entity = proxyResponse.getEntity(); - if (entity != null) { - OutputStream servletOutputStream = res.getOutputStream(); - entity.writeTo(servletOutputStream); + protected void copyResponseEntity(IPXIncomingResponse proxyResponse) throws IOException { + InputStream body = proxyResponse.body(); + if (body != null) { + copy(body, res.getOutputStream()); } } /** * Copy proxied response headers back to the servlet client. */ - protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletRequest servletRequest, + protected void copyResponseHeaders(IPXIncomingResponse proxyResponse, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { - for (Header header : proxyResponse.getAllHeaders()) { + for (PXHttpHeader header : proxyResponse.headers()) { copyResponseHeader(servletRequest, servletResponse, header); } } @@ -167,7 +167,7 @@ protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletReques * This is easily overwritten to filter out certain headers if desired. */ protected void copyResponseHeader(HttpServletRequest servletRequest, - HttpServletResponse servletResponse, Header header) { + HttpServletResponse servletResponse, PXHttpHeader header) { String headerName = header.getName(); if (hopByHopHeaders.containsHeader(headerName)) return; @@ -251,43 +251,42 @@ protected void copyProxyCookie(HttpServletRequest servletRequest, * Copy request headers from the servlet client to the proxy request. * This is easily overridden to add your own. */ - protected void copyRequestHeaders(HttpServletRequest servletRequest, HttpRequest proxyRequest) { + protected void copyRequestHeaders(HttpServletRequest servletRequest, PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder requestBuilder) { // Get an Enumeration of all of the header names sent by the client - @SuppressWarnings("unchecked") Enumeration enumerationOfHeaderNames = servletRequest.getHeaderNames(); while (enumerationOfHeaderNames.hasMoreElements()) { String headerName = enumerationOfHeaderNames.nextElement(); - copyRequestHeader(servletRequest, proxyRequest, headerName); + copyRequestHeader(servletRequest, requestBuilder, headerName); } } /** * Append request headers related to PerimeterX */ - protected void handlePXHeaders(HttpRequest proxyRequest) { - proxyRequest.addHeader("X-PX-ENFORCER-TRUE-IP", this.ipProvider.getRequestIP(this.req)); - proxyRequest.addHeader("X-PX-FIRST-PARTY", "1"); + protected void handlePXHeaders(PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder proxyRequest) { + proxyRequest.header(new PXHttpHeader("X-PX-ENFORCER-TRUE-IP", this.ipProvider.getRequestIP(this.req))); + proxyRequest.header(new PXHttpHeader("X-PX-FIRST-PARTY", "1")); } - private void handleXForwardedForHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest) { + private void handleXForwardedForHeader(HttpServletRequest servletRequest, PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder proxyRequest) { String forHeaderName = "X-Forwarded-For"; String forHeader = servletRequest.getRemoteAddr(); String existingForHeader = servletRequest.getHeader(forHeaderName); if (existingForHeader != null) { forHeader = existingForHeader + ", " + forHeader; } - proxyRequest.setHeader(forHeaderName, forHeader); + proxyRequest.header(new PXHttpHeader(forHeaderName, forHeader)); String protoHeaderName = "X-Forwarded-Proto"; String protoHeader = servletRequest.getScheme(); - proxyRequest.setHeader(protoHeaderName, protoHeader); + proxyRequest.header(new PXHttpHeader(protoHeaderName, protoHeader)); } /** * Copy a request header from the servlet client to the proxy request. * This is easily overridden to filter out certain headers if desired. */ - protected void copyRequestHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest, + protected void copyRequestHeader(HttpServletRequest servletRequest, PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder proxyRequest, String headerName) { //Instead the content-length is effectively set via InputStreamEntity if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { @@ -302,7 +301,6 @@ protected void copyRequestHeader(HttpServletRequest servletRequest, HttpRequest return; } - @SuppressWarnings("unchecked") Enumeration headers = servletRequest.getHeaders(headerName); while (headers.hasMoreElements()) {//sometimes more than one value String headerValue = headers.nextElement(); @@ -313,26 +311,11 @@ protected void copyRequestHeader(HttpServletRequest servletRequest, HttpRequest headerValue += ":" + host.getPort(); } } - proxyRequest.addHeader(headerName, headerValue); + proxyRequest.header(new PXHttpHeader(headerName, headerValue)); } } - protected HttpRequest newProxyRequestWithEntity(String method, String proxyRequestUri, HttpServletRequest servletRequest) throws IOException { - HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri); - // Add the input entity (streamed) - // note: we don't bother ensuring we close the servletInputStream since the container handles it - eProxyRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), getContentLength(servletRequest))); - return eProxyRequest; - } - // Get the header value as a long in order to more correctly proxy very large requests - private long getContentLength(HttpServletRequest request) { - String contentLengthHeader = request.getHeader(CONTENT_LENGTH_HEADER); - if (contentLengthHeader != null) { - return Long.parseLong(contentLengthHeader); - } - return -1L; - } protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) { logger.debug("Rewiring url from request"); @@ -427,7 +410,7 @@ private static CharSequence encodeUriQuery(CharSequence in, boolean encodePercen asciiQueryChars.set((int) '%');//leave existing percent escapes in place } - protected HttpResponse doExecute(HttpRequest proxyRequest) throws IOException { - return proxyClient.execute(targetHost, proxyRequest); + protected IPXIncomingResponse doExecute(IPXOutgoingRequest proxyRequest) throws IOException { + return proxyClient.send(proxyRequest); } } diff --git a/src/main/java/com/perimeterx/api/proxy/mock/MockReverseProxyFactory.java b/src/main/java/com/perimeterx/api/proxy/mock/MockReverseProxyFactory.java new file mode 100644 index 00000000..ade80552 --- /dev/null +++ b/src/main/java/com/perimeterx/api/proxy/mock/MockReverseProxyFactory.java @@ -0,0 +1,12 @@ +package com.perimeterx.api.proxy.mock; + +import com.perimeterx.api.proxy.ReverseProxy; + +public final class MockReverseProxyFactory { + private MockReverseProxyFactory() { + } + + public static ReverseProxy createNeverReverseProxy() { + return NeverReverseProxy.getInstance(); + } +} diff --git a/src/main/java/com/perimeterx/api/proxy/mock/NeverReverseProxy.java b/src/main/java/com/perimeterx/api/proxy/mock/NeverReverseProxy.java new file mode 100644 index 00000000..e66846a5 --- /dev/null +++ b/src/main/java/com/perimeterx/api/proxy/mock/NeverReverseProxy.java @@ -0,0 +1,38 @@ +package com.perimeterx.api.proxy.mock; + +import com.perimeterx.api.proxy.ReverseProxy; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.IOException; +import java.net.URISyntaxException; + +public class NeverReverseProxy implements ReverseProxy { + public static NeverReverseProxy instance; + + public static NeverReverseProxy getInstance() { + if (instance == null) { + instance = new NeverReverseProxy(); + } + return instance; + } + + private NeverReverseProxy() { + } + + @Override + public boolean reversePxClient(HttpServletRequest req, HttpServletResponse res) throws URISyntaxException, IOException { + return false; + } + + @Override + public boolean reversePxXhr(HttpServletRequest req, HttpServletResponse res) throws URISyntaxException, IOException { + return false; + } + + @Override + public boolean reverseCaptcha(HttpServletRequest req, HttpServletResponseWrapper res) throws IOException, URISyntaxException { + return false; + } +} diff --git a/src/main/java/com/perimeterx/http/IPXHttpClient.java b/src/main/java/com/perimeterx/http/IPXHttpClient.java index 62e65dff..1e9f43d8 100644 --- a/src/main/java/com/perimeterx/http/IPXHttpClient.java +++ b/src/main/java/com/perimeterx/http/IPXHttpClient.java @@ -4,9 +4,6 @@ import java.io.IOException; public interface IPXHttpClient extends Closeable { - default void init() throws IOException { - } - IPXIncomingResponse send(IPXOutgoingRequest request) throws IOException; void sendAsync(IPXOutgoingRequest request) throws IOException; diff --git a/src/main/java/com/perimeterx/http/IPXIncomingResponse.java b/src/main/java/com/perimeterx/http/IPXIncomingResponse.java index 71b2101d..68bc9345 100644 --- a/src/main/java/com/perimeterx/http/IPXIncomingResponse.java +++ b/src/main/java/com/perimeterx/http/IPXIncomingResponse.java @@ -2,9 +2,10 @@ import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; public interface IPXIncomingResponse extends Closeable { - String body() throws IOException; + InputStream body() throws IOException; PXHttpStatus status(); PXHttpHeader[] headers(); } diff --git a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java index 8f489f58..7ec4d481 100644 --- a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java +++ b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java @@ -1,5 +1,6 @@ package com.perimeterx.http; +import java.io.InputStream; import java.util.List; public interface IPXOutgoingRequest { @@ -7,7 +8,7 @@ public interface IPXOutgoingRequest { PXHttpMethod getHttpMethod(); - String getBody(); + InputStream getBody(); List getHeaders(); diff --git a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java index 6cc4eb8c..66ecdd31 100644 --- a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java @@ -5,7 +5,6 @@ import com.perimeterx.utils.PXCommonUtils; import com.perimeterx.utils.PXLogger; import org.apache.http.client.methods.*; -import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; @@ -20,9 +19,11 @@ import org.apache.http.nio.reactor.IOReactorExceptionHandler; import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.concurrent.TimeUnit; -import static java.nio.charset.StandardCharsets.UTF_8; public class PXApacheHttpClient implements IPXHttpClient { private static final PXLogger logger = PXLogger.getLogger(PXApacheHttpClient.class); @@ -34,13 +35,21 @@ public class PXApacheHttpClient implements IPXHttpClient { private TimerValidateRequestsQueue timerConfigUpdater; public PXApacheHttpClient(PXConfiguration pxConfiguration) { - this.pxConfiguration = pxConfiguration; + this(pxConfiguration, null, null); } - @Override - public void init() throws IOException { - initHttpClient(); - initAsyncHttpClient(); + public PXApacheHttpClient(PXConfiguration pxConfiguration, + CloseableHttpClient httpClient, + CloseableHttpAsyncClient asyncHttpClient) { + this.pxConfiguration = pxConfiguration; + this.httpClient = httpClient; + this.asyncHttpClient = asyncHttpClient; + if (this.httpClient == null) { + initHttpClient(); + } + if (this.asyncHttpClient == null) { + initAsyncHttpClient(); + } } @Override @@ -100,17 +109,20 @@ private void initHttpClient() { .build(); } - private void initAsyncHttpClient() throws IOReactorException { - DefaultConnectingIOReactor ioReactor = getDefaultConnectingIOReactor(); - - PoolingNHttpClientConnectionManager nHttpConnectionManager = new PoolingNHttpClientConnectionManager(ioReactor); - CloseableHttpAsyncClient closeableHttpAsyncClient = HttpAsyncClients.custom() - .setConnectionManager(nHttpConnectionManager) - .build(); - closeableHttpAsyncClient.start(); - asyncHttpClient = closeableHttpAsyncClient; - this.timerConfigUpdater = new TimerValidateRequestsQueue(nHttpConnectionManager, pxConfiguration); - timerConfigUpdater.schedule(); + private void initAsyncHttpClient() { + try { + DefaultConnectingIOReactor ioReactor = getDefaultConnectingIOReactor(); + PoolingNHttpClientConnectionManager nHttpConnectionManager = new PoolingNHttpClientConnectionManager(ioReactor); + CloseableHttpAsyncClient closeableHttpAsyncClient = HttpAsyncClients.custom() + .setConnectionManager(nHttpConnectionManager) + .build(); + closeableHttpAsyncClient.start(); + asyncHttpClient = closeableHttpAsyncClient; + this.timerConfigUpdater = new TimerValidateRequestsQueue(nHttpConnectionManager, pxConfiguration); + timerConfigUpdater.schedule(); + } catch (IOReactorException e) { + throw new RuntimeException(e); + } } private static DefaultConnectingIOReactor getDefaultConnectingIOReactor() throws IOReactorException { @@ -136,7 +148,11 @@ public boolean handle(RuntimeException ex) { private HttpRequestBase createRequest(IPXOutgoingRequest request) { HttpRequestBase req = buildBaseRequest(request); - + try { + req.setURI(new URI(request.getUrl())); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } for (PXHttpHeader header : request.getHeaders()) { req.addHeader(header.getName(), header.getValue()); } @@ -144,25 +160,22 @@ private HttpRequestBase createRequest(IPXOutgoingRequest request) { return req; } - private HttpRequestBase createGetRequest(IPXOutgoingRequest request) { - return new HttpGet(request.getUrl()); - - } - - private HttpRequestBase createPostRequest(IPXOutgoingRequest request) { - HttpPost post = new HttpPost(request.getUrl()); - post.setEntity(new StringEntity(request.getBody(), UTF_8)); - return post; - } - private HttpRequestBase buildBaseRequest(IPXOutgoingRequest request) { - switch (request.getHttpMethod()) { - case POST: - return createPostRequest(request); - case GET: - return createGetRequest(request); - default: - throw new IllegalArgumentException("unsupported method " + request.getHttpMethod()); + InputStream body = request.getBody(); + if (body != null) { + return new HttpEntityEnclosingRequestBase() { + @Override + public String getMethod() { + return request.getHttpMethod().name(); + } + }; + } else { + return new HttpRequestBase() { + @Override + public String getMethod() { + return request.getHttpMethod().name(); + } + }; } } diff --git a/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java b/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java index a7a19cc9..372cb13c 100644 --- a/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java +++ b/src/main/java/com/perimeterx/http/PXApacheIncomingResponse.java @@ -2,10 +2,12 @@ import org.apache.commons.io.IOUtils; import org.apache.http.Header; +import org.apache.http.HttpEntity; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import java.io.IOException; +import java.io.InputStream; import static java.nio.charset.StandardCharsets.UTF_8; @@ -17,8 +19,13 @@ public PXApacheIncomingResponse(CloseableHttpResponse response) { } @Override - public String body() throws IOException { - return IOUtils.toString(response.getEntity().getContent(), UTF_8); + public InputStream body() throws IOException { + HttpEntity entity = response.getEntity(); + if (entity == null) { + return null; + } + + return entity.getContent(); } @Override diff --git a/src/main/java/com/perimeterx/http/PXHttpClient.java b/src/main/java/com/perimeterx/http/PXHttpClient.java index aed131ae..aabd60e7 100644 --- a/src/main/java/com/perimeterx/http/PXHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXHttpClient.java @@ -14,15 +14,19 @@ import com.perimeterx.utils.Constants; import com.perimeterx.utils.JsonUtils; import com.perimeterx.utils.PXLogger; +import org.apache.commons.io.IOUtils; import org.apache.http.HttpHeaders; import org.apache.http.HttpStatus; import org.apache.http.conn.ConnectTimeoutException; +import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.List; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Low level HTTP client @@ -44,7 +48,6 @@ public PXHttpClient(PXConfiguration pxConfiguration) throws PXException { } else { this.client = pxConfiguration.getHttpClient(); } - client.init(); } catch (Exception e) { throw new PXException(e); } @@ -90,7 +93,7 @@ private IPXIncomingResponse executeRiskAPICall(String requestBody, PXContext pxC IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() .url(this.pxConfiguration.getServerURL() + Constants.API_RISK) .httpMethod(PXHttpMethod.POST) - .body(requestBody) + .body(new ByteArrayInputStream(requestBody.getBytes())) .build(); try { @@ -117,7 +120,7 @@ private RiskResponse validateRiskAPIResponse(IPXIncomingResponse httpResponse, P } try { - String s = httpResponse.body(); + String s = IOUtils.toString(httpResponse.body(), UTF_8); if (s.equals("null")) { throw new PXException("Risk API returned null JSON"); } @@ -158,7 +161,7 @@ public void sendActivity(Activity activity) throws IOException { String requestBody = JsonUtils.writer.writeValueAsString(activity); logger.debug("Sending Activity: {}", requestBody); IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() - .body(requestBody) + .body(new ByteArrayInputStream(requestBody.getBytes())) .url(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES) .build(); httpResponse = client.send(request); @@ -178,7 +181,7 @@ public void sendBatchActivities(List activities) throws IOException { IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() .url(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES) .httpMethod(PXHttpMethod.POST) - .body(requestBody) + .body(new ByteArrayInputStream(requestBody.getBytes())) .header(new PXHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json")) .header(new PXHttpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken())) .build(); @@ -201,7 +204,7 @@ public PXDynamicConfiguration getConfigurationFromServer() { try (IPXIncomingResponse httpResponse = client.send(request)) { int httpCode = httpResponse.status().getStatusCode(); if (httpCode == HttpStatus.SC_OK) { - String bodyContent = httpResponse.body(); + String bodyContent = IOUtils.toString(httpResponse.body(), UTF_8); stub = JsonUtils.pxConfigurationStubReader.readValue(bodyContent); logger.debug("[getConfiguration] GET request successfully executed"); } else if (httpResponse.status().getStatusCode() == HttpStatus.SC_NO_CONTENT) { @@ -223,7 +226,7 @@ public void sendEnforcerTelemetry(EnforcerTelemetry enforcerTelemetry) throws IO IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() .url(this.pxConfiguration.getServerURL() + Constants.API_ENFORCER_TELEMETRY) .httpMethod(PXHttpMethod.POST) - .body(requestBody) + .body(new ByteArrayInputStream(requestBody.getBytes())) .header(new PXHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json")) .header((new PXHttpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken()))) .build(); diff --git a/src/main/java/com/perimeterx/http/PXHttpMethod.java b/src/main/java/com/perimeterx/http/PXHttpMethod.java index 8d2d306b..ef9a5ef4 100644 --- a/src/main/java/com/perimeterx/http/PXHttpMethod.java +++ b/src/main/java/com/perimeterx/http/PXHttpMethod.java @@ -1,6 +1,11 @@ package com.perimeterx.http; public enum PXHttpMethod { + GET, POST, - GET + PUT, + PATCH, + DELETE, + HEAD, + OPTIONS } diff --git a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java index dfc8a6f0..151efbfb 100644 --- a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java +++ b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java @@ -2,6 +2,7 @@ import lombok.*; +import java.io.InputStream; import java.util.List; @Getter @@ -16,7 +17,7 @@ public class PXOutgoingRequestImpl implements IPXOutgoingRequest { private final PXHttpMethod httpMethod = PXHttpMethod.GET; @Builder.Default - private final String body = null; + private final InputStream body = null; @Singular private final List headers; diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index d9a36e8c..f4620cb1 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -11,6 +11,7 @@ import com.perimeterx.api.blockhandler.DefaultBlockHandler; import com.perimeterx.api.providers.CustomParametersProvider; import com.perimeterx.api.providers.DefaultCustomParametersProvider; +import com.perimeterx.api.proxy.ReverseProxy; import com.perimeterx.http.IPXHttpClient; import com.perimeterx.http.PXClient; import com.perimeterx.models.configuration.credentialsIntelligenceconfig.CILoginMap; @@ -279,6 +280,9 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Builder.Default private PXClient pxClient = null; + + @Builder.Default + private ReverseProxy pxReverseProxy = null; /** * @return Configuration Object clone without cookieKey and authToken **/ diff --git a/src/main/java/com/perimeterx/utils/PXIOUtils.java b/src/main/java/com/perimeterx/utils/PXIOUtils.java new file mode 100644 index 00000000..eb43a072 --- /dev/null +++ b/src/main/java/com/perimeterx/utils/PXIOUtils.java @@ -0,0 +1,20 @@ +package com.perimeterx.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public final class PXIOUtils { + private PXIOUtils() { + + } + + public static void copy(InputStream source, OutputStream target) throws IOException { + byte[] buf = new byte[8192]; + int length; + while ((length = source.read(buf)) != -1) { + target.write(buf, 0, length); + } + } + +} diff --git a/src/test/java/com/perimeterx/api/ReverseProxyTest.java b/src/test/java/com/perimeterx/api/ReverseProxyTest.java index e83b0234..7d8fbd62 100644 --- a/src/test/java/com/perimeterx/api/ReverseProxyTest.java +++ b/src/test/java/com/perimeterx/api/ReverseProxyTest.java @@ -3,10 +3,12 @@ import com.perimeterx.api.providers.CombinedIPProvider; import com.perimeterx.api.proxy.DefaultReverseProxy; import com.perimeterx.api.proxy.ReverseProxy; +import com.perimeterx.http.PXApacheHttpClient; import com.perimeterx.http.PXClient; import com.perimeterx.models.configuration.PXConfiguration; import org.apache.http.*; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicHeader; @@ -105,17 +107,17 @@ public void testReverseProxyClientMethod() throws Exception { CloseableHttpClient mockProxyHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedHttpResponse = getMockedHttpResponse("function()", "application/javascript"); - when(mockProxyHttpClient.execute(any(HttpHost.class), any(HttpRequest.class))).thenReturn(mockedHttpResponse); + when(mockProxyHttpClient.execute(any(HttpUriRequest.class))).thenReturn(mockedHttpResponse); ReverseProxy reverseProxy = new DefaultReverseProxy(pxConfiguration, new CombinedIPProvider(pxConfiguration)); - ((DefaultReverseProxy) reverseProxy).setProxyClient(mockProxyHttpClient); + ((DefaultReverseProxy) reverseProxy).setProxyClient(new PXApacheHttpClient(pxConfiguration, mockProxyHttpClient, null)); HttpServletRequest request = new MockHttpServletRequest(); ((MockHttpServletRequest) request).setRequestURI("/12345678/init.js"); HttpServletResponse response = new MockHttpServletResponse(); reverseProxy.reversePxClient(request, response); - verify(mockProxyHttpClient, times(1)).execute(any(HttpHost.class), any(HttpRequest.class)); + verify(mockProxyHttpClient, times(1)).execute(any(HttpUriRequest.class)); Assert.assertEquals("function()", ((MockHttpServletResponse) response).getContentAsString()); Assert.assertEquals(response.getHeaderNames().size(), mockedHttpResponse.getAllHeaders().length); } @@ -131,17 +133,17 @@ public void testReverseProxyXHRMethod() throws Exception { CloseableHttpClient mockProxyHttpClient = mock(CloseableHttpClient.class); CloseableHttpResponse mockedHttpResponse = getMockedHttpResponse("{\"some\": '\"answer\"}", "application/json"); - when(mockProxyHttpClient.execute(any(HttpHost.class), any(HttpRequest.class))).thenReturn(mockedHttpResponse); + when(mockProxyHttpClient.execute(any())).thenReturn(mockedHttpResponse); ReverseProxy reverseProxy = new DefaultReverseProxy(pxConfiguration, new CombinedIPProvider(pxConfiguration)); - ((DefaultReverseProxy) reverseProxy).setProxyClient(mockProxyHttpClient); + ((DefaultReverseProxy) reverseProxy).setProxyClient(new PXApacheHttpClient(pxConfiguration, mockProxyHttpClient, null)); HttpServletRequest request = new MockHttpServletRequest(); ((MockHttpServletRequest) request).setRequestURI("/12345678/xhr/api/v1/collector"); HttpServletResponse response = new MockHttpServletResponse(); reverseProxy.reversePxXhr(request, response); - verify(mockProxyHttpClient, times(1)).execute(any(HttpHost.class), any(HttpRequest.class)); + verify(mockProxyHttpClient, times(1)).execute(any()); Assert.assertEquals("{\"some\": '\"answer\"}", ((MockHttpServletResponse) response).getContentAsString()); Assert.assertEquals(response.getHeaderNames().size(), mockedHttpResponse.getAllHeaders().length); } From 25519a04d6d73ddb146a2d52d41959327127d748 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 19:18:39 +0300 Subject: [PATCH 04/12] Fixed http client not passing the body --- .../com/perimeterx/api/proxy/RemoteServer.java | 11 ++++++++--- .../perimeterx/http/IPXOutgoingRequest.java | 2 +- .../perimeterx/http/PXApacheHttpClient.java | 7 +++++-- .../java/com/perimeterx/http/PXHttpClient.java | 8 ++++---- .../perimeterx/http/PXOutgoingRequestImpl.java | 18 ++++++++++++++++-- .../com/perimeterx/http/PXRequestBody.java | 13 +++++++++++++ 6 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/perimeterx/http/PXRequestBody.java diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index 803f6a20..bfdb07ca 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -85,9 +85,14 @@ public IPXOutgoingRequest prepareProxyRequest() throws IOException { String proxyRequestUri = rewriteUrlFromRequest(req); PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder requestBuilder = PXOutgoingRequestImpl.builder(); - // Copy the body if content-length exists or transfer encoding - if (req.getHeader(HttpHeaders.CONTENT_LENGTH) != null || req.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) { - requestBuilder.body(req.getInputStream()); + // Copy the body if content-length exists + if (req.getHeader(HttpHeaders.CONTENT_LENGTH) != null) { + requestBuilder.body( + new PXRequestBody( + req.getInputStream(), + Integer.parseInt(req.getHeader(HttpHeaders.CONTENT_LENGTH)) + ) + ); } if (!Objects.equals(method, "")) { diff --git a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java index 7ec4d481..97e6fead 100644 --- a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java +++ b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java @@ -8,7 +8,7 @@ public interface IPXOutgoingRequest { PXHttpMethod getHttpMethod(); - InputStream getBody(); + PXRequestBody getBody(); List getHeaders(); diff --git a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java index 66ecdd31..a34112c9 100644 --- a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java @@ -5,6 +5,7 @@ import com.perimeterx.utils.PXCommonUtils; import com.perimeterx.utils.PXLogger; import org.apache.http.client.methods.*; +import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; @@ -161,14 +162,16 @@ private HttpRequestBase createRequest(IPXOutgoingRequest request) { } private HttpRequestBase buildBaseRequest(IPXOutgoingRequest request) { - InputStream body = request.getBody(); + PXRequestBody body = request.getBody(); if (body != null) { - return new HttpEntityEnclosingRequestBase() { + HttpEntityEnclosingRequestBase req = new HttpEntityEnclosingRequestBase() { @Override public String getMethod() { return request.getHttpMethod().name(); } }; + req.setEntity(new InputStreamEntity(body.getInputStream(), body.getLength())); + return req; } else { return new HttpRequestBase() { @Override diff --git a/src/main/java/com/perimeterx/http/PXHttpClient.java b/src/main/java/com/perimeterx/http/PXHttpClient.java index aabd60e7..0f4ce621 100644 --- a/src/main/java/com/perimeterx/http/PXHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXHttpClient.java @@ -93,7 +93,7 @@ private IPXIncomingResponse executeRiskAPICall(String requestBody, PXContext pxC IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() .url(this.pxConfiguration.getServerURL() + Constants.API_RISK) .httpMethod(PXHttpMethod.POST) - .body(new ByteArrayInputStream(requestBody.getBytes())) + .stringBody(requestBody) .build(); try { @@ -161,7 +161,7 @@ public void sendActivity(Activity activity) throws IOException { String requestBody = JsonUtils.writer.writeValueAsString(activity); logger.debug("Sending Activity: {}", requestBody); IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() - .body(new ByteArrayInputStream(requestBody.getBytes())) + .stringBody(requestBody) .url(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES) .build(); httpResponse = client.send(request); @@ -181,7 +181,7 @@ public void sendBatchActivities(List activities) throws IOException { IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() .url(this.pxConfiguration.getServerURL() + Constants.API_ACTIVITIES) .httpMethod(PXHttpMethod.POST) - .body(new ByteArrayInputStream(requestBody.getBytes())) + .stringBody(requestBody) .header(new PXHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json")) .header(new PXHttpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken())) .build(); @@ -226,7 +226,7 @@ public void sendEnforcerTelemetry(EnforcerTelemetry enforcerTelemetry) throws IO IPXOutgoingRequest request = PXOutgoingRequestImpl.builder() .url(this.pxConfiguration.getServerURL() + Constants.API_ENFORCER_TELEMETRY) .httpMethod(PXHttpMethod.POST) - .body(new ByteArrayInputStream(requestBody.getBytes())) + .stringBody(requestBody) .header(new PXHttpHeader(HttpHeaders.CONTENT_TYPE, "application/json")) .header((new PXHttpHeader(HttpHeaders.AUTHORIZATION, "Bearer " + pxConfiguration.getAuthToken()))) .build(); diff --git a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java index 151efbfb..243ef95e 100644 --- a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java +++ b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java @@ -2,7 +2,7 @@ import lombok.*; -import java.io.InputStream; +import java.io.ByteArrayInputStream; import java.util.List; @Getter @@ -17,9 +17,23 @@ public class PXOutgoingRequestImpl implements IPXOutgoingRequest { private final PXHttpMethod httpMethod = PXHttpMethod.GET; @Builder.Default - private final InputStream body = null; + private final PXRequestBody body = null; @Singular private final List headers; + public static class PXOutgoingRequestImplBuilder { + public PXOutgoingRequestImplBuilder stringBody(String body) { + if (body == null) { + this.body(null); + return this; + } + + PXRequestBody b = new PXRequestBody( + new ByteArrayInputStream(body.getBytes()), + body.length() + ); + return this.body(b); + } + } } diff --git a/src/main/java/com/perimeterx/http/PXRequestBody.java b/src/main/java/com/perimeterx/http/PXRequestBody.java new file mode 100644 index 00000000..8b17e9fb --- /dev/null +++ b/src/main/java/com/perimeterx/http/PXRequestBody.java @@ -0,0 +1,13 @@ +package com.perimeterx.http; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.InputStream; + +@Getter +@AllArgsConstructor +public class PXRequestBody { + private InputStream inputStream; + private int length; +} From 275431df174f156b2eac08a747e2b9dbe7c0e510 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 19:26:18 +0300 Subject: [PATCH 05/12] Shorter name for builder --- .../java/com/perimeterx/api/proxy/RemoteServer.java | 11 ++++++----- .../com/perimeterx/http/PXOutgoingRequestImpl.java | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index bfdb07ca..a3a30ff3 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -10,6 +10,7 @@ import org.apache.http.message.BasicHeader; import org.apache.http.message.HeaderGroup; +import com.perimeterx.http.PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -84,7 +85,7 @@ public IPXOutgoingRequest prepareProxyRequest() throws IOException { String method = req.getMethod(); String proxyRequestUri = rewriteUrlFromRequest(req); - PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder requestBuilder = PXOutgoingRequestImpl.builder(); + PXOutgoingRequestImplBuilder requestBuilder = PXOutgoingRequestImpl.builder(); // Copy the body if content-length exists if (req.getHeader(HttpHeaders.CONTENT_LENGTH) != null) { requestBuilder.body( @@ -256,7 +257,7 @@ protected void copyProxyCookie(HttpServletRequest servletRequest, * Copy request headers from the servlet client to the proxy request. * This is easily overridden to add your own. */ - protected void copyRequestHeaders(HttpServletRequest servletRequest, PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder requestBuilder) { + protected void copyRequestHeaders(HttpServletRequest servletRequest, PXOutgoingRequestImplBuilder requestBuilder) { // Get an Enumeration of all of the header names sent by the client Enumeration enumerationOfHeaderNames = servletRequest.getHeaderNames(); while (enumerationOfHeaderNames.hasMoreElements()) { @@ -268,12 +269,12 @@ protected void copyRequestHeaders(HttpServletRequest servletRequest, PXOutgoingR /** * Append request headers related to PerimeterX */ - protected void handlePXHeaders(PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder proxyRequest) { + protected void handlePXHeaders(PXOutgoingRequestImplBuilder proxyRequest) { proxyRequest.header(new PXHttpHeader("X-PX-ENFORCER-TRUE-IP", this.ipProvider.getRequestIP(this.req))); proxyRequest.header(new PXHttpHeader("X-PX-FIRST-PARTY", "1")); } - private void handleXForwardedForHeader(HttpServletRequest servletRequest, PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder proxyRequest) { + private void handleXForwardedForHeader(HttpServletRequest servletRequest, PXOutgoingRequestImplBuilder proxyRequest) { String forHeaderName = "X-Forwarded-For"; String forHeader = servletRequest.getRemoteAddr(); String existingForHeader = servletRequest.getHeader(forHeaderName); @@ -291,7 +292,7 @@ private void handleXForwardedForHeader(HttpServletRequest servletRequest, PXOutg * Copy a request header from the servlet client to the proxy request. * This is easily overridden to filter out certain headers if desired. */ - protected void copyRequestHeader(HttpServletRequest servletRequest, PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder proxyRequest, + protected void copyRequestHeader(HttpServletRequest servletRequest, PXOutgoingRequestImplBuilder proxyRequest, String headerName) { //Instead the content-length is effectively set via InputStreamEntity if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) { diff --git a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java index 243ef95e..084ddef9 100644 --- a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java +++ b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java @@ -22,6 +22,7 @@ public class PXOutgoingRequestImpl implements IPXOutgoingRequest { @Singular private final List headers; + // Additional methods are generated by Lombok public static class PXOutgoingRequestImplBuilder { public PXOutgoingRequestImplBuilder stringBody(String body) { if (body == null) { From e2c87dcaeab354778ad0fd94f327a3556081f1c8 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 19:34:41 +0300 Subject: [PATCH 06/12] replace content length with long instead of int --- .../java/com/perimeterx/api/proxy/RemoteServer.java | 10 ++++++++-- src/main/java/com/perimeterx/http/PXRequestBody.java | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index a3a30ff3..1bc45334 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -91,7 +91,7 @@ public IPXOutgoingRequest prepareProxyRequest() throws IOException { requestBuilder.body( new PXRequestBody( req.getInputStream(), - Integer.parseInt(req.getHeader(HttpHeaders.CONTENT_LENGTH)) + getContentLength(req) ) ); } @@ -322,7 +322,13 @@ protected void copyRequestHeader(HttpServletRequest servletRequest, PXOutgoingRe } // Get the header value as a long in order to more correctly proxy very large requests - + private long getContentLength(HttpServletRequest request) { + String contentLengthHeader = request.getHeader(CONTENT_LENGTH_HEADER); + if (contentLengthHeader != null) { + return Long.parseLong(contentLengthHeader); + } + return -1L; + } protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) { logger.debug("Rewiring url from request"); StringBuilder uri = new StringBuilder(this.maxUrlLength); diff --git a/src/main/java/com/perimeterx/http/PXRequestBody.java b/src/main/java/com/perimeterx/http/PXRequestBody.java index 8b17e9fb..e11926b3 100644 --- a/src/main/java/com/perimeterx/http/PXRequestBody.java +++ b/src/main/java/com/perimeterx/http/PXRequestBody.java @@ -9,5 +9,5 @@ @AllArgsConstructor public class PXRequestBody { private InputStream inputStream; - private int length; + private long length; } From 3d93d211015672822d18bc3310e73673941324c5 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 19:36:45 +0300 Subject: [PATCH 07/12] remove spaces --- src/main/java/com/perimeterx/http/IPXOutgoingRequest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java index 97e6fead..18529a11 100644 --- a/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java +++ b/src/main/java/com/perimeterx/http/IPXOutgoingRequest.java @@ -5,12 +5,7 @@ public interface IPXOutgoingRequest { String getUrl(); - PXHttpMethod getHttpMethod(); - PXRequestBody getBody(); - List getHeaders(); - - } \ No newline at end of file From e9e1ef3d9e6591df8c44b04433aa55ba97d25b79 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 19:42:03 +0300 Subject: [PATCH 08/12] directly returning in builder --- src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java index 084ddef9..f3e2f7b3 100644 --- a/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java +++ b/src/main/java/com/perimeterx/http/PXOutgoingRequestImpl.java @@ -26,8 +26,7 @@ public class PXOutgoingRequestImpl implements IPXOutgoingRequest { public static class PXOutgoingRequestImplBuilder { public PXOutgoingRequestImplBuilder stringBody(String body) { if (body == null) { - this.body(null); - return this; + return this.body(null); } PXRequestBody b = new PXRequestBody( From 47f204e326cf0155d32222055d01e1030be82530 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Tue, 5 Sep 2023 19:42:43 +0300 Subject: [PATCH 09/12] rename to MockPXClient --- .../http/mock/{MockPxClient.java => MockPXClient.java} | 4 ++-- .../java/com/perimeterx/http/mock/MockPXClientFactory.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/perimeterx/http/mock/{MockPxClient.java => MockPXClient.java} (95%) diff --git a/src/main/java/com/perimeterx/http/mock/MockPxClient.java b/src/main/java/com/perimeterx/http/mock/MockPXClient.java similarity index 95% rename from src/main/java/com/perimeterx/http/mock/MockPxClient.java rename to src/main/java/com/perimeterx/http/mock/MockPXClient.java index 03ca5621..3f65054b 100644 --- a/src/main/java/com/perimeterx/http/mock/MockPxClient.java +++ b/src/main/java/com/perimeterx/http/mock/MockPXClient.java @@ -13,8 +13,8 @@ @Builder @AllArgsConstructor -public class MockPxClient implements PXClient { - private static final PXLogger logger = PXLogger.getLogger(MockPxClient.class); +public class MockPXClient implements PXClient { + private static final PXLogger logger = PXLogger.getLogger(MockPXClient.class); protected RiskResponse riskResponse; protected PXDynamicConfiguration pxDynamicConfiguration; @Override diff --git a/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java b/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java index 182dc5ff..f9bd3452 100644 --- a/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java +++ b/src/main/java/com/perimeterx/http/mock/MockPXClientFactory.java @@ -8,13 +8,13 @@ private MockPXClientFactory() { } public static PXClient createPassAllPXClient() { - return MockPxClient.builder() + return MockPXClient.builder() .riskResponse(new RiskResponse("uuid", 0, 0, "c", null, null, "", "")) .build(); } public static PXClient createBlockAllPXClient() { - return MockPxClient.builder() + return MockPXClient.builder() .riskResponse(new RiskResponse("uuid", 0, 100, "c", null, null, "", "")) .build(); } From d6c5afc8923bb886ebe029087e30d31b46fcd143 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Wed, 6 Sep 2023 15:39:29 +0300 Subject: [PATCH 10/12] Using built in content length --- src/main/java/com/perimeterx/api/proxy/RemoteServer.java | 8 ++------ src/main/java/com/perimeterx/http/PXApacheHttpClient.java | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java index 1bc45334..55e476d1 100644 --- a/src/main/java/com/perimeterx/api/proxy/RemoteServer.java +++ b/src/main/java/com/perimeterx/api/proxy/RemoteServer.java @@ -87,7 +87,7 @@ public IPXOutgoingRequest prepareProxyRequest() throws IOException { PXOutgoingRequestImplBuilder requestBuilder = PXOutgoingRequestImpl.builder(); // Copy the body if content-length exists - if (req.getHeader(HttpHeaders.CONTENT_LENGTH) != null) { + if (getContentLength(req) != -1) { requestBuilder.body( new PXRequestBody( req.getInputStream(), @@ -323,11 +323,7 @@ protected void copyRequestHeader(HttpServletRequest servletRequest, PXOutgoingRe // Get the header value as a long in order to more correctly proxy very large requests private long getContentLength(HttpServletRequest request) { - String contentLengthHeader = request.getHeader(CONTENT_LENGTH_HEADER); - if (contentLengthHeader != null) { - return Long.parseLong(contentLengthHeader); - } - return -1L; + return request.getContentLength(); } protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) { logger.debug("Rewiring url from request"); diff --git a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java index a34112c9..2a22c322 100644 --- a/src/main/java/com/perimeterx/http/PXApacheHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXApacheHttpClient.java @@ -163,7 +163,7 @@ private HttpRequestBase createRequest(IPXOutgoingRequest request) { private HttpRequestBase buildBaseRequest(IPXOutgoingRequest request) { PXRequestBody body = request.getBody(); - if (body != null) { + if (body != null && body.getInputStream() != null) { HttpEntityEnclosingRequestBase req = new HttpEntityEnclosingRequestBase() { @Override public String getMethod() { From 0ff07feb5f34e86c95c04c18185134c81f2256eb Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Wed, 6 Sep 2023 16:02:24 +0300 Subject: [PATCH 11/12] Using configuration to get default instances --- .../java/com/perimeterx/api/PerimeterX.java | 14 +--- .../api/proxy/DefaultReverseProxy.java | 6 +- .../com/perimeterx/http/PXHttpClient.java | 7 +- .../models/configuration/PXConfiguration.java | 73 ++++++++++++++++++- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/perimeterx/api/PerimeterX.java b/src/main/java/com/perimeterx/api/PerimeterX.java index 9ecb2028..7a6b70d2 100644 --- a/src/main/java/com/perimeterx/api/PerimeterX.java +++ b/src/main/java/com/perimeterx/api/PerimeterX.java @@ -119,19 +119,11 @@ private void init(PXConfiguration configuration) throws PXException { } private void setReverseProxy(PXConfiguration configuration) { - if(this.configuration.getPxReverseProxy() == null) { - this.reverseProxy = new DefaultReverseProxy(configuration, ipProvider); - } else { - this.reverseProxy = configuration.getPxReverseProxy(); - } + this.reverseProxy = configuration.getReverseProxyInstance(); } private void setPxClient(PXConfiguration configuration) throws PXException { - if(configuration.getPxClient() == null) { - this.pxClient = new PXHttpClient(configuration); - } else { - this.pxClient = configuration.getPxClient(); - } + this.pxClient = configuration.getPxClientInstance(); } private void setVerificationHandler() { @@ -355,7 +347,7 @@ public void setVerificationHandler(VerificationHandler verificationHandler) { @Override public void close() throws IOException { - if(this.pxClient != null) { + if (this.pxClient != null) { this.pxClient.close(); } } diff --git a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java index 4ce765cc..99623f92 100644 --- a/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java +++ b/src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java @@ -62,11 +62,7 @@ public DefaultReverseProxy(PXConfiguration pxConfiguration, IPProvider ipProvide PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(pxConfiguration.getMaxConnections()); cm.setDefaultMaxPerRoute(pxConfiguration.getMaxConnectionsPerRoute()); - if (pxConfiguration.getHttpClient() == null) { - this.proxyClient = new PXApacheHttpClient(pxConfiguration); - } else { - this.proxyClient = pxConfiguration.getHttpClient(); - } + this.proxyClient = pxConfiguration.getIPXHttpClientInstance(); } public boolean reversePxClient(HttpServletRequest req, HttpServletResponse res) throws URISyntaxException, IOException { diff --git a/src/main/java/com/perimeterx/http/PXHttpClient.java b/src/main/java/com/perimeterx/http/PXHttpClient.java index 0f4ce621..12f4799f 100644 --- a/src/main/java/com/perimeterx/http/PXHttpClient.java +++ b/src/main/java/com/perimeterx/http/PXHttpClient.java @@ -19,7 +19,6 @@ import org.apache.http.HttpStatus; import org.apache.http.conn.ConnectTimeoutException; -import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.net.SocketTimeoutException; @@ -43,11 +42,7 @@ public class PXHttpClient implements PXClient, Closeable { public PXHttpClient(PXConfiguration pxConfiguration) throws PXException { try { this.pxConfiguration = pxConfiguration; - if (pxConfiguration.getHttpClient() == null) { - this.client = new PXApacheHttpClient(pxConfiguration); - } else { - this.client = pxConfiguration.getHttpClient(); - } + this.client = pxConfiguration.getIPXHttpClientInstance(); } catch (Exception e) { throw new PXException(e); } diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index f4620cb1..6ed43dc6 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -9,21 +9,23 @@ import com.perimeterx.api.additionalContext.credentialsIntelligence.loginresponse.LoginResponseValidator; import com.perimeterx.api.blockhandler.BlockHandler; import com.perimeterx.api.blockhandler.DefaultBlockHandler; +import com.perimeterx.api.providers.CombinedIPProvider; import com.perimeterx.api.providers.CustomParametersProvider; import com.perimeterx.api.providers.DefaultCustomParametersProvider; +import com.perimeterx.api.proxy.DefaultReverseProxy; import com.perimeterx.api.proxy.ReverseProxy; import com.perimeterx.http.IPXHttpClient; +import com.perimeterx.http.PXApacheHttpClient; import com.perimeterx.http.PXClient; +import com.perimeterx.http.PXHttpClient; import com.perimeterx.models.configuration.credentialsIntelligenceconfig.CILoginMap; +import com.perimeterx.models.exceptions.PXException; import com.perimeterx.models.risk.CustomParameters; import com.perimeterx.utils.Constants; import com.perimeterx.utils.FilesUtils; import com.perimeterx.utils.LoggerSeverity; import com.perimeterx.utils.PXLogger; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; @@ -276,13 +278,30 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { private String customCookieHeader = "x-px-cookies"; @Builder.Default + @Getter(AccessLevel.PRIVATE) private IPXHttpClient httpClient = null; @Builder.Default + @Getter(AccessLevel.PRIVATE) private PXClient pxClient = null; @Builder.Default + @Getter(AccessLevel.PRIVATE) private ReverseProxy pxReverseProxy = null; + + @Setter(AccessLevel.NONE) + @Getter(AccessLevel.NONE) + private volatile PXClient pxClientInstance = null; + + @Setter(AccessLevel.NONE) + @Getter(AccessLevel.NONE) + private volatile IPXHttpClient ipxHttpClientInstance = null; + + @Setter(AccessLevel.NONE) + @Getter(AccessLevel.NONE) + private volatile ReverseProxy reverseProxyInstance = null; + + /** * @return Configuration Object clone without cookieKey and authToken **/ @@ -294,6 +313,52 @@ public void disableModule() { this.moduleEnabled = false; } + public PXClient getPxClientInstance() throws PXException { + if (pxClientInstance == null) { + synchronized (this) { + if (pxClientInstance == null) { + if (this.getPxClient() == null) { + pxClientInstance = new PXHttpClient(this); + } else { + pxClientInstance = this.getPxClient(); + } + } + } + } + return pxClientInstance; + } + + public IPXHttpClient getIPXHttpClientInstance() { + if (ipxHttpClientInstance == null) { + synchronized (this) { + if (ipxHttpClientInstance == null) { + if (this.getHttpClient() == null) { + ipxHttpClientInstance = new PXApacheHttpClient(this); + } else { + ipxHttpClientInstance = this.getHttpClient(); + } + } + } + } + return ipxHttpClientInstance; + } + + public ReverseProxy getReverseProxyInstance() { + if (reverseProxyInstance == null) { + synchronized (this) { + if (reverseProxyInstance == null) { + if (this.getPxReverseProxy() == null) { + reverseProxyInstance = new DefaultReverseProxy(this, new CombinedIPProvider(this)); + } else { + reverseProxyInstance = this.getPxReverseProxy(); + } + } + } + } + return reverseProxyInstance; + } + + public void update(PXDynamicConfiguration pxDynamicConfiguration) { logger.debug("Updating PXConfiguration file"); this.appId = pxDynamicConfiguration.getAppId(); From d4b256d4ff6aad80a319a56e5ba721f66f7759b3 Mon Sep 17 00:00:00 2001 From: guyeisenbach Date: Wed, 6 Sep 2023 19:11:36 +0300 Subject: [PATCH 12/12] Adding transient to unserializable fields in configuration --- .../perimeterx/models/configuration/PXConfiguration.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 6ed43dc6..181b6b93 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -291,15 +291,15 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Setter(AccessLevel.NONE) @Getter(AccessLevel.NONE) - private volatile PXClient pxClientInstance = null; + private transient volatile PXClient pxClientInstance = null; @Setter(AccessLevel.NONE) @Getter(AccessLevel.NONE) - private volatile IPXHttpClient ipxHttpClientInstance = null; + private transient volatile IPXHttpClient ipxHttpClientInstance = null; @Setter(AccessLevel.NONE) @Getter(AccessLevel.NONE) - private volatile ReverseProxy reverseProxyInstance = null; + private transient volatile ReverseProxy reverseProxyInstance = null; /**