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