.NET Core, HttpClient, Proxy and localhost

Recently I wanted to spy network traffic between a HttpClient and a REST service. This task turned out to be more difficult than I though.

My first challenge was to force HttpClient to use a proxy. After a bit of googling I have found the following code:

using System.Net;
using System.Net.Http;

var builder = new ConfigurationBuilder()
	 .SetBasePath(Directory.GetCurrentDirectory())
	 .AddJsonFile("appsettings.json");
var configuration = builder.Build();

var webProxy = new WebProxy(
	 new Uri(configuration["ProxyUri"]), 
	 BypassOnLocal: false);

var proxyHttpClientHandler = new HttpClientHandler {
	 Proxy = webProxy,
	 UseProxy = true,
};

var httpClient = new HttpClient(proxyHttpClientHandler) {
	 BaseAddress = new Uri(configuration["RestServiceUri"])
};

Unfortunately my REST service was exposed on localhost and later I found out that proxies are not used for local requests:

/* appsettings.json */
{
    "RestServiceUri": "http://localhost:5001/api",
    "ProxyUri": "http://localhost:8080"
}

OK, no problem I though, let’s just add another DNS alias to localhost - just to fool Uri class to think that we are accessing some other machine. This can be done by modifying hosts file, which on my Ubuntu machine is located in /etc directory:

127.0.0.1   localhost
127.0.0.2   mymachine

Then I had to change my appsettings.json file:

/* appsettings.json */
{
    "RestServiceUri": "http://mymachine:5001/api",
    "ProxyUri": "http://localhost:8080"
}

And my REST service configuration so that it will listen for incoming connections on all interfaces:

public static IWebHost BuildWebHost(string[] args) =>
	WebHost.CreateDefaultBuilder(args)
		 .UseStartup<Startup>()
		 .UseUrls("http://0.0.0.0:5001")
		 .Build();

After all these preparations I was able to intercept traffic using ZAP Proxy: Intercepted traffix

But was this all necessary? Turns out that not really. You may use your vanilla HttpClient:

var restServiceUri = new Uri(configuration["RestServiceUri"]);
var httpClient = new HttpClient() {
    BaseAddress = restServiceUri
};

And then just set http_proxy environmental variable to get exactly the same behaviour (without any need to modify hosts, or to force REST service to listen on all interfaces). Just run in Bash:

http_proxy=http://localhost:8080 dotnet run

Since I don’t have any machine with Windows/MacOS I cannot confirm that it works on all OS’es, but at least it works on my Ubuntu.

marcin-chwedczuk

A Programmer, A Geek, A Human