interceptor) {
+ super(address);
+ this.interceptor = interceptor;
+ this.messageSerializer = new MessageSerializer();
+ }
+
+ @Override
+ public void onOpen(WebSocket conn, ClientHandshake handshake) {
+ LOGGER.debug("new connection to websocket server" + conn.getRemoteSocketAddress());
+ }
+
+ @Override
+ public void onClose(WebSocket conn, int code, String reason, boolean remote) {
+ LOGGER.debug("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason);
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, String message) {
+ LOGGER.debug("received message from " + conn.getRemoteSocketAddress() + " : " + message);
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, ByteBuffer rawInputMessage) {
+ // NOTE: this method runs concurrently on a per-connection thread — see
+ // the class-level Javadoc. deserializeMessage/serializeMessage are
+ // stateless so they're safe to call from multiple threads here; the
+ // interceptor call below is the boundary into user code.
+ InterceptedMessage incomingMessage = this.messageSerializer.deserializeMessage(rawInputMessage);
+
+ InterceptedMessage messageToSendBack;
+ try {
+ InterceptedMessage modifiedMessage = interceptor.apply(incomingMessage);
+
+ // if the supplied interceptor function does not return a message, assume no changes were intended and
+ // just complete the request
+ messageToSendBack = (modifiedMessage != null) ? modifiedMessage : incomingMessage;
+ } catch (RuntimeException e) {
+ // Without this, an exception thrown by user code (e.g. a
+ // non-thread-safe collection blowing up under concurrent access)
+ // propagates up through the websocket library's thread and the
+ // in-flight HTTP request mitmproxy is waiting on hangs forever
+ // with no indication of why. Log it and pass the request through
+ // unmodified instead of leaving the caller hanging.
+ LOGGER.error("interceptor threw an exception while processing " + incomingMessage.getRequest().getUrl()
+ + "; passing request through unmodified", e);
+ messageToSendBack = incomingMessage;
}
- @Override
- public void onStart() {
- LOGGER.info("websocket server started successfully");
+ try {
+ conn.send(this.messageSerializer.serializeMessage(messageToSendBack));
+ } catch (JsonProcessingException e) {
+ LOGGER.error(e.getMessage());
}
-}
+ }
+
+ @Override
+ public void onError(WebSocket conn, Exception ex) {
+ // conn is null for server-level fatal errors (e.g. a bind failure on
+ // startup) that happen before any client connection exists — calling
+ // conn.getRemoteSocketAddress() in that case throws an NPE that hides
+ // the actual underlying exception (ex) from the logs.
+ String connectionInfo = (conn != null) ? String.valueOf(conn.getRemoteSocketAddress()) : "N/A (no connection - likely a server-level error)";
+ LOGGER.error("an error occured on connection " + connectionInfo + ":" + ex, ex);
+ }
+
+ @Override
+ public void onStart() {
+ LOGGER.info("websocket server started successfully");
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/scripts/proxy.py b/src/main/resources/scripts/proxy.py
index 01ab562..530034f 100644
--- a/src/main/resources/scripts/proxy.py
+++ b/src/main/resources/scripts/proxy.py
@@ -57,9 +57,15 @@ def __init__(self):
self.queue = queue.Queue()
self.intercept_paths = frozenset([])
self.only_intercept_text_files = False
+ self.ws_port = 8765
self.finished = False
- # Start websocket thread
- threading.Thread(target=self.websocket_thread).start()
+ self._websocket_thread_started = False
+ # NOTE: the websocket thread is intentionally NOT started here. mitmproxy
+ # calls __init__() before load()/configure(), so starting the thread here
+ # would race against configure() setting the real wsPort from the CLI —
+ # the background thread could connect using the stale default before the
+ # real port is applied. It's started instead on the first configure()
+ # call, once all options (including wsPort) are guaranteed to be set.
def load(self, loader):
loader.add_option(
@@ -75,6 +81,16 @@ def load(self, loader):
default = False,
help = "If true, the plugin only intercepts text files and passes through other types of files",
)
+ loader.add_option(
+ name = "wsPort",
+ typespec = int,
+ default = 8765,
+ help = "The port this script's websocket client connects to, to talk back to the Java control process. "
+ "Must match the port MitmproxyJava bound its control-channel server to. Defaults to 8765 for "
+ "backwards compatibility, but MUST be set explicitly to a unique free port whenever more than "
+ "one MitmproxyJava instance may run on the same machine at once (e.g. parallel test execution) "
+ "— otherwise multiple instances will collide trying to use the same fixed port.",
+ )
return
def configure(self, updates):
@@ -86,6 +102,12 @@ def configure(self, updates):
self.only_intercept_text_files = ctx.options.onlyInterceptTextFiles
#print("Only intercept text files:")
#print(self.only_intercept_text_files)
+ if "wsPort" in updates:
+ self.ws_port = ctx.options.wsPort
+
+ if not self._websocket_thread_started:
+ self._websocket_thread_started = True
+ threading.Thread(target=self.websocket_thread).start()
return
def send_message(self, metadata, data1, data2):
@@ -220,7 +242,7 @@ async def websocket_loop(self):
"""
while not self.finished:
try:
- async with websockets.connect('ws://localhost:8765', max_size = None) as websocket:
+ async with websockets.connect(f'ws://localhost:{self.ws_port}', max_size = None) as websocket:
while True:
# Make sure connection is still live.
await websocket.ping()
@@ -253,4 +275,4 @@ async def websocket_loop(self):
addons = [
WebSocketAdapter()
-]
+]
\ No newline at end of file
diff --git a/src/test/java/io/appium/mitmproxy/ConcurrencyTest.java b/src/test/java/io/appium/mitmproxy/ConcurrencyTest.java
new file mode 100644
index 0000000..9dd3ef7
--- /dev/null
+++ b/src/test/java/io/appium/mitmproxy/ConcurrencyTest.java
@@ -0,0 +1,226 @@
+package io.appium.mitmproxy;
+
+import kong.unirest.core.Unirest;
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests specifically exercising the thread-safety fixes made to
+ * {@link MitmproxyJava} and {@link MitmproxyServer}:
+ *
+ * 1. Many requests in flight at once should all be captured correctly by a
+ * thread-safe interceptor, with no lost/corrupted messages.
+ * 2. A racing start() from multiple threads should let exactly one caller
+ * succeed and everyone else should fail fast with IllegalStateException,
+ * rather than double-starting the websocket server / mitmdump process.
+ * 3. An interceptor that throws should not hang the in-flight HTTP request —
+ * the request should still complete (unmodified) instead of timing out.
+ * 4. stop() without a successful start() should be a safe no-op, not an NPE.
+ *
+ * Requires mitmdump to be resolvable — see MitmproxyJavaTest.getMITMDumpPath().
+ */
+public class ConcurrencyTest {
+
+ private MitmproxyJava proxy;
+
+ @After
+ public void tearDown() throws InterruptedException {
+ if (proxy != null) {
+ proxy.stop(); // safe no-op now even if start() never succeeded
+ }
+ // Unirest 4's Config locks itself once the underlying HTTP client is
+ // built (i.e. after the first request), so the *next* test's call to
+ // Unirest.config().proxy(...) would throw UnirestConfigException
+ // without this reset(). reset() also clears default headers, proxy,
+ // etc., so there's no separate clearDefaultHeaders() call needed.
+ Unirest.config().reset();
+ }
+
+ @Test
+ public void concurrentRequestsAreAllCapturedWithoutLossOrCorruption() throws Exception {
+ int requestCount = 25;
+ int port = 8090;
+
+ // The collection under test: this MUST be thread-safe, since the
+ // interceptor is invoked concurrently once more than one request is
+ // in flight. This is exactly the pattern from ThreadSafeMessageCollector.
+ List messages = new CopyOnWriteArrayList<>();
+
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> {
+ messages.add(m);
+ return m;
+ }, port, null);
+ proxy.start();
+
+ Unirest.config().proxy("localhost", port);
+
+ ExecutorService pool = Executors.newFixedThreadPool(10);
+ try {
+ List> futures = IntStream.range(0, requestCount)
+ .mapToObj(i -> pool.submit(() -> {
+ try {
+ Unirest.get("http://appium.io")
+ .header("requestIndex", String.valueOf(i))
+ .asString();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }))
+ .collect(Collectors.toList());
+
+ for (Future> f : futures) {
+ f.get(15, TimeUnit.SECONDS); // fail the test if any request hangs
+ }
+ } finally {
+ pool.shutdown();
+ }
+
+ proxy.stop();
+
+ // No lost or duplicated messages despite concurrent writers.
+ assertThat(messages).hasSize(requestCount);
+
+ // Every request index shows up exactly once — if the underlying list
+ // were a plain ArrayList, concurrent adds could silently drop entries
+ // or throw ConcurrentModificationException before we even get here.
+ List seenIndices = messages.stream()
+ .map(m -> headerValue(m, "requestIndex"))
+ .collect(Collectors.toList());
+ List expectedIndices = IntStream.range(0, requestCount)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.toList());
+ assertThat(seenIndices).containsExactlyInAnyOrderElementsOf(expectedIndices);
+ }
+
+ @Test
+ public void onlyOneConcurrentStartCallSucceeds() throws Exception {
+ int port = 8091;
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, port, null);
+
+ int threadCount = 5;
+ CountDownLatch readyLatch = new CountDownLatch(threadCount);
+ CountDownLatch goLatch = new CountDownLatch(1);
+ AtomicInteger successCount = new AtomicInteger(0);
+ AtomicInteger failureCount = new AtomicInteger(0);
+
+ ExecutorService pool = Executors.newFixedThreadPool(threadCount);
+ try {
+ for (int i = 0; i < threadCount; i++) {
+ pool.submit(() -> {
+ readyLatch.countDown();
+ try {
+ goLatch.await(); // line every thread up to hit start() at once
+ proxy.start();
+ successCount.incrementAndGet();
+ } catch (IllegalStateException e) {
+ failureCount.incrementAndGet();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ readyLatch.await(5, TimeUnit.SECONDS);
+ goLatch.countDown(); // release all threads simultaneously
+
+ pool.shutdown();
+ assertThat(pool.awaitTermination(15, TimeUnit.SECONDS)).isTrue();
+ } finally {
+ if (!pool.isShutdown()) {
+ pool.shutdownNow();
+ }
+ }
+
+ // synchronized + AtomicBoolean guard means exactly one thread wins the race.
+ assertThat(successCount.get()).isEqualTo(1);
+ assertThat(failureCount.get()).isEqualTo(threadCount - 1);
+
+ // and the proxy is actually usable after the race settles
+ Unirest.config().proxy("localhost", port);
+ assertThat(Unirest.get("http://appium.io").asString().getStatus()).isEqualTo(200);
+ }
+
+ @Test
+ public void interceptorExceptionDoesNotHangTheRequest() throws Exception {
+ int port = 8092;
+
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> {
+ throw new RuntimeException("boom - simulated broken interceptor");
+ }, port, null);
+ proxy.start();
+
+ Unirest.config().proxy("localhost", port);
+
+ // Before the try/catch fix in MitmproxyServer.onMessage, this would
+ // hang indefinitely because the connection thread died with an
+ // uncaught exception and never sent a response back to mitmdump.
+ ExecutorService pool = Executors.newSingleThreadExecutor();
+ try {
+ Future future = pool.submit(() ->
+ Unirest.get("http://appium.io").asString().getStatus());
+
+ Integer status = future.get(10, TimeUnit.SECONDS); // throws TimeoutException if it hangs
+ assertThat(status).isEqualTo(200);
+ } finally {
+ pool.shutdownNow();
+ }
+ }
+
+ @Test
+ public void stopWithoutStartIsASafeNoOp() throws InterruptedException {
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8093, null);
+ // start() deliberately not called
+ proxy.stop(); // should not throw
+ }
+
+ @Test
+ public void stopCanBeCalledMultipleTimesSafely() throws Exception {
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8094, null);
+ proxy.start();
+ proxy.stop();
+ proxy.stop(); // second call should be a no-op, not throw
+ }
+
+ @Test
+ public void startAfterStopWorksAgain() throws Exception {
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8095, null);
+ proxy.start();
+ proxy.stop();
+
+ // instance should be reusable, not permanently wedged
+ proxy.start();
+ Unirest.config().proxy("localhost", 8095);
+ assertThat(Unirest.get("http://appium.io").asString().getStatus()).isEqualTo(200);
+ }
+
+ @Test
+ public void startTwiceWithoutStopThrows() throws Exception {
+ proxy = new MitmproxyJava(MitmproxyJavaTest.getMITMDumpPath(), m -> m, 8096, null);
+ proxy.start();
+
+ assertThatThrownBy(() -> proxy.start())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ private static String headerValue(InterceptedMessage message, String headerName) {
+ return message.getRequest().getHeaders().stream()
+ .filter(h -> h.length == 2 && headerName.equalsIgnoreCase(h[0]))
+ .map(h -> h[1])
+ .findFirst()
+ .orElse(null);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java b/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java
index 85ea9c3..4386550 100644
--- a/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java
+++ b/src/test/java/io/appium/mitmproxy/MitmproxyJavaTest.java
@@ -1,9 +1,8 @@
package io.appium.mitmproxy;
-import com.mashape.unirest.http.HttpResponse;
-import com.mashape.unirest.http.Unirest;
-import com.mashape.unirest.http.exceptions.UnirestException;
-import org.apache.http.HttpHost;
+import kong.unirest.core.HttpResponse;
+import kong.unirest.core.Unirest;
+import kong.unirest.core.UnirestException;
import org.junit.Test;
import java.io.IOException;
@@ -18,117 +17,115 @@
public class MitmproxyJavaTest {
- //private static final String MITMDUMP_PATH = "C:\\Python37\\Scripts\\mitmdump.exe";
- private static final String MITMDUMP_PATH = getMITMDumpPath();
-
- static String getMITMDumpPath(){
- if(System.getProperty("os.name").contains("Mac")){
- return "/opt/homebrew/bin/mitmdump";
- }
- return "/usr/local/bin/mitmdump";
- }
- @Test
- public void ConstructorTest() throws InterruptedException, IOException, TimeoutException {
- MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
- System.out.println(m.getRequest().getUrl());
- return m;
- });
- proxy.start();
- System.out.println("advanced in test");
- proxy.stop();
- }
-
- @Test
- public void SimpleTest() throws InterruptedException, TimeoutException, IOException, UnirestException {
- List messages = new ArrayList<>();
-
- MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
- messages.add(m);
- return m;
- });
- proxy.start();
-
- Unirest.setProxy(new HttpHost("localhost", 8080));
- Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString();
-
- proxy.stop();
- final InterceptedMessage firstMessage = messages.get(0);
-
- assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io");
- assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"});
- assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200);
- }
-
- @Test
- public void NullInterceptorReturnTest() throws InterruptedException, TimeoutException, IOException, UnirestException {
- List messages = new ArrayList<>();
-
- MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
- messages.add(m);
- return null;
- }, 8087, null);
- proxy.start();
-
- Unirest.setProxy(new HttpHost("localhost", 8087));
- Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString();
-
- proxy.stop();
-
- assertThat(messages).isNotEmpty();
-
- final InterceptedMessage firstMessage = messages.get(0);
-
- assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io");
- assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"});
- assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200);
- }
-
- @Test
- public void ResponseModificationTest() throws InterruptedException, TimeoutException, IOException, UnirestException {
- List messages = new ArrayList<>();
-
- MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
- messages.add(m);
- m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8));
- m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"});
- m.getResponse().setStatusCode(208);
- return m;
- });
- proxy.start();
-
- Unirest.setProxy(new HttpHost("localhost", 8080));
- HttpResponse response = Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString();
- proxy.stop();
-
- assertThat(response.getBody()).isEqualTo("Hi from Test");
-
- final InterceptedMessage firstMessage = messages.get(0);
-
- assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io");
- assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"});
- assertThat(firstMessage.getResponse().getHeaders()).containsOnlyOnce(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"});
- assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(208);
- }
-
- @Test
- public void shouldAddParametersToMitmdumpStart() throws IOException, TimeoutException, InterruptedException {
- List mitmdumpParams = new ArrayList<>();
- mitmdumpParams.add("testParam");
-
- List spiedParams = spy(mitmdumpParams);
-
- MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
- m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8));
- m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"});
- m.getResponse().setStatusCode(208);
- return m;
- }, 8087, spiedParams);
-
- proxy.start();
- proxy.stop();
-
- //to verify that additional params were actually included to start path
- verify(spiedParams).toArray();
-
- }
+ //private static final String MITMDUMP_PATH = "C:\\Python37\\Scripts\\mitmdump.exe";
+ private static final String MITMDUMP_PATH = getMITMDumpPath();
+
+ static String getMITMDumpPath() {
+ return "/Library/Frameworks/Python.framework/Versions/3.11/bin/mitmdump";
+ }
+
+ @Test
+ public void ConstructorTest() throws InterruptedException, IOException, TimeoutException {
+ MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
+ System.out.println(m.getRequest().getUrl());
+ return m;
+ });
+ proxy.start();
+ System.out.println("advanced in test");
+ proxy.stop();
+ }
+
+ @Test
+ public void SimpleTest() throws InterruptedException, TimeoutException, IOException, UnirestException {
+ List messages = new ArrayList<>();
+
+ MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
+ messages.add(m);
+ return m;
+ });
+ proxy.start();
+
+ Unirest.config().reset().proxy("localhost", 8080);
+ Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString();
+
+ proxy.stop();
+ final InterceptedMessage firstMessage = messages.get(0);
+
+ assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io");
+ assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"});
+ assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200);
+ }
+
+ @Test
+ public void NullInterceptorReturnTest() throws InterruptedException, TimeoutException, IOException, UnirestException {
+ List messages = new ArrayList<>();
+
+ MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
+ messages.add(m);
+ return null;
+ }, 8087, null);
+ proxy.start();
+
+ Unirest.config().reset().proxy("localhost", 8087);
+ Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString();
+
+ proxy.stop();
+
+ assertThat(messages).isNotEmpty();
+
+ final InterceptedMessage firstMessage = messages.get(0);
+
+ assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io");
+ assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"});
+ assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(200);
+ }
+
+ @Test
+ public void ResponseModificationTest() throws InterruptedException, TimeoutException, IOException, UnirestException {
+ List messages = new ArrayList<>();
+
+ MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
+ messages.add(m);
+ m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8));
+ m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"});
+ m.getResponse().setStatusCode(208);
+ return m;
+ });
+ proxy.start();
+
+ Unirest.config().reset().proxy("localhost", 8080);
+ HttpResponse response = Unirest.get("http://appium.io").header("myTestHeader", "myTestValue").asString();
+ proxy.stop();
+
+ assertThat(response.getBody()).isEqualTo("Hi from Test");
+
+ final InterceptedMessage firstMessage = messages.get(0);
+
+ assertThat(firstMessage.getRequest().getUrl()).startsWith("http://appium.io");
+ assertThat(firstMessage.getRequest().getHeaders()).containsOnlyOnce(new String[]{"myTestHeader", "myTestValue"});
+ assertThat(firstMessage.getResponse().getHeaders()).containsOnlyOnce(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"});
+ assertThat(firstMessage.getResponse().getStatusCode()).isEqualTo(208);
+ }
+
+ @Test
+ public void shouldAddParametersToMitmdumpStart() throws IOException, TimeoutException, InterruptedException {
+ List mitmdumpParams = new ArrayList<>();
+ mitmdumpParams.add("testParam");
+
+ List spiedParams = spy(mitmdumpParams);
+
+ MitmproxyJava proxy = new MitmproxyJava(MITMDUMP_PATH, (InterceptedMessage m) -> {
+ m.getResponse().setBody("Hi from Test".getBytes(StandardCharsets.UTF_8));
+ m.getResponse().getHeaders().add(new String[]{"myTestResponseHeader", "myTestResponseHeaderValue"});
+ m.getResponse().setStatusCode(208);
+ return m;
+ }, 8087, spiedParams);
+
+ proxy.start();
+ proxy.stop();
+
+ //to verify that additional params were actually included to start path
+ verify(spiedParams).toArray();
+
+ }
}
\ No newline at end of file