Hi all,
I'm trying to build a reverse proxy, but I'm not getting HTTP compression working using IIS 7 on Win2008.
Either using the compressed response from the remote server or using IIS's compression, I cannot make IE7 or Fiddler2 to accept the compressed content.
Here is my code:
global.asax.cs:
namespace ReverseProxy
{
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Web;
public class ReverseProxyHttpApplication : HttpApplication
{
#region Private Constants
private const int BUFFER_SIZE = 65536;
private const string StartsWithAppSettingsKey = "StartsWith";
private const string ReplaceWithAppSettingsKey = "ReplaceWith";
#endregion
private static Dictionary<string, Action<HttpWebRequest, string>> setHeaders;
private byte[] bytes = new byte[BUFFER_SIZE];
public ReverseProxyHttpApplication()
{
ReverseProxyHttpApplication.setHeaders = new Dictionary<string, Action<HttpWebRequest, string>>(StringComparer.OrdinalIgnoreCase)
{
#if DEBUG
{"Accept-Encoding", (httpWebRequest, value) => { }},
#endif
{"Accept", (httpWebRequest, value) => httpWebRequest.Accept = value},
{"Connection", null},
{"Content-Type", (httpWebRequest, value) => httpWebRequest.ContentType = value},
{"Content-Length", null},
{"Expect", null},
{"Date", null},
{"Host", null},
{"Referer", (httpWebRequest, value) => httpWebRequest.Referer = value},
{"TransferEncoding", (httpWebRequest, value) =>
{
httpWebRequest.SendChunked = true;
httpWebRequest.TransferEncoding = value;
}},
{"User-Agent", (httpWebRequest, value) => httpWebRequest.UserAgent = value}
};
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
string localPath = null;
string remotePath;
HttpContext context = this.Context;
try
{
HttpRequest request = this.Request;
HttpResponse response = this.Response;
localPath = request.AppRelativeCurrentExecutionFilePath;
remotePath = GetRemotePath(localPath);
if (string.IsNullOrEmpty(remotePath))
{
return;
}
HttpWebRequest httpWebRequest = SendRemoteRequest(remotePath, request);
GetRemoteResponse(response, httpWebRequest);
response.Flush();
}
finally
{
this.CompleteRequest();
}
}
private static string GetRemotePath(string localPath)
{
string remotePath;
string startsWith = System.Web.Configuration.WebConfigurationManager.AppSettings[StartsWithAppSettingsKey];
if (!localPath.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase))
{
return null;
}
remotePath = System.Web.Configuration.WebConfigurationManager.AppSettings[ReplaceWithAppSettingsKey] + localPath.Substring(startsWith.Length);
return remotePath;
}
private HttpWebRequest SendRemoteRequest(string remotePath, HttpRequest request)
{
HttpWebRequest httpWebRequest;
Uri remoteUri = new Uri(remotePath);
httpWebRequest = (HttpWebRequest)(WebRequest.Create(remoteUri));
httpWebRequest.Method = request.HttpMethod;
SetRequestHeaders(httpWebRequest, request.Headers);
SetRequestCookies(remoteUri, httpWebRequest, request.Cookies);
if (request.ContentLength > 0)
{
using (Stream requestStream = httpWebRequest.GetRequestStream())
{
this.CopyStream(request.InputStream, requestStream);
}
}
return httpWebRequest;
}
private void SetRequestHeaders(HttpWebRequest httpWebRequest, NameValueCollection requestHeaders)
{
WebHeaderCollection remoteRequestHeaders = httpWebRequest.Headers;
foreach (string header in requestHeaders)
{
string value = requestHeaders[header];
Action<HttpWebRequest, string> action;
if (ReverseProxyHttpApplication.setHeaders.TryGetValue(header, out action))
{
if (action != null)
{
action(httpWebRequest, value);
}
}
else
{
remoteRequestHeaders.Add(header, value);
}
}
}
private static void SetRequestCookies(Uri remoteUri, HttpWebRequest httpWebRequest, HttpCookieCollection requestCookies)
{
if (requestCookies.Count > 0)
{
httpWebRequest.CookieContainer = new CookieContainer();
foreach (string cookieName in requestCookies)
{
HttpCookie httpCookie = requestCookies[cookieName];
httpWebRequest.CookieContainer.Add(new Cookie
{
Name = cookieName,
Path = httpCookie.Path,
Domain = (string.IsNullOrEmpty(httpCookie.Domain)) ? remoteUri.Authority : httpCookie.Domain,
HttpOnly = httpCookie.HttpOnly,
Expires = httpCookie.Expires,
Secure = httpCookie.Secure
});
}
}
}
private void GetRemoteResponse(HttpResponse response, HttpWebRequest httpWebRequest)
{
using (HttpWebResponse httpWebResponse = (HttpWebResponse)(httpWebRequest.GetResponse()))
{
if (httpWebRequest.HaveResponse)
{
using (Stream remoteResponseStream = httpWebResponse.GetResponseStream())
{
this.CopyStream(remoteResponseStream, response.OutputStream);
}
SetResponseHeaders(response, httpWebResponse.Headers);
SetResponseCookies(response, httpWebResponse.Cookies);
}
}
}
private static void SetResponseHeaders(HttpResponse response, WebHeaderCollection remoteResponseHeaders)
{
response.ClearHeaders();
foreach (string header in remoteResponseHeaders)
{
if ("Server".Equals(header, StringComparison.OrdinalIgnoreCase))
{
continue;
}
response.AppendHeader(header, remoteResponseHeaders[header]);
}
}
private static void SetResponseCookies(HttpResponse response, CookieCollection responseCookies)
{
response.Cookies.Clear();
foreach (Cookie cookie in responseCookies)
{
response.Cookies.Add(new HttpCookie(cookie.Name, cookie.Value)
{
Path = cookie.Path,
Domain = cookie.Domain,
HttpOnly = cookie.HttpOnly,
Expires = cookie.Expires,
Secure = cookie.Secure
});
}
}
private void CopyStream(Stream from, Stream to)
{
int read;
while ((read = from.Read(this.bytes, 0, BUFFER_SIZE)) > 0)
{
to.Write(this.bytes, 0, read);
}
}
}
}
web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.diagnostics>
<assert assertuienabled="false" />
<sources>
<source name="ReverseProxy" switchValue="Verbose">
</source>
</sources>
</system.diagnostics>
<appSettings>
<clear />
<add key="StartsWith" value="~/" />
<add key="ReplaceWith" value="http://msdn.microsoft.com/" />
</appSettings>
<system.web>
<authentication mode="None" />
<httpHandlers>
<clear />
</httpHandlers>
<httpModules>
<clear />
</httpModules>
<compilation debug="true" />
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<clear />
<add name="dummy" path="*" verb="*" type="System.Web.HttpForbiddenHandler, System.Web" />
</handlers>
<modules>
</modules>
</system.webServer>
</configuration>