How to specify server IP in HttpWebRequest

I was writing an HTTP proxy during last weeks. I need to address the origin server with a custom IP address, but I should keep the "Host" header.

I googled about "HttpWebRequest set server IP", "WebRequest modify Host", etc. I read through several posts, and realized:

  • When HttpWebRequest is created, the "Host" header is automatically set to the "host:port" portion of the Uri.
  • Attempt to modify HttpWebRequest.Headers["Host"] will throw an exception.
  • The server IP address of the HTTP Request is determined by the host portion of the Uri, and is automatically resolved against the DNS server.
  • When HttpWebRequest.Proxy is set to use a proxy, the server IP address is determined by resolving the host of Proxy property. I can set the origin server as the proxy, this is called "Proxy hack".
  • When using a proxy, the HTTP Request-Line looks like "GET http://host/ HTTP/1.1" rather than "GET / HTTP/1.1". Every HTTP/1.1 compliant origin server should accept this, but firewalls may think it's a "proxy request" and block it, and some non-compliant servers does not accept it.

SOLUTION

My purpose is: make HttpWebRequest send its request to a custom IP address, but leave the Request-Line as "GET / HTTP/1.1".

After hours of Reflector, I found the solution:

HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://blogs.msdn.com");//url and Host header
FieldInfo field_ServicePoint_ProxyServicePoint = (typeof(ServicePoint))
    .GetField("m_ProxyServicePoint", BindingFlags.NonPublic | BindingFlags.Instance);
req.Proxy = new WebProxy("www.google.com:80");//server IP and port
field_ServicePoint_ProxyServicePoint.SetValue(req.ServicePoint, false);
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();

When the above code is executed, I can see the following request in Wireshark:

GET / HTTP/1.1
Host: blogs.msdn.com
Connection: Keep-Alive

And, the request is sent to 64.233.189.99:80, a www.google.com server.

DOWNLOAD: C# code & Wireshark capture

BEHIND THE SCENES

ServicePoint class provides connection management for HTTP connections. A ServicePoint instance is created for each "host:port" combination, and reused across several requests. ServicePoint.m_ProxyServicePoint determines whether this ServicePoint connects to a proxy. When m_ProxyServicePoint is set to false, HttpWebRequest don't think it's a proxy, so the Request-Line becomes "GET / HTTP/1.1". However, m_ProxyServicePoint is a private property of ServicePoint, so I have to use Reflection to change it.

HttpWebRequest.Host in .Net Framework 4.0

2010-05-31 update: As of .Net Framework 4.0, you can use HttpWebRequest.Host property to set Host header independent from the request URI.

HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://www.google.com");//url path and actual server
req.Host = "blogs.msdn.com");//Host header
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();