Skip to content

Commit 34fd435

Browse files
committed
Merge pull request #282 from jekh/capture-redirect-url-in-har
Capture redirectURL in HAR
2 parents 27fdaf1 + 1dcbe1c commit 34fd435

File tree

3 files changed

+133
-0
lines changed

3 files changed

+133
-0
lines changed

browsermob-core-littleproxy/src/main/java/net/lightbody/bmp/filters/HarCaptureFilter.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ protected void captureResponse(HttpResponse httpResponse) {
479479
if (dataToCapture.contains(CaptureType.RESPONSE_HEADERS)) {
480480
captureResponseHeaders(httpResponse);
481481
}
482+
483+
if (BrowserMobHttpUtil.isRedirect(httpResponse)) {
484+
captureRedirectUrl(httpResponse);
485+
}
482486
}
483487

484488
protected void captureResponseCookies(HttpResponse httpResponse) {
@@ -524,6 +528,13 @@ protected void captureResponseHeaders(HttpResponse httpResponse) {
524528
}
525529
}
526530

531+
protected void captureRedirectUrl(HttpResponse httpResponse) {
532+
String locationHeaderValue = HttpHeaders.getHeader(httpResponse, HttpHeaders.Names.LOCATION);
533+
if (locationHeaderValue != null) {
534+
harEntry.getResponse().setRedirectURL(locationHeaderValue);
535+
}
536+
}
537+
527538
/**
528539
* Adds the size of this httpContent to the requestBodySize.
529540
*

browsermob-core-littleproxy/src/test/groovy/net/lightbody/bmp/proxy/NewHarTest.groovy

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import net.lightbody.bmp.proxy.dns.AdvancedHostResolver
1515
import net.lightbody.bmp.proxy.test.util.MockServerTest
1616
import net.lightbody.bmp.proxy.test.util.ProxyServerTest
1717
import net.lightbody.bmp.proxy.util.IOUtils
18+
import org.apache.http.client.config.RequestConfig
1819
import org.apache.http.client.methods.CloseableHttpResponse
1920
import org.apache.http.client.methods.HttpGet
2021
import org.junit.After
@@ -857,5 +858,105 @@ class NewHarTest extends MockServerTest {
857858
assertEquals("Expected receive time to not be populated", 0L, harTimings.getReceive(TimeUnit.NANOSECONDS))
858859
}
859860

861+
@Test
862+
void testRedirectUrlCapturedForRedirects() {
863+
mockServer.when(request()
864+
.withMethod("GET")
865+
.withPath("/test300"),
866+
Times.once())
867+
.respond(response()
868+
.withStatusCode(300)
869+
.withHeader("Location", "/redirected-location"))
870+
871+
mockServer.when(request()
872+
.withMethod("GET")
873+
.withPath("/test301"),
874+
Times.once())
875+
.respond(response()
876+
.withStatusCode(301)
877+
.withHeader("Location", "/redirected-location"))
878+
879+
mockServer.when(request()
880+
.withMethod("GET")
881+
.withPath("/test302"),
882+
Times.once())
883+
.respond(response()
884+
.withStatusCode(302)
885+
.withHeader("Location", "/redirected-location"))
886+
887+
mockServer.when(request()
888+
.withMethod("GET")
889+
.withPath("/test303"),
890+
Times.once())
891+
.respond(response()
892+
.withStatusCode(303)
893+
.withHeader("Location", "/redirected-location"))
894+
895+
mockServer.when(request()
896+
.withMethod("GET")
897+
.withPath("/test307"),
898+
Times.once())
899+
.respond(response()
900+
.withStatusCode(307)
901+
.withHeader("Location", "/redirected-location"))
902+
903+
mockServer.when(request()
904+
.withMethod("GET")
905+
.withPath("/test301-no-location-header"),
906+
Times.once())
907+
.respond(response()
908+
.withStatusCode(301))
909+
910+
proxy = new BrowserMobProxyServer();
911+
proxy.start()
912+
913+
proxy.newHar()
914+
915+
def verifyRedirect = { String requestUrl, expectedStatusCode, expectedLocationValue ->
916+
ProxyServerTest.getNewHttpClient(proxy.port).withCloseable {
917+
// for some reason, even when the HTTP client is built with .disableRedirectHandling(), it still tries to follow
918+
// the 301. so explicitly disable following redirects at the request level.
919+
def request = new HttpGet(requestUrl)
920+
request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build())
921+
922+
CloseableHttpResponse response = it.execute(request)
923+
assertEquals("HTTP response code did not match expected response code", expectedStatusCode, response.getStatusLine().getStatusCode())
924+
};
925+
926+
Thread.sleep(500)
927+
Har har = proxy.getHar()
928+
929+
assertThat("Expected to find entries in the HAR", har.getLog().getEntries(), not(empty()))
930+
931+
// make sure request data is still captured despite the failure
932+
String capturedUrl = har.log.entries[0].request.url
933+
assertEquals("URL captured in HAR did not match request URL", requestUrl, capturedUrl)
934+
935+
HarResponse harResponse = har.log.entries[0].response
936+
assertNotNull("No HAR response found", harResponse)
937+
938+
assertEquals("Expected redirect location to be populated in redirectURL field", expectedLocationValue, harResponse.redirectURL);
939+
}
940+
941+
verifyRedirect("http://localhost:${mockServerPort}/test300", 300, "/redirected-location")
942+
943+
// clear the HAR between every request, to make the verification step easier
944+
proxy.newHar()
945+
verifyRedirect("http://localhost:${mockServerPort}/test301", 301, "/redirected-location")
946+
947+
proxy.newHar()
948+
verifyRedirect("http://localhost:${mockServerPort}/test302", 302, "/redirected-location")
949+
950+
proxy.newHar()
951+
verifyRedirect("http://localhost:${mockServerPort}/test303", 303, "/redirected-location")
952+
953+
proxy.newHar()
954+
verifyRedirect("http://localhost:${mockServerPort}/test307", 307, "/redirected-location")
955+
956+
proxy.newHar()
957+
// redirectURL should always be populated or an empty string, never null
958+
verifyRedirect("http://localhost:${mockServerPort}/test301-no-location-header", 301, "")
959+
}
960+
860961
//TODO: Add Request Capture Type tests
861962
}

browsermob-core/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.netty.buffer.ByteBuf;
55
import io.netty.handler.codec.http.HttpHeaders;
66
import io.netty.handler.codec.http.HttpRequest;
7+
import io.netty.handler.codec.http.HttpResponse;
78
import net.lightbody.bmp.exception.DecompressionException;
89
import org.apache.http.entity.ContentType;
910
import org.slf4j.Logger;
@@ -323,6 +324,26 @@ public static String getHostAndPortFromUri(String uriString) throws URISyntaxExc
323324
}
324325
}
325326

327+
/**
328+
* Returns true if the specified response is an HTTP redirect response, i.e. a 300, 301, 302, 303, or 307.
329+
*
330+
* @param httpResponse HTTP response
331+
* @return true if the response is a redirect, otherwise false
332+
*/
333+
public static boolean isRedirect(HttpResponse httpResponse) {
334+
switch (httpResponse.getStatus().code()) {
335+
case 300:
336+
case 301:
337+
case 302:
338+
case 303:
339+
case 307:
340+
return true;
341+
342+
default:
343+
return false;
344+
}
345+
}
346+
326347
/**
327348
* Retrieves the host and, optionally, the port from the specified request's Host header.
328349
*

0 commit comments

Comments
 (0)