Skip to content

Commit 5637a00

Browse files
committedJul 25, 2023
Added support for recording via an existing proxy configuration (forward or reverse) and not having to set the target URL.
1 parent 5f0677f commit 5637a00

File tree

11 files changed

+122
-114
lines changed

11 files changed

+122
-114
lines changed
 

‎src/main/java/com/github/tomakehurst/wiremock/client/WireMock.java

+9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.github.tomakehurst.wiremock.http.Request;
3434
import com.github.tomakehurst.wiremock.http.RequestMethod;
3535
import com.github.tomakehurst.wiremock.matching.*;
36+
import com.github.tomakehurst.wiremock.recording.RecordSpec;
3637
import com.github.tomakehurst.wiremock.recording.RecordSpecBuilder;
3738
import com.github.tomakehurst.wiremock.recording.RecordingStatusResult;
3839
import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult;
@@ -966,6 +967,10 @@ public static void startRecording(String targetBaseUrl) {
966967
defaultInstance.get().startStubRecording(targetBaseUrl);
967968
}
968969

970+
public static void startRecording() {
971+
defaultInstance.get().startStubRecording();
972+
}
973+
969974
public static void startRecording(RecordSpecBuilder spec) {
970975
defaultInstance.get().startStubRecording(spec);
971976
}
@@ -974,6 +979,10 @@ public void startStubRecording(String targetBaseUrl) {
974979
admin.startRecording(targetBaseUrl);
975980
}
976981

982+
public void startStubRecording() {
983+
admin.startRecording(RecordSpec.DEFAULTS);
984+
}
985+
977986
public void startStubRecording(RecordSpecBuilder spec) {
978987
admin.startRecording(spec.build());
979988
}

‎src/main/java/com/github/tomakehurst/wiremock/common/HttpClientUtils.java

+21-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.github.tomakehurst.wiremock.common;
1717

18+
import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked;
1819
import static java.nio.charset.StandardCharsets.UTF_8;
1920

