Skip to content

Commit

Permalink
Added support for recording via an existing proxy configuration (forw…
Browse files Browse the repository at this point in the history
…ard or reverse) and not having to set the target URL.
  • Loading branch information
tomakehurst committed Jul 25, 2023
1 parent 5f0677f commit 5637a00
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.RequestMethod;
import com.github.tomakehurst.wiremock.matching.*;
import com.github.tomakehurst.wiremock.recording.RecordSpec;
import com.github.tomakehurst.wiremock.recording.RecordSpecBuilder;
import com.github.tomakehurst.wiremock.recording.RecordingStatusResult;
import com.github.tomakehurst.wiremock.recording.SnapshotRecordResult;
Expand Down Expand Up @@ -966,6 +967,10 @@ public static void startRecording(String targetBaseUrl) {
defaultInstance.get().startStubRecording(targetBaseUrl);
}

public static void startRecording() {
defaultInstance.get().startStubRecording();
}

public static void startRecording(RecordSpecBuilder spec) {
defaultInstance.get().startStubRecording(spec);
}
Expand All @@ -974,6 +979,10 @@ public void startStubRecording(String targetBaseUrl) {
admin.startRecording(targetBaseUrl);
}

public void startStubRecording() {
admin.startRecording(RecordSpec.DEFAULTS);
}

