Why scriptLocation may not work when pointing to a proxy script file in your application config file.

Consider this scenario: You have a .NET application that issues HttpWebRequests to a remote server.  You deploy this application to an environment that requires proxy servers be configured using a proxy script file (WPAD / PAC).  The solution is pretty simple.  In your app.config, you can add this configuration:

  <system.net>

    <defaultProxy useDefaultCredentials="true">

      <proxy usesystemdefault="False" scriptLocation="https://<IIS Server or IP Address>/wpad.dat" />

    </defaultProxy>

  </system.net>

This, of course, assumes your script file is named wpad.dat and is hosted on your IIS server.  Adjust this value as appropriate for your environment.

Arriving at this simple configuration recently proved to be a very challenging issue for me.  When I first tried this, my configuration looked like this:

  <system.net>

    <defaultProxy useDefaultCredentials="true">

      <proxy usesystemdefault="False" scriptLocation="https://<IIS Server or IP Address>/wpad.dat" bypassonlocal="True" />

    </defaultProxy>

  </system.net>

Notice, the only difference is the additional assignment of the bypassonlocal attribute.  Which, at least in my experience, is a pretty common thing to do.  But, in this case, it didn’t work!  Why?  If you like debugging, read on….

I captured a netmon trace and what I found was that my application was trying to establish a direct TCP connection to the target URL rather than establishing a connection with the proxy server that my proxy script would return.  For example,

image

In a working scenario, you would expect to see something like this in a netmon trace.  Here, we establish a TCP connection with the proxy server, then issue an HTTP CONNECT on that connection, requesting a connection to the target URL, and finally setup SSL for secure transport.

image

Going back to my failing scenario, I attached a debugger to my client and saw this exception – validating what I saw in the netmon trace.

image

So, why is this happening?  I decided to look at the actual instance of the WebProxy that gets created when you configure .NET to use a proxy server and found a suspicious value for a ScriptEngine member.

image

Yes, there should be a value there.  The ScriptEngine is what compiles and runs my proxy script identified in the scriptLocation property above.  If there is not a ScriptEngine, then there can be no proxy.  If there is no proxy, then we try to connect directly to the target url, which is exactly what we see in the netmon trace above.  This is why we were not successful when trying to use the proxy script.  So, now the question becomes, why is the ScriptEngine null?

If you think about it for a while, it starts to make some sense.  A proxy script has a single function that has to be implemented called FindProxyUrl.  This function takes two parameters, one of which is the target url, <host>, that you ultimately want to connect to.  The implementation details for this function can obviously vary from one environment to the next.   But, whether or not to connect directly to the target url or go through one of the proxy servers that the script is programmed to return is ultimately a decision made in the script.  So, it is somewhat contradicting if you tell .NET to use a proxy script through the scriptLocation property and also set the bypassonlocal property. 

After some collaboration with one of our Networking Escalation Engineers, it was pointed out to me that .NET will delete the ScriptEngine if it gets an instruction to set the bypassonlocal property.  This happens whether you set bypassonlocal to true or false.  Just the act of setting bypassonlocal results in the WebProxy deleting the ScriptEngine.

So, as I showed above, don’t use bypassonlocal if you are also specifying the scriptLocation.

If you want to learn more about how proxy scripts actually work, there is a nice article here.  It is dated, but still a very good read.