1414import java .io .IOException ;
1515import java .io .PipedInputStream ;
1616import java .io .PipedOutputStream ;
17+ import java .net .BindException ;
1718import java .net .InetSocketAddress ;
1819import java .util .ArrayList ;
1920import java .util .Arrays ;
@@ -73,7 +74,7 @@ public void start(int port, List<AbstractAddon> addons) {
7374 try {
7475 this .proxyPort = port ;
7576
76- startProxy (port , addons );
77+ startProxyWithRetries (port , addons , 3 );
7778
7879 this .isRunning = true ;
7980
@@ -119,10 +120,11 @@ public boolean isRunning() {
119120 public void stop () {
120121 this .isRunning = false ;
121122
122- Process process = startedProcess .getProcess ();
123- process .destroyForcibly ();
124-
125- Awaitility .await ().atMost (10 , TimeUnit .SECONDS ).until (() -> !process .isAlive ());
123+ if (startedProcess != null ) {
124+ Process process = startedProcess .getProcess ();
125+ process .destroyForcibly ();
126+ Awaitility .await ().atMost (10 , TimeUnit .SECONDS ).until (() -> !process .isAlive ());
127+ }
126128 }
127129
128130 public void setTrustAll (boolean trustAll ) {
@@ -146,6 +148,26 @@ private List<AbstractAddon> defaultAddons() {
146148 return Arrays .asList (addonsArray );
147149 }
148150
151+ private void startProxyWithRetries (int port , List <AbstractAddon > addons , int retryCount ) {
152+ for (int attempt = 1 ; attempt <= retryCount ; attempt ++) {
153+ try {
154+ startProxy (port , addons );
155+ break ;
156+ } catch (Exception ex ) {
157+ // For binding exception not going to retry (let driver to try another port)
158+ if (ex .getCause () != null && ex .getCause () instanceof BindException ) {
159+ throw ex ;
160+ }
161+ if (attempt < retryCount ) {
162+ LOGGER .error ("Failed to start proxy, attempt: {}, retries count: {}, going to retry..." , attempt , retryCount , ex );
163+ } else {
164+ LOGGER .error ("Failed to start proxy, no retries left, throwing exception" , ex );
165+ throw ex ;
166+ }
167+ }
168+ }
169+ }
170+
149171 private void startProxy (int port , List <AbstractAddon > addons ) {
150172 List <String > command = new ArrayList <String >() {{
151173 add ("mitmdump" );
@@ -156,25 +178,59 @@ private void startProxy(int port, List<AbstractAddon> addons) {
156178 command .add ("--ssl-insecure" );
157179 }
158180
159- InetSocketAddress upstreamProxyAddress = proxyManager .getUpstreamProxyAddress ();
160- if (upstreamProxyAddress != null ) {
161- String schema = "http" ;
162- if (proxyManager .isUseHttpsUpstreamProxy ()) {
163- schema = "https" ;
164- }
165- command .add ("--mode" );
166- command .add ("upstream:" + schema + "://" + upstreamProxyAddress .getHostName () + ":" + upstreamProxyAddress .getPort ());
167- }
181+ updateCommandWithUpstreamProxy (command );
182+ updateCommandWithLogLevel (command );
183+ updateCommandWithAddOns (addons , command );
168184
169- command .add ("--set" );
170- command .add ("termlog_verbosity=" + getMitmProxyLoggingLevel ());
185+ LOGGER .info ("Starting proxy using command: " + String .join (" " , command ));
171186
172- addons .forEach (addon -> command .addAll (Arrays .asList (addon .getCommandParams ())));
187+ ProcessExecutor processExecutor = createProcessExecutor (command );
188+ try {
189+ startedProcess = processExecutor .start ();
190+ } catch (Exception ex ) {
191+ throw new RuntimeException ("Couldn't start mitmproxy process" , ex );
192+ }
193+ try {
194+ Awaitility .await ()
195+ .atMost (5 , TimeUnit .SECONDS )
196+ .until (this .proxyManager ::callHealthCheck );
197+ } catch (ConditionTimeoutException ex ) {
198+ handleHealthCheckFailure ();
199+ }
200+ }
173201
174- LOGGER .info ("Starting proxy using command: " + String .join (" " , command ));
202+ private void handleHealthCheckFailure () {
203+ LOGGER .error ("MitmProxy might not started properly, healthcheck failed for port: " + this .proxyPort );
204+ if (startedProcess == null ) return ;
205+
206+ if (startedProcess .getProcess ().isAlive ()) {
207+ LOGGER .error ("MitmProxy's healthcheck failed but process is alive, killing mitmproxy process..." );
208+ startedProcess .getProcess ().destroyForcibly ();
209+ try {
210+ Awaitility .await ()
211+ .atMost (5 , TimeUnit .SECONDS )
212+ .until (() -> !startedProcess .getProcess ().isAlive ());
213+ LOGGER .info ("MitmProxy process was killed successfully." );
214+ } catch (ConditionTimeoutException ex2 ) {
215+ LOGGER .error ("Didn't manage to kill MitmProxy in time, throwing error" );
216+ throw new RuntimeException ("Couldn't kill mitmproxy in time" , ex2 );
217+ }
218+ }
219+ if (!startedProcess .getProcess ().isAlive () && startedProcess .getProcess ().exitValue () > 0 ) {
220+ Throwable cause = null ;
221+ if (proxyLog .toString ().contains ("Address already in use" )) {
222+ cause = new BindException ();
223+ }
224+ throw new RuntimeException (
225+ "Couldn't start mitmproxy process on port: " + this .proxyPort +
226+ ", exit with code: " + startedProcess .getProcess ().exitValue (), cause );
227+ }
228+ }
175229
230+ private ProcessExecutor createProcessExecutor (List <String > command ) {
176231 String logPrefix = "MitmProxy[" + this .proxyPort + "]: " ;
177- ProcessExecutor processExecutor = new ProcessExecutor (command )
232+
233+ return new ProcessExecutor (command )
178234 .readOutput (true )
179235 .destroyOnExit ()
180236 .redirectOutput (Slf4jStream .ofCaller ().asInfo ())
@@ -185,26 +241,26 @@ protected void processLine(String line) {
185241 proxyLog .append (line ).append ("\n " );
186242 }
187243 });
244+ }
188245
189- try {
190- startedProcess = processExecutor .start ();
191- } catch (Exception ex ) {
192- throw new RuntimeException ("Couldn't start mitmproxy process" , ex );
193- }
246+ private void updateCommandWithAddOns (List <AbstractAddon > addons , List <String > command ) {
247+ addons .forEach (addon -> command .addAll (Arrays .asList (addon .getCommandParams ())));
248+ }
194249
195- try {
196- Awaitility .await ()
197- .atMost (5 , TimeUnit .SECONDS )
198- .until (this .proxyManager ::callHealthCheck );
199- } catch (ConditionTimeoutException ex ) {
200- LOGGER .error ("MitmProxy might not started properly, healthcheck failed for port: " + this .proxyPort );
201- if (startedProcess != null && startedProcess .getProcess ().exitValue () > 0 ) {
202- Throwable cause = null ;
203- if (proxyLog .toString ().contains ("Address already in use" )) {
204- cause = new java .net .BindException ();
205- }
206- throw new RuntimeException ("Couldn't start mitmproxy process on port: " + this .proxyPort + ", exit with code: " + startedProcess .getProcess ().exitValue (), cause );
250+ private void updateCommandWithLogLevel (List <String > command ) {
251+ command .add ("--set" );
252+ command .add ("termlog_verbosity=" + getMitmProxyLoggingLevel ());
253+ }
254+
255+ private void updateCommandWithUpstreamProxy (List <String > command ) {
256+ InetSocketAddress upstreamProxyAddress = proxyManager .getUpstreamProxyAddress ();
257+ if (upstreamProxyAddress != null ) {
258+ String schema = "http" ;
259+ if (proxyManager .isUseHttpsUpstreamProxy ()) {
260+ schema = "https" ;
207261 }
262+ command .add ("--mode" );
263+ command .add ("upstream:" + schema + "://" + upstreamProxyAddress .getHostName () + ":" + upstreamProxyAddress .getPort ());
208264 }
209265 }
210266
0 commit comments