How to use Proxyman with Flutter

⋅ 4 min read ⋅ Flutter Debugging

Table of Contents

Proxyman is a great tool for developers and QA to inspect and manipulate HTTP requests/responses.

Too bad Flutter doesn't use the system-level proxy, which is the underlying mechanic for most network inspectors like Proxyman to work. So, if you use Proxyman, you might not see any traffic from your Flutter Project.

The good news is there is a workaround for this issue by manually configuring Flutter’s HTTP client to use Proxyman as its proxy. Proxyman mentioned this problem and solution on their website here.

Problem

There is one problem with the solution mentioned on the website. We need to know the IP up front at compile time.

Dart HTTPClient Class

// Make sure to replace <YOUR_LOCAL_IP> with 
// the external IP of your computer if you're using Android.
// You can get the IP in the Android Setup Guide window
String proxy = Platform.isAndroid ? '<YOUR_LOCAL_IP>:9090' : 'localhost:9090';

// Create a new HttpClient instance.
HttpClient httpClient = new HttpClient();

// Hook into the findProxy callback to set
// the client's proxy.
httpClient.findProxy = (uri) {
return "PROXY $proxy;";
};

// This is a workaround to allow Proxyman to receive
// SSL payloads when your app is running on Android
httpClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => Platform.isAndroid);

HTTP Package

// Make sure to replace <YOUR_LOCAL_IP> with 
// the external IP of your computer if you're using Android.
// You can get the IP in the Android Setup Guide window
String proxy = Platform.isAndroid ? '<YOUR_LOCAL_IP>:9090' : 'localhost:9090';

// Create a new HttpClient instance.
HttpClient httpClient = new HttpClient();

// Hook into the findProxy callback to set
// the client's proxy.
httpClient.findProxy = (uri) {
return "PROXY $proxy;";
};

// This is a workaround to allow Proxyman to receive
// SSL payloads when your app is running on Android.
httpClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => Platform.isAndroid);

// Pass your newly instantiated HttpClient to http.IOClient.
IOClient myClient = IOClient(httpClient);

// Make your request as normal.
var response = myClient.get('/my-url');

Dio

// Make sure to replace <YOUR_LOCAL_IP> with 
// the external IP of your computer if you're using Android.
// You can get the IP in the Android Setup Guide window
String proxy = Platform.isAndroid ? '<YOUR_LOCAL_IP>:9090' : 'localhost:9090';

// Create a new Dio instance.
Dio dio = Dio();

// Tap into the onHttpClientCreate callback
// to configure the proxy just as we did earlier.
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
// Hook into the findProxy callback to set the client's proxy.
client.findProxy = (url) {
return 'PROXY $proxy'?;
};

// This is a workaround to allow Proxyman to receive
// SSL payloads when your app is running on Android.
client.badCertificateCallback = (X509Certificate cert, String host, int port) => Platform.isAndroid;
}

This might not be a problem for developers, but if you want to release an app for QA or testers, we can't possibly know the IP of their network.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Solution

There might be many ways to solve this, but in my case, I get help from https://github.com/kaivean/system_proxy.

system_proxy is a Flutter Plugin to get a system proxy setting. We will use this to grab a system proxy instead of hard-coded it.

I will show you how I use system_proxy with Dio.

Let's say we have a simple ApiClient class that holds an instance of Dio.

class ApiClient {
late Dio dioClient;

ApiClient({required String baseUrl}) {
var options = BaseOptions(
baseUrl: baseUrl,
);

dioClient = Dio(options);
}
}

ApiClient(baseUrl: 'https://www.example.com/api');

We can get a system proxy and configure it to dioClient like this.

class ApiClient {
late Dio dioClient;

// 1
ApiClient({required String baseUrl, Map<String, String>? proxy}) {
var options = BaseOptions(
baseUrl: baseUrl,
);

dioClient = Dio(options);

if (proxy != null) {
// 2
final proxyString = '${proxy['host']}:${proxy['port']}';

(dioClient.httpClientAdapter as DefaultHttpClientAdapter)
.onHttpClientCreate = (client) {
client.findProxy = (url) {
return 'PROXY $proxyString';
};

client.badCertificateCallback =
(X509Certificate cert, String host, int port) => Platform.isAndroid;
return null;
};
}
}
}

/// get system proxy
/// Has proxy, return: {port: 8899, host: 172.24.141.93}
/// no proxy, return: null
final proxy = await SystemProxy.getProxySettings();
// 3
final apiClient = ApiClient(baseUrl: 'https://www.example.com/api', proxy: proxy);

1 Accept proxy information in the constructor.
2 If proxy is not null, use that information to populate proxy. We use this in a place where we used to hard-coded our IP.
3 We inject the proxy that we get from system_proxy. SystemProxy.getProxySettings() is an async function, so we need to await for the result.


Read more article about Flutter, Debugging, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
How to modularize existing iOS projects using Swift Package

Modular programming is a software design technique that breaks your project into a smaller maintainable module which promotes separation of concern and reusability. Let's see how easy it is to modularize an iOS app with Swift Package.

Next
Swift typealias: What is it and when to use it

A type alias declaration introduces a named alias of an existing type into your app. You can think of it as defining a nickname for an existing type. Let's learn the benefit and when to use them.

← Home