public void startStubRecording(RecordSpecBuilder spec) {
admin.startRecording(spec.build());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.github.tomakehurst.wiremock.common;

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

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

public static byte[] getEntityAsByteArrayAndCloseStream(ClassicHttpResponse httpResponse) {
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
try {
byte[] content = EntityUtils.toByteArray(entity);
entity.getContent().close();
return content;
} catch (IOException ioe) {
throw new RuntimeException(ioe);
try {
if (entity != null) {
return EntityUtils.toByteArray(entity);
}
} catch (IOException ioe) {
return throwUnchecked(ioe, byte[].class);
} finally {
Exceptions.uncheck(httpResponse::close);
}

return null;
}

public static byte[] getEntityAsByteArray(ClassicHttpResponse httpResponse) {
HttpEntity entity = httpResponse.getEntity();
try {
if (entity != null) {
return EntityUtils.toByteArray(entity);
}
} catch (IOException ioe) {
return throwUnchecked(ioe, byte[].class);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package com.github.tomakehurst.wiremock.http;

import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArrayAndCloseStream;
import static com.github.tomakehurst.wiremock.common.HttpClientUtils.getEntityAsByteArray;
import static com.github.tomakehurst.wiremock.http.Response.response;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;

Expand All @@ -39,7 +39,6 @@
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.entity.GzipCompressingEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
Expand Down Expand Up @@ -122,20 +121,25 @@ public Response render(ServeEvent serveEvent) {
|| originalRequest.containsHeader(CONTENT_LENGTH)) {
httpRequest.setEntity(buildEntityFrom(originalRequest));
}

CloseableHttpClient client = buildClient(serveEvent.getRequest().isBrowserProxyRequest());
try (CloseableHttpResponse httpResponse = client.execute(httpRequest)) {
return response()
.status(httpResponse.getCode())
.headers(headersFrom(httpResponse, responseDefinition))
.body(getEntityAsByteArrayAndCloseStream(httpResponse))
.fromProxy(true)
.configureDelay(
settings.getFixedDelay(),
settings.getDelayDistribution(),
responseDefinition.getFixedDelayMilliseconds(),
responseDefinition.getDelayDistribution())
.chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay())
.build();

try {
return client.execute(
httpRequest,
httpResponse ->
response()
.status(httpResponse.getCode())
.headers(headersFrom(httpResponse, responseDefinition))
.body(getEntityAsByteArray(httpResponse))
.fromProxy(true)
.configureDelay(
settings.getFixedDelay(),
settings.getDelayDistribution(),
responseDefinition.getFixedDelayMilliseconds(),
responseDefinition.getDelayDistribution())
.chunkedDribbleDelay(responseDefinition.getChunkedDribbleDelay())
.build());
} catch (SSLException e) {
return proxyResponseError("SSL", httpRequest, e);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2015-2022 Thomas Akehurst
* Copyright (C) 2015-2023 Thomas Akehurst
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,22 +18,18 @@
import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked;

import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import java.lang.reflect.Method;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;

public class JettyUtils {

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

static {
Expand Down Expand Up @@ -83,30 +79,13 @@ public static Socket getTlsSocket(Response response) {
}
}

public static boolean uriIsAbsolute(Request request) {
HttpURI uri = getHttpUri(request);
return uri.getScheme() != null;
}

private static HttpURI getHttpUri(Request request) {
try {
return (HttpURI) getURIMethodFromClass(request.getClass()).invoke(request);
} catch (Exception e) {
throw new IllegalArgumentException(request + " does not have a getUri or getHttpURI method");
public static boolean isBrowserProxyRequest(HttpServletRequest request) {
if (request instanceof Request) {
Request jettyRequest = (Request) request;
return "https".equals(jettyRequest.getHttpURI().getScheme())
|| "http".equals(jettyRequest.getMetaData().getURI().getScheme());
}
}

private static Method getURIMethodFromClass(Class<?> requestClass) throws NoSuchMethodException {
if (URI_METHOD_BY_CLASS_CACHE.containsKey(requestClass)) {
return URI_METHOD_BY_CLASS_CACHE.get(requestClass);
}
Method method;
try {
method = requestClass.getDeclaredMethod("getUri");
} catch (NoSuchMethodException ignored) {
method = requestClass.getDeclaredMethod("getHttpURI");
}
URI_METHOD_BY_CLASS_CACHE.put(requestClass, method);
return method;
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier;
import static com.google.common.collect.Iterables.indexOf;

import com.github.tomakehurst.wiremock.common.Errors;
import com.github.tomakehurst.wiremock.common.InvalidInputException;
import com.github.tomakehurst.wiremock.common.Json;
import com.github.tomakehurst.wiremock.core.Admin;
import com.github.tomakehurst.wiremock.extension.Extensions;
Expand Down Expand Up @@ -57,14 +55,12 @@ public synchronized void startRecording(RecordSpec spec) {
return;
}

if (spec.getTargetBaseUrl() == null || spec.getTargetBaseUrl().isEmpty()) {
throw new InvalidInputException(
Errors.validation("/targetBaseUrl", "targetBaseUrl is required"));
StubMapping proxyMapping = null;
if (spec.getTargetBaseUrl() != null && !spec.getTargetBaseUrl().isEmpty()) {
proxyMapping = proxyAllTo(spec.getTargetBaseUrl()).build();
admin.addStubMapping(proxyMapping);
}

StubMapping proxyMapping = proxyAllTo(spec.getTargetBaseUrl()).build();
admin.addStubMapping(proxyMapping);

List<ServeEvent> serveEvents = admin.getServeEvents().getServeEvents();
UUID initialId = serveEvents.isEmpty() ? null : serveEvents.get(0).getId();
state = state.start(initialId, proxyMapping, spec);
Expand All @@ -84,7 +80,10 @@ public synchronized SnapshotRecordResult stopRecording() {
UUID lastId = serveEvents.isEmpty() ? null : serveEvents.get(0).getId();
state = state.stop(lastId);
stateStore.set(state);
admin.removeStubMapping(state.getProxyMapping());

if (state.getProxyMapping() != null) {
admin.removeStubMapping(state.getProxyMapping());
}

if (serveEvents.isEmpty()) {
return SnapshotRecordResult.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,7 @@ public boolean isBrowserProxyRequest() {
return false;
}

if (request instanceof org.eclipse.jetty.server.Request) {
org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request) request;
return JettyUtils.uriIsAbsolute(jettyRequest);
}

return false;
return JettyUtils.isBrowserProxyRequest(request);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class BrowserProxyAcceptanceTest {
public void init() {
testClient = new WireMockTestClient(target.getPort());

proxy = new WireMockServer(wireMockConfig().dynamicPort().enableBrowserProxying(true));
proxy = new WireMockServer(wireMockConfig().port(8111).enableBrowserProxying(true));
proxy.start();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.InvalidInputException;
import com.github.tomakehurst.wiremock.matching.EqualToJsonPattern;
import com.github.tomakehurst.wiremock.recording.NotRecordingException;
import com.github.tomakehurst.wiremock.recording.RecordingStatus;
Expand Down Expand Up @@ -62,7 +61,11 @@ public void init() {
fileRoot = setupTempFileRoot();
proxyingService =
new WireMockServer(
wireMockConfig().dynamicPort().withRootDirectory(fileRoot.getAbsolutePath()));
wireMockConfig()
.dynamicPort()
.withRootDirectory(fileRoot.getAbsolutePath())
.enableBrowserProxying(true)
.trustAllProxyTargets(true));
proxyingService.start();

targetService = wireMockServer;
Expand Down Expand Up @@ -368,6 +371,21 @@ public void doesNotWriteTextResponseFilesUnder1KbByDefault() {
assertThat(bodyFileName, nullValue());
}

@Test
void recordsViaBrowserProxyingWhenNoTargetUrlSpecified() {
targetService.stubFor(get(urlPathMatching("/record-this/.*")).willReturn(ok("Via proxy")));

startRecording();

String url = targetService.baseUrl() + "/record-this/123";
client.getViaProxy(url, proxyingService.port());

List<StubMapping> mappings = stopRecording().getStubMappings();

StubMapping mapping = mappings.get(0);
assertThat(mapping.getRequest().getUrl(), is("/record-this/123"));
}

@Test
public void throwsAnErrorIfAttemptingToStopViaStaticRemoteDslWhenNotRecording() {
assertThrows(NotRecordingException.class, WireMock::stopRecording);
Expand All @@ -382,22 +400,4 @@ public void throwsAnErrorIfAttemptingToStopViaInstanceRemoteDslWhenNotRecording(
public void throwsAnErrorIfAttemptingToStopViaDirectDslWhenNotRecording() {
assertThrows(NotRecordingException.class, proxyingService::stopRecording);
}

@Test
public void throwsValidationErrorWhenAttemptingToStartRecordingViaStaticDslWithNoTargetUrl() {
assertThrows(
InvalidInputException.class,
() -> {
startRecording(recordSpec());
});
}

@Test
public void throwsValidationErrorWhenAttemptingToStartRecordingViaDirectDslWithNoTargetUrl() {
assertThrows(
InvalidInputException.class,
() -> {
proxyingService.startRecording(recordSpec());
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;

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

public WireMockResponse(ClassicHttpResponse httpResponse) {
public WireMockResponse(CloseableHttpResponse httpResponse) {
this.httpResponse = httpResponse;
content = getEntityAsByteArrayAndCloseStream(httpResponse);
}
Expand Down

0 comments on commit 5637a00

Please sign in to comment.