How do I make HTTP requests using proxy settings?

JosephDowlandJosephDowland USMember ✭✭

On the face of it this may seem to be a simple question with a simple answer, but the past couple of weeks have proven that to not be the case.

First of all I'm working on a Xamarin.Forms application, using Xamarin.Forms 2.3.3.193 (I can't upgrade as .Net Framework 4.7+ is not compatible with my version of Windows 10 Enterprise edition)

The general workflow when my app starts is:
1) Ping the server
2) If the server does not respond, push a modal navigation page onto the stack

I have a proxy set up on my network which I can turn on and off at a whim (it's FreeProxy)
On my devices I configure the wifi connection to go through that proxy. When the proxy is off, all network traffic should result in an error (probably a 404 error). When on, all traffic should go through. Similarly, when the devices don't have a proxy configured, the proxy should be irrelevant.

On Android it was frustrating, but I finally managed to get it working by asking the device for the proxy settings, and if they exist, implementing them into the HttpClientHandler configuration:

public HttpClient GetClient()
    {
        var handler = new HttpClientHandler();
        string host = Java.Lang.JavaSystem.GetProperty("http.proxyHost");
        string port = Java.Lang.JavaSystem.GetProperty("http.proxyPort");

        if (!string.IsNullOrEmpty(host))
        {
            host = host.TrimEnd('/');
        }

        if (!string.IsNullOrEmpty(host) && !string.IsNullOrEmpty(port))
        {
            handler.Proxy = new WebProxy($"{host}:{port}", true);
            handler.UseProxy = true;
        }

        return new HttpClient(handler);
    }

On iOS, however, not only can I not get this working properly, but I'm getting some incredibly odd results.

I can get the proxy settings via

CFNetwork.GetSystemProxySettings();

but then implementing those settings into any form of request is proving to be nigh on impossible.

If I do like I do on Android, using HttpClientHandler, the proxy settings are ignored.
If I instead use ModernHttpClient, the proxy settings are ignored.
Currently I'm using NSUrlSessionHandler:

public HttpClient GetClient()
    {
        var configuration = NSUrlSessionConfiguration.DefaultSessionConfiguration;

    var settings = CFNetwork.GetSystemProxySettings();
        string host = settings.HTTPProxy;
        string port = settings.HTTPPort.ToString();

        if (!string.IsNullOrEmpty(host))
        {
            host = host.TrimEnd('/');
        }

        if (!string.IsNullOrEmpty(host) && !string.IsNullOrEmpty(port))
        {
            NSObject[] values = new NSObject[]
            {
                NSObject.FromObject(host),     //ProxyHost
                NSNumber.FromInt32 (settings.HTTPPort),     //Port
                NSNumber.FromInt32 (1),                   //Enable HTTP proxy
                NSObject.FromObject(host),     //ProxyHost
                NSNumber.FromInt32 (settings.HTTPPort),     //Port
                NSNumber.FromInt32 (1),                   //Enable HTTP proxy
            };

            NSObject[] keys = new NSObject[]
            {
                NSObject.FromObject("HTTPProxy"),
                NSObject.FromObject("HTTPPort"),
                NSObject.FromObject("HTTPEnable"),
                NSObject.FromObject("HTTPSProxy"),
                NSObject.FromObject("HTTPSPort"),
                NSObject.FromObject("HTTPSEnable")
            };

            NSDictionary proxyDict = NSDictionary.FromObjectsAndKeys(values, keys);
            configuration.ConnectionProxyDictionary = proxyDict;
        }

        var handler = new NSUrlSessionHandler(configuration);

        var client = new HttpClient(handler);
        return client;
    }

Or

public async Task<RequestResultDto> MakeRequest(RequestDto dto)
    {
        var returnDto = new RequestResultDto();

        var configuration = NSUrlSessionConfiguration.DefaultSessionConfiguration;

        var settings = CFNetwork.GetSystemProxySettings();
        string host = settings.HTTPProxy;
        string port = settings.HTTPPort.ToString();

        if (!string.IsNullOrEmpty(host))
        {
            host = host.TrimEnd('/');
        }

        if (!string.IsNullOrEmpty(host) && !string.IsNullOrEmpty(port))
        {
    NSObject[] values = new NSObject[]
            {
                NSObject.FromObject(host),     //ProxyHost
                NSNumber.FromInt32 (settings.HTTPPort),     //Port
                NSNumber.FromInt32 (1),                   //Enable HTTP proxy
                NSObject.FromObject(host),     //ProxyHost
                NSNumber.FromInt32 (settings.HTTPPort),     //Port
                NSNumber.FromInt32 (1),                   //Enable HTTP proxy
            };

            NSObject[] keys = new NSObject[]
            {
                NSObject.FromObject("HTTPProxy"),
                NSObject.FromObject("HTTPPort"),
                NSObject.FromObject("HTTPEnable"),
                NSObject.FromObject("HTTPSProxy"),
                NSObject.FromObject("HTTPSPort"),
                NSObject.FromObject("HTTPSEnable")
            };

            NSDictionary proxyDict = NSDictionary.FromObjectsAndKeys(values, keys);
            configuration.ConnectionProxyDictionary = proxyDict;
        }

        var session = NSUrlSession.FromConfiguration(configuration);

        var request = new NSUrlRequest(new NSUrl(dto.uri), NSUrlRequestCachePolicy.UseProtocolCachePolicy, dto.timeout);

        //var requestTask = await session.CreateDataTaskAsync(request);

        var requestTask = await NSUrlSession.SharedSession.CreateDataTaskAsync(request);

        if (requestTask != null)
        {
            var data = requestTask.Data;
            var response = requestTask.Response; //the response always has a 200 status code
        }

        return returnDto;
    }

but, as you might be able to guess, the proxy settings are ignored.
I also tried using WebRequest and HttpWebRequest, but those have the same problem.

Now, here's the truly bizarre part. If I try to use CFNetworkHandler, I get absolutely no traffic whether or not the device has a proxy set up. Not only that, but this breaks all threading in the application!
The aforementioned error page gets pushed onto the stack within Device.BeginInvokeOnMainThread (since the validation is itself on a different thread), but if I use CFNetworkHandler no calls to Device.BeginInvokeOnMainThread invoke any code!

Does anyone know how to ensure that HTTP requests actually respect the proxy settings that are on the device, or at least the ones I provide to it?

Answers

Sign In or Register to comment.