19 November 2007

Creating a simple proxy

Sometimes you want to give web browsers access to web resources that are best not opened indiscriminatly to the world. A good example are WMS servers: you want the users to be able to see maps, but putting a WMS server directly online is like putting a read-only sql client online. The solution is put the web resource on a server that is not accessible by the internet, but is accessible by the web application - i.e. the limited resource is put inside the DMZ, and create some kind of proxy page that streams data from the server inside the DMZ to the outside world - after doing some validations, of course. A setup for a proxy page (that only does the streaming, not the validation) may be as follows: Create an aspx page MyProxyPage.aspx and add the following code:
void ProcessProxyRequest( string request, HttpResponse response)
 {
   HttpWebRequest webRequest = HttpWebRequest.Create(request) as HttpWebRequest;
 
   // Important! Keeps the request from blocking after the first time!
   webRequest.KeepAlive = false;
   webRequest.Credentials = CredentialCache.DefaultCredentials;
   using (HttpWebResponse backendResponse = (HttpWebResponse)webRequest.GetResponse())
   {
     Stream receiveStream = backendResponse.GetResponseStream();
 
     // Clear whatever is already sent
     response.Clear();
     response.ClearContent();
     response.ClearHeaders();
 
     // Copy headers
     // Check if header contains a contenth-lenght since IE
     // goes bananas if this is missing
     bool contentLenghtFound = false;
     foreach (string header in backendResponse.Headers)
     {
       if (string.Compare(header, "CONTENT-LENGTH", true) == 0) 
       { 
         contentLenghtFound = true;
       }
       response.AppendHeader(header, backendResponse.Headers[header]);
     }
     // Copy content           
     byte[] buff = new byte[1024];
     long length = 0;
     int bytes;
     while ((bytes = receiveStream.Read(buff, 0, 1024)) > 0)
     {
       length += bytes;
       response.OutputStream.Write(buff, 0, bytes);
     }
 
     // Manually add content-lenght header to satisfy IE
     if (!contentLenghtFound) 
     {
       response.AppendHeader("Content-Length", 
         length.ToString());
     }
 
     receiveStream.Close();
     backendResponse.Close();
     response.Flush();
     response.Close();
   }
 }
Now of course, in the Page_Load you have to do something first:
protected void Page_Load(object sender, EventArgs e)
{
   string requestInsideDMZ = "http://somewhereinyourdmz/somepage.aspx";
   ProcessProxyRequest( requestInsideDMZ, 
                        Response);
}
If the user accesses MyProxyPage.aspx he gets the contents of http://somewhereinyourdmz/somepage.aspx instead.

1 comment:

Unknown said...

Hi,

I am using your code and it's working very well!

I had only one little bug though that I fixed. If you want to get a page that encodes in chunks the response, you will have a problem because the headers will say "encoded" and the content won't be.

So here is the little trick:


foreach (string header in backendResponse.Headers)
{
if (string.Compare(header, "Content-Length", true) == 0)
{
contentLenghtFound = true;
}
if (string.Compare(header, "Transfer-Encoding", true) != 0)
{
response.AppendHeader(header, backendResponse.Headers[header]);
}
}

Hope it helps someone :)