I figured I should share some code too. Most of this isn't going to be relevant, but I want to be absolutely sure. Here's where I start the proxy:
private synchronized static HttpProxyServer generateProxyServer()
{
// Load port configuration for proxy or find a new one
int port = Config.getInteger("proxyPort");
if(port == 0) port = SystemUtils.findAvailablePort();
// Record current thread ID so filters can use it to log to the current test context
final long associatedThreadId = Thread.currentThread().getId();
// Create and start the proxy server object
HttpProxyServer proxyServer = null;
String proxyInfo = String.format("Starting proxy server on port [%s]... ", port);
try
{
HttpProxyServerBootstrap proxyServerBootstrap = DefaultHttpProxyServer.bootstrap();
proxyServerBootstrap = proxyServerBootstrap.withPort(port);
proxyServerBootstrap = proxyServerBootstrap.withFiltersSource(new HttpFiltersSourceAdapter()
{
public HttpFilters filterRequest(HttpRequest originalRequest, ChannelHandlerContext ctx)
{
return new ProxyFilters(associatedThreadId);
}
});
// Allow external connections to the proxy port if using an external browser via Selenium Grid
if(Config.getBoolean("useGrid"))
{
proxyServerBootstrap = proxyServerBootstrap.withAllowLocalOnly(false);
}
proxyServer = proxyServerBootstrap.start();
}
catch(Exception e)
{
CombinedLogger.error(proxyInfo + "FAILURE (HALTING)!", e);
}
CombinedLogger.info(proxyInfo + "success");
return proxyServer;
}
Here's my code for attaching the proxy to a WebDriver instance:
private static void configureGenericCapabilities(DesiredCapabilities capabilities)
{
if(Config.getBoolean("useProxy"))
{
// Start the proxy server
HttpProxyServer proxyServer = generateProxyServer();
Context.put("proxyServer", proxyServer);
// Get the proxy server address. Note: The NODE_NAME variable (used by Jenkins) overrides any value
// which was set for proxyServerAddress. This allows us to redirect browsers back to the Jenkins
// machines without knowing beforehand what the machine label is. If you still want to manually configure
// the proxy server address for Jenkins jobs you'll also need to set
// proxyServerAddressOverridesJenkinsNodeName to true.
String proxyServerAddress = Config.getString("proxyServerAddress");
if(!Config.getBoolean("proxyServerAddressOverridesJenkinsNodeName"))
{
String jenkinsNodeName = Config.getString("NODE_NAME");
if(jenkinsNodeName != null && !jenkinsNodeName.isEmpty())
{
String infoString = String.format("Jenkins NODE_NAME variable has been externally set to [%s]. " +
"Using this value to assign proxy server address to current " +
"node. Please set proxyServerAddressOverridesJenkinsNodeName " +
"to true if you still wish to manually define the proxy " +
"server address.", jenkinsNodeName);
CombinedLogger.info(infoString);
proxyServerAddress = jenkinsNodeName;
}
}
// Add proxy connection information to the WebDriver capabilities object
Proxy proxy = new Proxy();
proxy.setProxyType(Proxy.ProxyType.MANUAL);
proxy.setHttpProxy(String.format("%s:%s", proxyServerAddress, proxyServer.getListenAddress().getPort()));
proxy.setSslProxy(String.format("%s:%s", proxyServerAddress, proxyServer.getListenAddress().getPort()));
capabilities.setCapability(CapabilityType.PROXY, proxy);
}
capabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
capabilities.setCapability(CapabilityType.SUPPORTS_JAVASCRIPT, true);
capabilities.setCapability(CapabilityType.SUPPORTS_WEB_STORAGE, false);
}
And here's my code which handles the actual filtering:
public class ProxyFilters implements HttpFilters
{
private long associatedThreadId;
public ProxyFilters(long associatedThreadId)
{
this.associatedThreadId = associatedThreadId;
}
/**
* Performs request filtering for HTTP calls between the client and the proxy
* @param request: The request object being filtered
* @return null: Signals that we're not mocking any responses
*/
public HttpResponse requestPre(HttpObject request)
{
if(request == null)
{
String warningMessage = String.format("%s detected a null request!");
CombinedLogger.warn(warningMessage, associatedThreadId);
return null;
}
if(!(request instanceof HttpRequest))
{
String warningMessage = String.format("Unsupported HttpObject type detected! %s does not have the " +
"capability to process [%s] objects!",
this.getClass().getSimpleName(), request.getClass().getName());
if(Config.getBoolean("verboseProxy", associatedThreadId)) CombinedLogger.warn(warningMessage, associatedThreadId);
}
else
{
handleRequestURIRedirection((HttpRequest)request);
handleRequestHeaderRedirection((HttpRequest)request);
if(Config.getBoolean("reportProxyTransactions", associatedThreadId)) handleReporting(request);
}
return null;
}
/**
* Performs request filtering for HTTP calls between the proxy and the server
* @param request: The request object being filtered
* @return null: Signals that we're not mocking any responses
*/
public HttpResponse requestPost(HttpObject request)
{
// no-op since request was already handled in requestPre;
return null;
}
/**
* Performs response filtering for HTTP calls between the server and the proxy
* @param response: The request object being filtered
* @return response: The unmodified response object
*/
public HttpObject responsePre(HttpObject response)
{
if(response == null)
{
String warningMessage = String.format("%s detected a null response!");
CombinedLogger.warn(warningMessage, associatedThreadId);
return response;
}
if(Config.getBoolean("reportProxyTransactions", associatedThreadId)) handleReporting(response);
return response;
}
/**
* Performs response filtering for HTTP calls between the proxy and the client
* @param response: The request object being filtered
* @return response: The unmodified response object
*/
public HttpObject responsePost(HttpObject response)
{
// no-op since response was already handled in responsePre;
return response;
}
/**
* Performs request filtering by checking the current test context for configured redirection entries
* @param request: The request object to perform redirection on
*/
private void handleRequestURIRedirection(HttpRequest request)
{
String currentUri = request.getUri();
Map<String, String> redirections = (Map<String, String>)Context.get("redirections", associatedThreadId);
if(redirections == null) return;
for(Entry<String, String> redirection : redirections.entrySet())
{
// Get the redirection info
String redirectFrom = redirection.getKey();
String redirectTo = redirection.getValue();
// Perform redirection for URI
if(currentUri.contains(redirectFrom))
{
String newUri = currentUri.replace(redirectFrom, redirectTo);
// If the redirected URL already contains a port (IE: 127.0.0.1:8443) then we want to strip out any
// remaining port in the rest of the URI since it will cause a conflict (IE: 127.0.0.1:8443:80).
Pattern twoPortPattern = Pattern.compile("(:\\d+):\\d+");
Matcher twoPortMatcher = twoPortPattern.matcher(newUri);
while(twoPortMatcher.find())
{
newUri = newUri.replace(twoPortMatcher.group(), twoPortMatcher.group(1));
}
// Update the URI
CombinedLogger.info(String.format("Changing request URI from [%s] to [%s]", currentUri, newUri),
associatedThreadId);
request.setUri(newUri);
}
}
}
private void handleRequestHeaderRedirection(HttpRequest request)
{
Map<String, String> redirections = (Map<String, String>)Context.get("headerRedirections", associatedThreadId);
if(redirections == null) return;
for(Entry<String, String> redirection : redirections.entrySet())
{
// Get the redirection info
String redirectFrom = redirection.getKey();
String redirectTo = redirection.getValue();
HttpHeaders requestHeaders = request.headers();
Iterator<Map.Entry<String, String>> requestHeaderIterator = requestHeaders.iterator();
while(requestHeaderIterator.hasNext())
{
// Check every header
Map.Entry<String, String> requestHeader = requestHeaderIterator.next();
String requestHeaderKey = requestHeader.getKey();
String requestHeaderValue = requestHeader.getValue();
if(requestHeaderValue.contains(redirectFrom))
{
// Update any redirected URLs in the header
String newRequestHeaderValue = requestHeaderValue.replace(redirectFrom, redirectTo);
// If the redirected URL in the header already contains a port (IE: 127.0.0.1:8443) then we want to
// strip out any remaining ports from the URIs in the rest of the header since they will cause
// conflicts (IE: 127.0.0.1:8443:80).
Pattern twoPortPattern = Pattern.compile("(:\\d+):\\d+");
Matcher twoPortMatcher = twoPortPattern.matcher(newRequestHeaderValue);
while(twoPortMatcher.find())
{
newRequestHeaderValue = newRequestHeaderValue.replace(twoPortMatcher.group(),
twoPortMatcher.group(1));
}
// Replace the old header with the modified one
if(Config.getBoolean("verboseProxy", associatedThreadId))
{
CombinedLogger.info(String.format("Changing header [%s] from [%s] to [%s]", requestHeaderKey,
requestHeaderValue, newRequestHeaderValue),
associatedThreadId);
}
requestHeaders.remove(requestHeaderKey);
requestHeaders.add(requestHeaderKey, newRequestHeaderValue);
}
}
}
}
private void handleReporting(HttpObject httpObject)
{
String transactionTitle;
StringBuilder transactionDetails = new StringBuilder();
HttpHeaders headers;
// Record request details
if(httpObject instanceof HttpRequest)
{
transactionTitle = String.format("Request at %s", new Date().toString());
transactionDetails.append(String.format("URI = %s</br>", ((HttpRequest)httpObject).getUri()));
headers = ((HttpRequest)httpObject).headers();
}
// Record response details
else if(httpObject instanceof HttpResponse)
{
transactionTitle = String.format("Response at %s", new Date().toString());
transactionDetails.append(String.format("Status = %s</br>", ((HttpResponse)httpObject).getStatus()));
headers = ((HttpResponse)httpObject).headers();
}
// Can't record transaction details
else
{
String warningMessage = String.format("Unsupported HttpObject type detected! %s does not have the " +
"capability to record [%s] object transactions!",
this.getClass().getSimpleName(), httpObject.getClass().getName());
if(Config.getBoolean("verboseProxy", associatedThreadId)) CombinedLogger.warn(warningMessage, associatedThreadId);
return;
}
// Record header details
if(headers != null && !headers.isEmpty())
{
transactionDetails.append("Headers:</br><ul style=\"list-style-type:bullet\">");
Iterator<Map.Entry<String, String>> headerIterator = headers.iterator();
while(headerIterator.hasNext())
{
// Record every header
Map.Entry<String, String> requestHeader = headerIterator.next();
String requestHeaderKey = requestHeader.getKey();
String requestHeaderValue = requestHeader.getValue();
transactionDetails.append(String.format("<li>%s - %s/<li>", requestHeaderKey, requestHeaderValue));
}
}
// Save the transaction info to the test method report
Report testMethodReport = (Report)Context.get("testMethodReport", associatedThreadId);
if(testMethodReport != null) testMethodReport.setField(transactionTitle, transactionDetails.toString());
else
{
// Test method report is null so we'll record to the test set report instead.
Report testSetReport = (Report)Context.get("testSetReport", associatedThreadId);
testSetReport.setField(transactionTitle, transactionDetails.toString());
}
if(Config.getBoolean("verboseProxy", associatedThreadId)) CombinedLogger.info(String.format("Logged transaction: [%s: %s]",
transactionTitle,
transactionDetails.toString()),
associatedThreadId);
}
}