mwrock / RequestReduce

Instantly makes your .net website faster by reducing the number and size of requests with almost no effort.
www.requestreduce.org
Apache License 2.0
228 stars 49 forks source link

Solved: Can't access resources when client uses external NAT address #208

Closed orand closed 12 years ago

orand commented 12 years ago

One of our customers installed our web app behind a NAT and configured routing so that the server couldn't access its external IP address. The external address is used in the URL sent by clients in the HTTP request header, and RequestReduce uses this URL to turn relative paths into absolute paths. This resulted in errors like the following:

System.ApplicationException: There were errors reducing https://nananananatman/MyApp/Scripts/jquery-1.7.1.js:: ---> System.InvalidOperationException: RequestReduce had problems accessing https://nananananatman/MyApp/Scripts/jquery-1.7.1.js.
The remote name could not be resolved: 'nananananatman' ---> System.Net.WebException: The remote name could not be resolved: 'nananananatman'

Or the following:

System.InvalidOperationException: RequestReduce had problems accessing https://10.226.1.23/MyApp/Scripts/jquery-1.7.1.js.
The underlying connection was closed: An unexpected error occurred on a send. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Authentication failed because the remote party has closed the transport stream.

The solution was to put the following in Global.asax.cs:

private static bool _rrInitialized;
private static object _rrLock = new object();

private void Application_BeginRequest(object sender, EventArgs eventArgs)
{
    if (!_rrInitialized)
    {
        lock (_rrLock)
        {
            if (!_rrInitialized)
            {
                var appPath = Context.Request.ApplicationPath;
                if (!appPath.EndsWith("/"))
                {
                    appPath += "/";
                }
                var localAddress = Context.Request.ServerVariables["LOCAL_ADDR"];

                // only change the BaseAddress if we have an IPV4 address,
                // otherwise fall back to RR's default behavior of using the base address specified by the client.
                IPAddress ipAddress;
                if(IPAddress.TryParse(localAddress, out ipAddress) && ipAddress.AddressFamily == AddressFamily.InterNetwork)
                {
                    var localPort = Request.ServerVariables["SERVER_PORT"];
                    string protocol = Request.ServerVariables["SERVER_PORT_SECURE"] == "1" ? "https" : "http";

                    string baseAddress = string.Format("{0}://{1}:{2}{3}", protocol, localAddress, localPort, appPath);

                    // make RequestReduce use the local base address instead of the absolute URL passed in the HTTP header by the client
                    // because the client's base address could be an external NAT address that the server can't reach.
                    RequestReduce.Api.Registry.Configuration.BaseAddress = baseAddress;

                }

                _rrInitialized = true;
            }
        }
    }
}

However, this then pushed the problem to the clients because RequestReduce was replacing relative URLs in CSS files with absolute URLs, using the address from inside the NAT that is unreachable outside the NAT. The solution was to make the minifier replace absolute URLs with relative URLs.

WARNING: the following code assumes that all your URLs were relative to begin with. If not, you will need to modify the code to preserve the absolute URLs you'd like to keep.

public class AjaxMinifier : Minifier
{
    // *? is the lazy match quantifier which means "match as few times as possible"
    Regex _regex = new Regex(@"http[s]?://.*?/", RegexOptions.Multiline | RegexOptions.Compiled);

    public override string Minify<T>(string unMinifiedContent)
    {
        string minified = base.Minify<T>(unMinifiedContent);
        string relativeUrls = _regex.Replace(minified, @"/");
        return relativeUrls;
    }
}

And then add the following to Application_Start in Global.asax.cs:

RequestReduce.Api.Registry.RegisterMinifier<AjaxMinifier>();

I hope this helps if anyone else runs into the same issue.