I am trying to get TinyMCE, gzip and caching to work correctly but are stuck with the browsers not caching requests to the gzip.ashx handler.
My setup:
This is my code (pretty standard):
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="/scripts/tinymce/tinymce.gzip.js"></script>
</head>
<body>
<script>
tinymce.init({
selector: 'textarea',
plugins: 'image link'
});
</script>
<textarea />
</body>
</html>
First load of the page:
tinymce.gzip.js
request TinyMCE Compressor tinymce.gzip.ashx
tinymce.gzip.ashx
compresses all the TinyMCE javascript files and produces a compressed file (.gz) like tinymce.gzip-C3F36E9F5715BFD1943ECF340F1AB753.gz
Subsequent load of the page:
tinymce.gzip.ashx
checks if the .gz file exists on disk and returns it to the browser My tinymce.gzip.ashx (full script at the end of the post) looks like the original but with this minor change, as the page diskcache
parameter was not passed in the querystring:
..
...
themes = GetParam("themes", "").Split(',');
diskCache = true; //GetParam("diskcache", "") == "true";
isJS = GetParam("js", "") == "true";
..
Anyways, all this works fine but the real problem occurs in the browsers caching of that .gz file. I can never get it to return a HTTP/1.1 304 Not Modified
response, so that .gz will not be requested again. All other files are 304'd.
Here is what I have tried:
I have tried to request the gzip directly like http://mysite/scripts/tinymce/tinymce.gzip-C3F36E9F5715BFD1943ECF340F1AB753.gz but I still get 200 OK
Manually setting Response.StatusCode = 304;
will just cause the response to be empty and not load tinymce.
Doing <script src="/scripts/tinymce/tinymce.gzip-C3F36E9F5715BFD1943ECF340F1AB753.gz"></script>
will return the .gz file but not load TinyMCE
I have spent five hours now on this - any help is appreciated.
Here are some screenshots from IE 11.0.9600.17498, FF 35.01 and Fiddler:
The full tinymce.gzip.ashx handler:
<%@ WebHandler Language="C#" Class="Handler" %>
/**
* tinymce.gzip.ashx
*
* Copyright, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*
* This file compresses the TinyMCE JavaScript using GZip and
* enables the browser to do two requests instead of one for each .js file.
*
* It's a good idea to use the diskcache option since it reduces the servers workload.
*/
using System;
using System.Web;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
public class Handler : IHttpHandler {
private HttpResponse Response;
private HttpRequest Request;
private HttpServerUtility Server;
public void ProcessRequest(HttpContext context) {
this.Response = context.Response;
this.Request = context.Request;
this.Server = context.Server;
this.StreamGzipContents();
}
public bool IsReusable {
get {
return false;
}
}
#region private
private void StreamGzipContents() {
string cacheKey = "", cacheFile = "", content = "", enc, suffix, cachePath;
string[] plugins, languages, themes;
bool diskCache, supportsGzip, isJS, compress, core;
int i, x, expiresOffset;
GZipStream gzipStream;
Encoding encoding = Encoding.GetEncoding("windows-1252");
byte[] buff;
// Get input
plugins = GetParam("plugins", "").Split(',');
languages = GetParam("languages", "").Split(',');
themes = GetParam("themes", "").Split(',');
diskCache = true; //GetParam("diskcache", "") == "true";
isJS = GetParam("js", "") == "true";
compress = GetParam("compress", "true") == "true";
core = GetParam("core", "true") == "true";
suffix = GetParam("suffix", "min");
cachePath = Server.MapPath("."); // Cache path, this is where the .gz files will be stored
expiresOffset = 10; // Cache for 10 days in browser cache
// Custom extra javascripts to pack
string[] custom = {/*
"some custom .js file",
"some custom .js file"
*/};
// Set response headers
Response.ContentType = "text/javascript";
Response.Charset = "UTF-8";
Response.Buffer = false;
// Setup cache
Response.Cache.SetExpires(DateTime.Now.AddDays(expiresOffset));
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(false);
// Vary by all parameters and some headers
Response.Cache.VaryByHeaders["Accept-Encoding"] = true;
Response.Cache.VaryByParams["theme"] = true;
Response.Cache.VaryByParams["language"] = true;
Response.Cache.VaryByParams["plugins"] = true;
Response.Cache.VaryByParams["lang"] = true;
Response.Cache.VaryByParams["index"] = true;
// Setup cache info
if (diskCache) {
cacheKey = GetParam("plugins", "") + GetParam("languages", "") + GetParam("themes", "");
for (i = 0; i < custom.Length; i++)
cacheKey += custom[i];
cacheKey = MD5(cacheKey);
if (compress)
cacheFile = cachePath + "/tinymce.gzip-" + cacheKey + ".gz";
else
cacheFile = cachePath + "/tinymce.gzip-" + cacheKey + ".js";
}
// Check if it supports gzip
enc = Regex.Replace("" + Request.Headers["Accept-Encoding"], @"\s+", "").ToLower();
supportsGzip = enc.IndexOf("gzip") != -1 || Request.Headers["---------------"] != null;
enc = enc.IndexOf("x-gzip") != -1 ? "x-gzip" : "gzip";
// Use cached file disk cache
if (diskCache && supportsGzip && File.Exists(cacheFile)) {
Response.AppendHeader("Content-Encoding", enc);
Response.WriteFile(cacheFile);
return;
}
// Add core
if (core) {
content += GetFileContents("tinymce." + suffix + ".js");
}
// Add core languages
for (x = 0; x < languages.Length; x++)
content += GetFileContents("langs/" + languages[x] + ".js");
// Add themes
for (i = 0; i < themes.Length; i++) {
content += GetFileContents("themes/" + themes[i] + "/theme." + suffix + ".js");
for (x = 0; x < languages.Length; x++)
content += GetFileContents("themes/" + themes[i] + "/langs/" + languages[x] + ".js");
}
// Add plugins
for (i = 0; i < plugins.Length; i++) {
content += GetFileContents("plugins/" + plugins[i] + "/plugin." + suffix + ".js");
for (x = 0; x < languages.Length; x++)
content += GetFileContents("plugins/" + plugins[i] + "/langs/" + languages[x] + ".js");
}
// Add custom files
for (i = 0; i < custom.Length; i++)
content += GetFileContents(custom[i]);
// Generate GZIP'd content
if (supportsGzip) {
if (compress)
Response.AppendHeader("Content-Encoding", enc);
if (diskCache && cacheKey != "") {
// Gzip compress
if (compress) {
using (Stream fileStream = File.Create(cacheFile)) {
gzipStream = new GZipStream(fileStream, CompressionMode.Compress, true);
buff = encoding.GetBytes(content.ToCharArray());
gzipStream.Write(buff, 0, buff.Length);
gzipStream.Close();
}
} else {
using (StreamWriter sw = File.CreateText(cacheFile)) {
sw.Write(content);
}
}
// Write to stream
Response.WriteFile(cacheFile);
} else {
gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress, true);
buff = encoding.GetBytes(content.ToCharArray());
gzipStream.Write(buff, 0, buff.Length);
gzipStream.Close();
}
} else
Response.Write(content);
}
private string GetParam(string name, string def) {
string value = Request.QueryString[name] != null ? "" + Request.QueryString[name] : def;
return Regex.Replace(value, @"[^0-9a-zA-Z\\-_,]+", "");
}
private string GetFileContents(string path) {
try {
string content;
path = Server.MapPath(path);
if (!File.Exists(path))
return "";
StreamReader sr = new StreamReader(path);
content = sr.ReadToEnd();
sr.Close();
return content;
} catch (Exception ex) {
// Ignore any errors
}
return "";
}
private string MD5(string str) {
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = md5.ComputeHash(Encoding.ASCII.GetBytes(str));
str = BitConverter.ToString(result);
return str.Replace("-", "");
}
#endregion
}
Looks to me like the ashx page is sending cookies for session state - if you have cookies in asp.net, it will not cache - i've been there myself. You can probably exclude that handler from session, and make sure it does not set any cookies.