Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable http client #303

Merged
merged 13 commits into from
Sep 6, 2023
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Change Log
## [vX.X.X](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.7.1...HEAD) (YYYY-MM-DD)
- configurable IPXHttpClient
- configurable PXClient
guyeisenbach marked this conversation as resolved.
Show resolved Hide resolved
- PXHD doesn't set cookie after risk_api

## [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
Expand Down
18 changes: 13 additions & 5 deletions src/main/java/com/perimeterx/api/PerimeterX.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -53,7 +54,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;
Expand Down Expand Up @@ -89,15 +89,15 @@ public class PerimeterX implements Closeable {
private HostnameProvider hostnameProvider;
private VerificationHandler verificationHandler;
private ReverseProxy reverseProxy;
private PXHttpClient pxClient = null;
private PXClient pxClient = null;
guyeisenbach marked this conversation as resolved.
Show resolved Hide resolved

private void init(PXConfiguration configuration) throws PXException {
logger.debug(PXLogger.LogReason.DEBUG_INITIALIZING_MODULE);
configuration.mergeConfigurations();
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()) {
Expand All @@ -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) {
this.reverseProxy = configuration.getReverseProxyInstance();
}

private void setPxClient(PXConfiguration configuration) throws PXException {
this.pxClient = configuration.getPxClientInstance();
}

private void setVerificationHandler() {
Expand Down Expand Up @@ -339,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();
}
}
Expand Down
20 changes: 9 additions & 11 deletions src/main/java/com/perimeterx/api/proxy/DefaultReverseProxy.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -62,9 +62,7 @@ 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();
this.proxyClient = pxConfiguration.getIPXHttpClientInstance();
}

public boolean reversePxClient(HttpServletRequest req, HttpServletResponse res) throws URISyntaxException, IOException {
Expand All @@ -82,7 +80,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;
}
Expand Down Expand Up @@ -112,7 +110,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;
Expand All @@ -135,7 +133,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;

Expand All @@ -149,7 +147,7 @@ public void setPredefinedResponseHelper(PredefinedResponseHelper predefinedRespo
this.predefinedResponseHelper = predefinedResponseHelper;
}

public void setProxyClient(CloseableHttpClient proxyClient) {
public void setProxyClient(IPXHttpClient proxyClient) {
this.proxyClient = proxyClient;
}

Expand Down
107 changes: 49 additions & 58 deletions src/main/java/com/perimeterx/api/proxy/RemoteServer.java
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
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 com.perimeterx.http.PXOutgoingRequestImpl.PXOutgoingRequestImplBuilder;
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.
Expand All @@ -35,7 +36,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;
Expand Down Expand Up @@ -65,7 +66,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;
Expand All @@ -79,36 +80,42 @@ 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;
// Copy the body if content-length exists or transfer encoding
if (req.getHeader(HttpHeaders.CONTENT_LENGTH) != null || req.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
guyeisenbach marked this conversation as resolved.
Show resolved Hide resolved
proxyRequest = newProxyRequestWithEntity(method, proxyRequestUri, req);
} else {
// case not, BasicHttpRequest
proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
PXOutgoingRequestImplBuilder requestBuilder = PXOutgoingRequestImpl.builder();
// Copy the body if content-length exists
if (getContentLength(req) != -1) {
requestBuilder.body(
new PXRequestBody(
req.getInputStream(),
getContentLength(req)
)
);
}

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) {
Expand Down Expand Up @@ -144,20 +151,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);
}
}
Expand All @@ -167,7 +173,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;
Expand Down Expand Up @@ -251,43 +257,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, PXOutgoingRequestImplBuilder requestBuilder) {
// Get an Enumeration of all of the header names sent by the client
@SuppressWarnings("unchecked")
Enumeration<String> 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(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, 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, PXOutgoingRequestImplBuilder proxyRequest,
String headerName) {
//Instead the content-length is effectively set via InputStreamEntity
if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) {
Expand All @@ -302,7 +307,6 @@ protected void copyRequestHeader(HttpServletRequest servletRequest, HttpRequest
return;
}

@SuppressWarnings("unchecked")
Enumeration<String> headers = servletRequest.getHeaders(headerName);
while (headers.hasMoreElements()) {//sometimes more than one value
String headerValue = headers.nextElement();
Expand All @@ -313,27 +317,14 @@ 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;
return request.getContentLength();
}

protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) {
logger.debug("Rewiring url from request");
StringBuilder uri = new StringBuilder(this.maxUrlLength);
Expand Down Expand Up @@ -427,7 +418,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);
}
}
Loading
Loading