2021
import java.io.IOException;
@@ -44,14 +45,27 @@ public static String getEntityAsStringAndCloseStream(ClassicHttpResponse httpRes
4445

4546
public static byte[] getEntityAsByteArrayAndCloseStream(ClassicHttpResponse httpResponse) {
4647
HttpEntity entity = httpResponse.getEntity();
47-
if (entity != null) {
48-
try {
49-
byte[] content = EntityUtils.toByteArray(entity);
50-
entity.getContent().close();
51-
return content;
52-
} catch (IOException ioe) {
53-
throw new RuntimeException(ioe);
48+
try {
49+
if (entity != null) {
50+
return EntityUtils.toByteArray(entity);
51+
}
52+
} catch (IOException ioe) {
53+
return throwUnchecked(ioe, byte[].class);
54+
} finally {
55+
Exceptions.uncheck(httpResponse::close);
56+
}
57+
58+
return null;
59+
}
60+
61+
public static byte[] getEntityAsByteArray(ClassicHttpResponse httpResponse) {
62+
HttpEntity entity = httpResponse.getEntity();
63+
try {
64+
if (entity != null) {
65+
return EntityUtils.toByteArray(entity);
5466
}
67+
} catch (IOException ioe) {
68+
return throwUnchecked(ioe, byte[].class);
5569
}
5670

5771
return null;

‎src/main/java/com/github/tomakehurst/wiremock/http/ProxyResponseRenderer.java

+19-15
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package com.github.tomakehurst.wiremock.http;
1717

18-
import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArrayAndCloseStream;
18+
import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArray;
1919
import static com.github.tomakehurst.wiremock.http.Response.response;
2020
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
2121

@@ -39,7 +39,6 @@
3939
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
4040
import org.apache.hc.client5.http.entity.GzipCompressingEntity;
4141
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
42-
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
4342
import org.apache.hc.core5.http.*;
4443
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
4544
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
@@ -122,20 +121,25 @@ public Response render(ServeEvent serveEvent) {
122121
|| originalRequest.containsHeader(CONTENT_LENGTH)) {
123122
httpRequest.setEntity(buildEntityFrom(originalRequest));
124123
}
124+
125125
CloseableHttpClient client = buildClient(serveEvent.getRequest().isBrowserProxyRequest());
126-
try (CloseableHttpResponse httpResponse = client.execute(httpRequest)) {
127-
return response()
128-
.status(httpResponse.getCode())
129-
.headers(headersFrom(httpResponse, responseDefinition))
130-
.body(getEntityAsByteArrayAndCloseStream(httpResponse))
131-
.fromProxy(true)
132-
.configureDelay(
133-
settings.getFixedDelay(),
134-
settings.getDelayDistribution(),
135-
responseDefinition.getFixedDelayMilliseconds(),
136-
responseDefinition.getDelayDistribution())
137-
.chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay())
138-
.build();
126+
127+
try {
128+
return client.execute(
129+
httpRequest,
130+
httpResponse ->
131+
response()
132+
.status(httpResponse.getCode())
133+
.headers(headersFrom(httpResponse, responseDefinition))
134+
.body(getEntityAsByteArray(httpResponse))
135+
.fromProxy(true)
136+
.configureDelay(
137+
settings.getFixedDelay(),
138+
settings.getDelayDistribution(),
139+
responseDefinition.getFixedDelayMilliseconds(),
140+
responseDefinition.getDelayDistribution())
141+
.chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay())
142+
.build());
139143
} catch (SSLException e) {
140144
return proxyResponseError("SSL", httpRequest, e);
141145
} catch (IOException e) {

‎src/main/java/com/github/tomakehurst/wiremock/jetty/JettyUtils.java

+8-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2015-2022 Thomas Akehurst
2+
* Copyright (C) 2015-2023 Thomas Akehurst
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,22 +18,18 @@
1818
import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked;
1919

2020
import jakarta.servlet.ServletResponse;
21+
import jakarta.servlet.http.HttpServletRequest;
2122
import jakarta.servlet.http.HttpServletResponse;
2223
import jakarta.servlet.http.HttpServletResponseWrapper;
23-
import java.lang.reflect.Method;
2424
import java.net.Socket;
2525
import java.nio.channels.SocketChannel;
26-
import java.util.HashMap;
27-
import java.util.Map;
28-
import org.eclipse.jetty.http.HttpURI;
2926
import org.eclipse.jetty.io.ssl.SslConnection;
3027
import org.eclipse.jetty.server.HttpChannel;
3128
import org.eclipse.jetty.server.Request;
3229
import org.eclipse.jetty.server.Response;
3330

3431
public class JettyUtils {
3532

36-
private static final Map<Class<?>, Method> URI_METHOD_BY_CLASS_CACHE = new HashMap<>();
3733
private static final boolean IS_JETTY;
3834

3935
static {
@@ -83,30 +79,13 @@ public static Socket getTlsSocket(Response response) {
8379
}
8480
}
8581

86-
public static boolean uriIsAbsolute(Request request) {
87-
HttpURI uri = getHttpUri(request);
88-
return uri.getScheme() != null;
89-
}
90-
91-
private static HttpURI getHttpUri(Request request) {
92-
try {
93-
return (HttpURI) getURIMethodFromClass(request.getClass()).invoke(request);
94-
} catch (Exception e) {
95-
throw new IllegalArgumentException(request + " does not have a getUri or getHttpURI method");
82+
public static boolean isBrowserProxyRequest(HttpServletRequest request) {
83+
if (request instanceof Request) {
84+
Request jettyRequest = (Request) request;
85+
return "https".equals(jettyRequest.getHttpURI().getScheme())
86+
|| "http".equals(jettyRequest.getMetaData().getURI().getScheme());
9687
}
97-
}
9888

99-
private static Method getURIMethodFromClass(Class<?> requestClass) throws NoSuchMethodException {
100-
if (URI_METHOD_BY_CLASS_CACHE.containsKey(requestClass)) {
101-
return URI_METHOD_BY_CLASS_CACHE.get(requestClass);
102-
}
103-
Method method;
104-
try {
105-
method = requestClass.getDeclaredMethod("getUri");
106-
} catch (NoSuchMethodException ignored) {
107-
method = requestClass.getDeclaredMethod("getHttpURI");
108-
}
109-
URI_METHOD_BY_CLASS_CACHE.put(requestClass, method);
110-
return method;
89+
return false;
11190
}
11291
}

‎src/main/java/com/github/tomakehurst/wiremock/recording/Recorder.java

+8-9
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier;
2020
import static com.google.common.collect.Iterables.indexOf;
2121

22-
import com.github.tomakehurst.wiremock.common.Errors;
23-
import com.github.tomakehurst.wiremock.common.InvalidInputException;
2422
import com.github.tomakehurst.wiremock.common.Json;
2523
import com.github.tomakehurst.wiremock.core.Admin;
2624
import com.github.tomakehurst.wiremock.extension.Extensions;
@@ -57,14 +55,12 @@ public synchronized void startRecording(RecordSpec spec) {
5755
return;
5856
}
5957

60-
if (spec.getTargetBaseUrl() == null || spec.getTargetBaseUrl().isEmpty()) {
61-
throw new InvalidInputException(
62-
Errors.validation("/targetBaseUrl", "targetBaseUrl is required"));
58+
StubMapping proxyMapping = null;
59+
if (spec.getTargetBaseUrl() != null && !spec.getTargetBaseUrl().isEmpty()) {
60+
proxyMapping = proxyAllTo(spec.getTargetBaseUrl()).build();
61+
admin.addStubMapping(proxyMapping);
6362
}
6463

65-
StubMapping proxyMapping = proxyAllTo(spec.getTargetBaseUrl()).build();
66-
admin.addStubMapping(proxyMapping);
67-
6864
List<ServeEvent> serveEvents = admin.getServeEvents().getServeEvents();
6965
UUID initialId = serveEvents.isEmpty() ? null : serveEvents.get(0).getId();
7066
state = state.start(initialId, proxyMapping, spec);
@@ -84,7 +80,10 @@ public synchronized SnapshotRecordResult stopRecording() {
8480
UUID lastId = serveEvents.isEmpty() ? null : serveEvents.get(0).getId();
8581
state = state.stop(lastId);
8682
stateStore.set(state);
87-
admin.removeStubMapping(state.getProxyMapping());
83+
84+
if (state.getProxyMapping() != null) {
85+
admin.removeStubMapping(state.getProxyMapping());
86+
}
8887

8988
if (serveEvents.isEmpty()) {
9089
return SnapshotRecordResult.empty();

‎src/main/java/com/github/tomakehurst/wiremock/servlet/WireMockHttpServletRequestAdapter.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -269,12 +269,7 @@ public boolean isBrowserProxyRequest() {
269269
return false;
270270
}
271271

272-
if (request instanceof org.eclipse.jetty.server.Request) {
273-
org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request) request;
274-
return JettyUtils.uriIsAbsolute(jettyRequest);
275-
}
276-
277-
return false;
272+
return JettyUtils.isBrowserProxyRequest(request);
278273
}
279274

280275
@Override

‎src/test/java/com/github/tomakehurst/wiremock/BrowserProxyAcceptanceTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class BrowserProxyAcceptanceTest {
4343
public void init() {
4444
testClient = new WireMockTestClient(target.getPort());
4545

46-
proxy = new WireMockServer(wireMockConfig().dynamicPort().enableBrowserProxying(true));
46+
proxy = new WireMockServer(wireMockConfig().port(8111).enableBrowserProxying(true));
4747
proxy.start();
4848
}
4949

‎src/test/java/com/github/tomakehurst/wiremock/RecordingDslAcceptanceTest.java

+20-20
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import static org.junit.jupiter.api.Assertions.assertThrows;
3131

3232
import com.github.tomakehurst.wiremock.client.WireMock;
33-
import com.github.tomakehurst.wiremock.common.InvalidInputException;
3433
import com.github.tomakehurst.wiremock.matching.EqualToJsonPattern;
3534
import com.github.tomakehurst.wiremock.recording.NotRecordingException;
3635
import com.github.tomakehurst.wiremock.recording.RecordingStatus;
@@ -62,7 +61,11 @@ public void init() {
6261
fileRoot = setupTempFileRoot();
6362
proxyingService =
6463
new WireMockServer(
65-
wireMockConfig().dynamicPort().withRootDirectory(fileRoot.getAbsolutePath()));
64+
wireMockConfig()
65+
.dynamicPort()
66+
.withRootDirectory(fileRoot.getAbsolutePath())
67+
.enableBrowserProxying(true)
68+
.trustAllProxyTargets(true));
6669
proxyingService.start();
6770

6871
targetService = wireMockServer;
@@ -368,6 +371,21 @@ public void doesNotWriteTextResponseFilesUnder1KbByDefault() {
368371
assertThat(bodyFileName, nullValue());
369372
}
370373

374+
@Test
375+
void recordsViaBrowserProxyingWhenNoTargetUrlSpecified() {
376+
targetService.stubFor(get(urlPathMatching("/record-this/.*")).willReturn(ok("Via proxy")));
377+
378+
startRecording();
379+
380+
String url = targetService.baseUrl() + "/record-this/123";
381+
client.getViaProxy(url, proxyingService.port());
382+
383+
List<StubMapping> mappings = stopRecording().getStubMappings();
384+
385+
StubMapping mapping = mappings.get(0);
386+
assertThat(mapping.getRequest().getUrl(), is("/record-this/123"));
387+
}
388+
371389
@Test
372390
public void throwsAnErrorIfAttemptingToStopViaStaticRemoteDslWhenNotRecording() {
373391
assertThrows(NotRecordingException.class, WireMock::stopRecording);
@@ -382,22 +400,4 @@ public void throwsAnErrorIfAttemptingToStopViaInstanceRemoteDslWhenNotRecording(
382400
public void throwsAnErrorIfAttemptingToStopViaDirectDslWhenNotRecording() {
383401
assertThrows(NotRecordingException.class, proxyingService::stopRecording);
384402
}
385-
386-
@Test
387-
public void throwsValidationErrorWhenAttemptingToStartRecordingViaStaticDslWithNoTargetUrl() {
388-
assertThrows(
389-
InvalidInputException.class,
390-
() -> {
391-
startRecording(recordSpec());
392-
});
393-
}
394-
395-
@Test
396-
public void throwsValidationErrorWhenAttemptingToStartRecordingViaDirectDslWithNoTargetUrl() {
397-
assertThrows(
398-
InvalidInputException.class,
399-
() -> {
400-
proxyingService.startRecording(recordSpec());
401-
});
402-
}
403403
}

‎src/test/java/com/github/tomakehurst/wiremock/testsupport/WireMockResponse.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.common.collect.ImmutableListMultimap;
2323
import com.google.common.collect.Multimap;
24+
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
2425
import org.apache.hc.core5.http.ClassicHttpResponse;
2526
import org.apache.hc.core5.http.Header;
2627

@@ -29,7 +30,7 @@ public class WireMockResponse {
2930
private final ClassicHttpResponse httpResponse;
3031
private final byte[] content;
3132

32-
public WireMockResponse(ClassicHttpResponse httpResponse) {
33+
public WireMockResponse(CloseableHttpResponse httpResponse) {
3334
this.httpResponse = httpResponse;
3435
content = getEntityAsByteArrayAndCloseStream(httpResponse);
3536
}

0 commit comments

Comments
 (0)