Skip to content

Better network debugging with Charles Proxy and iOS

Tools like Charles Proxy are great for debugging network activity. But sending all network through Charles and debugging your API calls alongside secure APIs can slow you down. With a little effort you can bypass the proxy for all network requests except the ones you choose; even from iOS devices!

Note:

I use Charles Proxy to debug network requests during iOS development. This tutorial is written from the perspective of using Charles but in theory should work with any tool that uses a proxy to capture network requests.

What’s the problem with Charles?

The first problem is that when you have Charles running, all your network traffic will be directed through it and, unless you’re using Firefox where you can ignore the system proxy, you will often see a screen like this:

This Connection Is Not Private

The Charles aficionados among you will know that you can bypass Charles by going to Proxy Settings -> Options and adding the hosts you’d like to ignore. You can list the sites that you visit frequently here completely bypass Charles. But it’s impossible to list all the sites that you are going to visit and it’s irritating to stop what you’re doing and add a new host to the list.

This brings me to the second problem which is that we cannot bypass any sites when debugging on an iOS device. Some secure APIs will not accept any requests made through a proxy. This means if our app talks to secure APIs, like for in-app purchases, we have to manually toggle the proxy settings on and off which leads to a snail-paced debugging process. Ain’t nobody got time for that!

Automatic Proxy Configuration to the rescue!

We can provide a configuration file to get a fine-grained control over what traffic goes to our proxy. Pretty cool eh?

If you’re not interested in the specifics, you can download the source code here.

Let’s take a quick look at how the automatic proxy configuration works.

Important

This configuration will only work for local traffic on your Mac. Don’t worry, we’ll add support for your iOS devices after.

First, in Charles turn off the proxy by going to Proxy Settings -> macOS and unchecking Enable macOS proxy. If you plan to use this solution in the future, also uncheck Enable macOS proxy on launch.

Next, we need to write our configuration file. All we have to do is implement a JavaScript function called FindProxyForURL. It receives a url and host and our job is to decide which requests should be sent via Charles.

Below is a simple configuration file which will bypass Charles on all requests except for those whose host matches our api host.

The following table shows the result of this proxy configuration.

URLSend to Charles?
https://api.my-app.com
http://assets.my-app.com
https://apple.com

Next, we need to host the proxy configuration file on a server. Thanks to Sinatra we can easily spin up a server and return a configuration file.

$ touch server.rb

Start the server

$ ruby server.rb

Then go to http://localhost:4567/proxy.pac and you’ll see configuration file.

Next, we need to point our network service to the configuration file which can be done in System Preferences -> Network -> Advanced or from the command line:

Now, using the iOS simulator, Charles will only receive traffic intended for your API. All other traffic will bypass Charles and is sent direct.

Time for some improvements

Now you have a good idea of how this process works, let’s make some improvements:

  1. Automate launching the server and set the proxy automatically.
  2. Allow iOS devices to get the proxy configuration.

Automate launching the server and set the proxy automatically

Start by creating a run script executable.

$ touch run; chmod +x run

  1. The target network device. If you’re not using Wi-Fi, use $ networksetup -listallnetworkservices to find your network device.
  2. This sets our proxy configuration automatically.
  3. Launch our server.
  4. Once the server has been shutdown, turn off the proxy.

Allow iOS devices to get the proxy configuration

Our run script and configuration only work with localhost at the moment. We need to replace this with a dynamic IP.

  1. Redirect traffic to your public IP address instead of localhost.

PUBLIC_IP is an environment variable that now needs to be set before running the server. Let’s do that now.

  1. Get our public IP address. (en0 is for Wi-Fi, use en1 for ethernet).
  2. Copy the URL to our configuration file to the clipboard. If your iOS device is logged in to the same iCloud account, it will be copied into your device’s clipboard.
  3. Launch the server with 0.0.0.0 IP address makes it accessible to external devices on your network.

Now you’re ready to test this on your iOS device, go to Settings -> Wi-Fi -> <your network> -> Configure Proxy -> Automatic and paste the URL to the proxy configuration file.

And you’re all done! When using your iOS device, Charles will only receive traffic intended for your API. All other traffic will bypass Charles and is sent direct.

Here’s one I made earlier

For convenience, you can download the source code here. Once downloaded replace *.my-app.com with the host(s) you’d like to match and execute the run script.

Happy coding!

Published iniOS

Be First to Comment

Leave a Reply