package net.lightbody.bmp.proxy; import net.lightbody.bmp.core.har.Har; import net.lightbody.bmp.core.har.HarEntry; import net.lightbody.bmp.core.har.HarLog; import net.lightbody.bmp.core.har.HarNameVersion; import net.lightbody.bmp.core.har.HarPage; import net.lightbody.bmp.core.util.ThreadUtils; import net.lightbody.bmp.proxy.http.BrowserMobHttpClient; import net.lightbody.bmp.proxy.http.RequestInterceptor; import net.lightbody.bmp.proxy.http.ResponseInterceptor; import net.lightbody.bmp.proxy.jetty.http.HttpContext; import net.lightbody.bmp.proxy.jetty.http.HttpListener; import net.lightbody.bmp.proxy.jetty.http.SocketListener; import net.lightbody.bmp.proxy.jetty.jetty.Server; import net.lightbody.bmp.proxy.jetty.util.InetAddrPort; import net.lightbody.bmp.proxy.util.Log; import org.java_bandwidthlimiter.BandwidthLimiter; import org.java_bandwidthlimiter.StreamManager; import java.util.Date; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class ProxyServer { public static int PROXY_TIMEOUT = 240000; //4 minutes, by default will be set during ProxyServer.start() private static final HarNameVersion CREATOR = new HarNameVersion("BrowserMob Proxy - for Wilma", "2.2"); private static final Log LOG = new Log(); private static Boolean responseVolatile = new Boolean(false); private Server server; private int port = -1; private static Boolean shouldKeepSslConnectionAlive = new Boolean(false); //set it to true if such (e.g. .net) clients we have private BrowserMobHttpClient client; private StreamManager streamManager; private HarPage currentPage; private BrowserMobProxyHandler handler; private int pageCount = 1; private final AtomicInteger requestCounter = new AtomicInteger(0); public ProxyServer() { } public ProxyServer(final int port) { this.port = port; } public void start(final int requestTimeOut) throws Exception { if (port == -1) { throw new IllegalStateException("Must set port before starting"); } PROXY_TIMEOUT = requestTimeOut; //create a stream manager that will be capped to 100 Megabits //remember that by default it is disabled! streamManager = new StreamManager(100 * BandwidthLimiter.OneMbps); server = new Server(); HttpListener listener = new SocketListener(new InetAddrPort(getPort())); server.addListener(listener); HttpContext context = new HttpContext(); context.setContextPath("/"); server.addContext(context); handler = new BrowserMobProxyHandler(); handler.setJettyServer(server); handler.setShutdownLock(new Object()); client = new BrowserMobHttpClient(streamManager, requestCounter, requestTimeOut); client.prepareForBrowser(); handler.setHttpClient(client); context.addHandler(handler); server.start(); setPort(listener.getPort()); } public void cleanup() { handler.cleanup(); } public void stop() throws Exception { cleanup(); client.shutdown(); server.stop(); } public int getPort() { return port; } public void setPort(final int port) { this.port = port; } public Har getHar() { // Wait up to 5 seconds for all active requests to cease before returning the HAR. // This helps with race conditions but won't cause deadlocks should a request hang // or error out in an unexpected way (which of course would be a bug!) boolean success = ThreadUtils.waitFor(new ThreadUtils.WaitCondition() { @Override public boolean checkCondition(final long elapsedTimeInMs) { return requestCounter.get() == 0; } }, TimeUnit.SECONDS, 5); if (!success) { LOG.warn("Waited 5 seconds for requests to cease before returning HAR; giving up!"); } return client.getHar(); } public Har newHar(final String initialPageRef) { pageCount = 1; Har oldHar = getHar(); Har har = new Har(new HarLog(CREATOR)); client.setHar(har); newPage(initialPageRef); return oldHar; } public void newPage(String pageRef) { if (pageRef == null) { pageRef = "Page " + pageCount; } client.setHarPageRef(pageRef); currentPage = new HarPage(pageRef); client.getHar().getLog().addPage(currentPage); pageCount++; } public void endPage() { if (currentPage == null) { return; } currentPage.getPageTimings().setOnLoad(new Date().getTime() - currentPage.getStartedDateTime().getTime()); client.setHarPageRef(null); currentPage = null; } public void setRetryCount(final int count) { client.setRetryCount(count); } public void remapHost(final String source, final String target) { client.remapHost(source, target); } public void addRequestInterceptor(final RequestInterceptor interceptor) { client.addRequestInterceptor(interceptor); } public void addResponseInterceptor(final ResponseInterceptor interceptor) { client.addResponseInterceptor(interceptor); } public StreamManager getStreamManager() { return streamManager; } public void setRequestTimeout(final int requestTimeout) { client.setRequestTimeout(requestTimeout); } public void setSocketOperationTimeout(final int readTimeout) { client.setSocketOperationTimeout(readTimeout); } public void setConnectionTimeout(final int connectionTimeout) { client.setConnectionTimeout(connectionTimeout); } public void autoBasicAuthorization(final String domain, final String username, final String password) { client.autoBasicAuthorization(domain, username, password); } public void rewriteUrl(final String match, final String replace) { client.rewriteUrl(match, replace); } public void blacklistRequests(final String pattern, final int responseCode) { client.blacklistRequests(pattern, responseCode); } public void whitelistRequests(final String[] patterns, final int responseCode) { client.whitelistRequests(patterns, responseCode); } public void addHeader(final String name, final String value) { client.addHeader(name, value); } public void setCaptureHeaders(final boolean captureHeaders) { client.setCaptureHeaders(captureHeaders); } public void setCaptureContent(final boolean captureContent) { client.setCaptureContent(captureContent); } public void setCaptureBinaryContent(final boolean captureBinaryContent) { client.setCaptureBinaryContent(captureBinaryContent); } public void clearDNSCache() { client.clearDNSCache(); } public void setDNSCacheTimeout(final int timeout) { client.setDNSCacheTimeout(timeout); } public void waitForNetworkTrafficToStop(final long quietPeriodInMs, final long timeoutInMs) { boolean result = ThreadUtils.waitFor(new ThreadUtils.WaitCondition() { @Override public boolean checkCondition(final long elapsedTimeInMs) { Date lastCompleted = null; Har har = client.getHar(); if (har == null || har.getLog() == null) { return true; } for (HarEntry entry : har.getLog().getEntries()) { // if there is an active request, just stop looking if (entry.getResponse().getStatus() < 0) { return false; } Date end = new Date(entry.getStartedDateTime().getTime() + entry.getTime()); if (lastCompleted == null) { lastCompleted = end; } else if (end.after(lastCompleted)) { lastCompleted = end; } } return lastCompleted != null && System.currentTimeMillis() - lastCompleted.getTime() >= quietPeriodInMs; } }, TimeUnit.MILLISECONDS, timeoutInMs); if (!result) { throw new RuntimeException("Timed out after " + timeoutInMs + " ms while waiting for network traffic to stop"); } } public void setOptions(final Map<String, String> options) { if (options.containsKey("httpProxy")) { client.setHttpProxy(options.get("httpProxy")); } } public static Boolean getResponseVolatile() { return responseVolatile; } public static void setResponseVolatile(Boolean responseVolatile) { ProxyServer.responseVolatile = responseVolatile; } public static Boolean getShouldKeepSslConnectionAlive() { return shouldKeepSslConnectionAlive; } public static void setShouldKeepSslConnectionAlive(Boolean shouldKeepSslConnectionAlive) { ProxyServer.shouldKeepSslConnectionAlive = shouldKeepSslConnectionAlive; } }