PageRenderTime 18ms CodeModel.GetById 2ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/BlogEngine/DotNetSlave.BusinessLogic/Web/HttpHandlers/JavaScriptHandler.cs

#
C# | 269 lines | 153 code | 45 blank | 71 comment | 16 complexity | 46103e0d600cdc88386e99030001c628 MD5 | raw file
  1namespace BlogEngine.Core.Web.HttpHandlers
  2{
  3    using System;
  4    using System.Collections.Generic;
  5    using System.Configuration;
  6    using System.IO;
  7    using System.Linq;
  8    using System.Net;
  9    using System.Net.Sockets;
 10    using System.Security;
 11    using System.Text;
 12    using System.Text.RegularExpressions;
 13    using System.Web;
 14    using System.Web.Caching;
 15
 16    using BlogEngine.Core.Web.HttpModules;
 17
 18    /// <summary>
 19    /// Removes whitespace in all stylesheets added to the 
 20    ///     header of the HTML document in site.master.
 21    /// </summary>
 22    /// <remarks>
 23    /// 
 24    /// This handler uses an external library to perform minification of scripts. 
 25    /// See the BlogEngine.Core.JavascriptMinifier class for more details.
 26    /// 
 27    /// </remarks>
 28    public class JavaScriptHandler : IHttpHandler
 29    {
 30        #region Properties
 31
 32        /// <summary>
 33        ///     Gets a value indicating whether another request can use the <see cref = "T:System.Web.IHttpHandler"></see> instance.
 34        /// </summary>
 35        /// <value></value>
 36        /// <returns>true if the <see cref = "T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>
 37        public bool IsReusable
 38        {
 39            get
 40            {
 41                return false;
 42            }
 43        }
 44
 45        #endregion
 46
 47        #region Implemented Interfaces
 48
 49        #region IHttpHandler
 50
 51        /// <summary>
 52        /// Enables processing of HTTP Web requests by a custom 
 53        ///     HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
 54        /// </summary>
 55        /// <param name="context">
 56        /// An <see cref="T:System.Web.HttpContext"></see> object that provides 
 57        ///     references to the intrinsic server objects 
 58        ///     (for example, Request, Response, Session, and Server) used to service HTTP requests.
 59        /// </param>
 60        public void ProcessRequest(HttpContext context)
 61        {
 62            var request = context.Request;
 63            var path = request.QueryString["path"];
 64
 65            if (string.IsNullOrEmpty(path))
 66            {
 67                return;
 68            }
 69
 70            string rawUrl = request.RawUrl.Trim();
 71            string cacheKey = context.Server.HtmlDecode(rawUrl);
 72            string script = (string)Blog.CurrentInstance.Cache[cacheKey];
 73            bool minify = ((request.QueryString["minify"] != null) || (BlogSettings.Instance.CompressWebResource && cacheKey.Contains("WebResource.axd")));
 74
 75
 76            if (String.IsNullOrEmpty(script))
 77            {
 78                if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
 79                {
 80                    script = RetrieveRemoteScript(path, cacheKey, minify);
 81                }
 82                else
 83                {
 84                    script = RetrieveLocalScript(path, cacheKey, minify);
 85                }
 86            }
 87
 88
 89            if (string.IsNullOrEmpty(script))
 90            {
 91                return;
 92            }
 93
 94            SetHeaders(script.GetHashCode(), context);
 95            context.Response.Write(script);
 96
 97            if (BlogSettings.Instance.EnableHttpCompression)
 98            {
 99                CompressionModule.CompressResponse(context); // Compress(context);
100            }
101        }
102
103        #endregion
104
105        #endregion
106
107        #region Methods
108
109        /// <summary>
110        /// Checks whether to hard minify output.
111        /// </summary>
112        /// <param name="file">The file name.</param>
113        /// <returns>Whether to hard minify output.</returns>
114        private static bool HardMinify(string file)
115        {
116            var lookfor = ConfigurationManager.AppSettings.Get("BlogEngine.HardMinify").Split(
117                new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
118            return lookfor.Any(file.Contains);
119        }
120
121        /// <summary>
122        /// Call this method for any extra processing that needs to be done on a script resource before
123        /// being written to the response.
124        /// </summary>
125        /// <param name="script"></param>
126        /// <param name="filePath"></param>
127        /// <param name="shouldMinify"></param>
128        /// <returns></returns>
129        private static string ProcessScript(string script, string filePath, bool shouldMinify)
130        {
131            // The HardMinify call is for backwards compatibility. It's really not needed anymore.
132            if ((shouldMinify) || HardMinify(filePath))
133            {
134                var min = new JavascriptMinifier();
135                min.VariableMinification = VariableMinification.LocalVariablesOnly;
136
137                return min.Minify(script);
138            }
139            else
140            {
141                return script;
142            }
143
144        }
145
146        /// <summary>
147        /// Retrieves the local script from the disk
148        /// </summary>
149        /// <param name="file">
150        /// The file name.
151        /// </param>
152        /// <param name="cacheKey">The key used to insert this script into the cache.</param>
153        /// <param name="minify">Whether or not the local script should be minfied</param>
154        /// <returns>
155        /// The retrieve local script.
156        /// </returns>
157        private static string RetrieveLocalScript(string file, string cacheKey, bool minify)
158        {
159
160            if (StringComparer.OrdinalIgnoreCase.Compare(Path.GetExtension(file), ".js") != 0)
161            {
162                throw new SecurityException("No access");
163            }
164
165            try
166            {
167                var path = HttpContext.Current.Server.MapPath(file);
168                if (File.Exists(path))
169                {
170                    string script;
171                    using (var reader = new StreamReader(path))
172                    {
173                        script = reader.ReadToEnd();
174                    }
175
176                    script = ProcessScript(script, file, minify);
177                    Blog.CurrentInstance.Cache.Insert(cacheKey, script, new CacheDependency(path));
178                    return script;
179                }
180            }
181            catch (Exception ex)
182            { 
183            }
184
185            return string.Empty;
186        }
187
188        /// <summary>
189        /// Retrieves and cached the specified remote script.
190        /// </summary>
191        /// <param name="file">
192        /// The remote URL
193        /// </param>
194        /// <param name="cacheKey">The key used to insert this script into the cache.</param>
195        /// <param name="minify">Whether or not the remote script should be minified</param>
196        /// <returns>
197        /// The retrieve remote script.
198        /// </returns>
199        private static string RetrieveRemoteScript(string file, string cacheKey, bool minify)
200        {
201
202            Uri url;
203
204            if (Uri.TryCreate(file, UriKind.Absolute, out url))
205            {
206                try
207                {
208
209                    var remoteFile = new RemoteFile(url, false);
210                    string script = remoteFile.GetFileAsString();
211                    script = ProcessScript(script, file, minify);
212                    Blog.CurrentInstance.Cache.Insert(cacheKey, script, null, Cache.NoAbsoluteExpiration, new TimeSpan(3, 0, 0, 0));
213                    return script;
214                }
215                catch (SocketException)
216                {
217                    // The remote site is currently down. Try again next time.
218                }
219            }
220
221            return String.Empty;
222        }
223
224
225
226        /// <summary>
227        /// This will make the browser and server keep the output
228        ///     in its cache and thereby improve performance.
229        /// </summary>
230        /// <param name="hash">
231        /// The hash number.
232        /// </param>
233        /// <param name="context">
234        /// The context.
235        /// </param>
236        private static void SetHeaders(int hash, HttpContext context)
237        {
238
239            var response = context.Response;
240
241            response.ContentType = "text/javascript";
242
243            var cache = response.Cache;
244
245            cache.VaryByHeaders["Accept-Encoding"] = true;
246            cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(7));
247            cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
248            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
249
250            var etag = string.Format("\"{0}\"", hash);
251            var incomingEtag = context.Request.Headers["If-None-Match"];
252
253            cache.SetETag(etag);
254            cache.SetCacheability(HttpCacheability.Public);
255
256            if (String.Compare(incomingEtag, etag) != 0)
257            {
258                return;
259            }
260
261            response.Clear();
262            response.StatusCode = (int)HttpStatusCode.NotModified;
263            response.SuppressContent = true;
264        }
265
266        #endregion
267
268    }
269}