Thursday, October 11, 2012

IIS7, JSON Compression, and You

How do you know if your requests are being compressed?

Fiddler. Select resource > Response Section > Transformer tab. Also, the Headers tab should have a Transport section listing Content-Encoding: gzip.

How do I enable it in IIS 7?

First, just get Dynamic and Static Compression running by going to the IIS level or the site level, selecting the Compression feature, and checking the boxes. You may need to install it if you don’t see these options.

How come my JSON requests aren’t being compressed?

The JSON MIME type isn’t compressed by default. That MIME type is application/json.

Great. So how do I add it?

You can use the appcmd.exe or you can edit the file directly. C:\Windows\System32\inetsrv\Config\applicationHost.config is locked by the system fairly tightly, but I did the following. Make a backup. From the Start menu, right click Notepad and start as administrator. Use File > Open to go to the config file. Go to the <httpCompression> node. In both <dynamicTypes> and <staticTypes>, add both <add mimeType="application/json" enabled="true" /> and <add mimeType="application/json; charset=utf-8" enabled="true" />. Note the charset in the second item. That has to match what is sent from the server, exactly. Again, I pulled that from Fiddler by inspecting one of the responses' headers and checking the Content-Type value. I restarted at the IIS level to get the changes recognized.

Saturday, September 8, 2012

Vegetable Casserole

We had a spurt of production from our vegetable garden and we weren't sure what to do with it. We found a vegetable casserole recipe, modified it, and were extremely happy with the results.

You'll need the following.

  • 9 x 9 Pyrex dish
  • Frying pan
  • Eggplant
  • Peppers
  • Tomatoes
  • Onion
  • Shredded mozzarella cheese
  • Breadcrumbs
  • Salt, pepper, and cooking oil (I used a mix of vegetable and olive oil)
  • 1 cup of tomato sauce
Here are the steps.
  1. Slice enough eggplant to cover the bottom of the Pyrex dish and fry it in the cooking oil. I cut the slices nice and thick so they wouldn't fall apart after cooking. You'll need two layers, one for the bottom and one for the top.
  2. Cover the bottom of the Pyrex dish with the eggplant in a neat layer.
  3. Slice the peppers and spread them on top of the eggplant. I used multiple kinds of peppers, including jalapeƱo. Note that the peppers are not cooked.
  4. Add salt and pepper to taste on the peppers. Sprinkle a little mozzarella cheese on them as well.
  5. Slice the tomatoes and spread them on top of the peppers in a neat layer. Again, keep the slices a bit thick. Note that the tomatoes are not cooked.
  6. Carmelize the onion. Spread the carmelized onion on top of the tomatoes.
  7. Add another layer of fried eggplant on top of the onion in a neat layer.
  8. Sprinkle a little mozzarella cheese on top of the eggplant. 
  9. Sprinkle a little breadcrumb on top of the mozzarella.
Preheat the oven to 375 and bake for about 25 minutes or until the top is brown and you see a little bubbling on the sides. Let it sit for about 5 minutes and cut into quarters. Have some bread with this on the side.
As an aside, avoid raw onion in this. It's too overpowering.

Saturday, March 3, 2012

Loading and Refreshing the Application Cache of Multiple Sites

THE PROBLEM:

We have multiple web applications that are offline-capable using the HTML5 Application Cache feature. We want to give users the ability to go to one location to load / refresh / update / synchronize all applications and avoid the need to go to each and every application individually. Details of interest: we are only targeting the iPad 2 (or higher) and each application is not guaranteed to be on the same domain or sub-domain.

THE SOLUTION:

The HTML5 Cross-Document Messaging feature + an iframe + jQuery. Certainly feels little goofy, but it works.

THE DETAILS:

At least at this stage, there doesn't appear to be a way to determine whether the Application Cache of a specific site is up-to-date without loading that site. Said another way, we don't know if a site's Application Cache contains stale data until we load that site, and as soon as that site is loaded, it will begin updating, if necessary. The user must then wait until the browser is done fetching and storing the resources for the cache to actually be ready for offline use.

Therefore, if we could come up with a way to visit each site for the user, we could guarantee the newest manifest file would be checked and the cache updated.

There are several pieces to this puzzle.

1) One site, which we'll call "the loader," with the list of URLs for the target sites in a JavaScript array. I injected that list into the page server side, but they don't need to be. These are the individual web applications using the Application Cache so they can be available offline.

2) An iframe on the loader, which can be visible or hidden.

3) The individual web applications will each need a function to listen for requests.
function crossDocumentListener(event) {
//NOTE: There's no security in place here! Educate yourself before using!
if (event) {
var response = "origin:" + event.origin + ";request:" + event.data + ";location:" + window.location + ";response:" + window.applicationCache.status;
event.source.postMessage(response, '*');
}
}
window.addEventListener("message", crossDocumentListener, false);

I'm returning the origin, the original request data, the site's location, and the application cache status. The latter is what I want most.

4) A listener on the loader to get the responses. It is set to message the site loaded in the iframe every 2 seconds.

function responseListener(event) {
var response = event.data;
displayResponseVisible(event);

// 0 = uncached, 2 = checking, 3 = downloading
if(response.indexOf('response:0') > -1 || response.indexOf('response:2') > -1 || response.indexOf('response:3') > -1) {
if(!intervalId) { // We only set it once.
intervalId = setInterval(
function() {
getStatus();
}
, 2000);
}
}
// 1 = idle, 4 = updateready, 5 = obsolete
else {
if(intervalId) {
clearInterval(intervalId);
intervalId = undefined;
}

if(nextUrlIndex == -1) {
// The array is empty. We're done.
$('#message').prepend(completeMessage);
}
else{
// There are more URLs in the array to process so load the next one.
$.when(loadUrl(nextUrlIndex)).then(function() { getStatus(); });
}
}
}
window.addEventListener('message', responseListener, false);

5) More code to glue it altogether. Here are the key bits.

function getStatus() {
document.getElementById("app").contentWindow.postMessage('status', '*');
}

function loadUrl(arrayIndex) {
// Set the URL on the iframe, triggering it to load.
var iframe = $('#app');
$(iframe).attr('src', urls[arrayIndex]);

// Set the nextUrlIndex appropriately.
if(arrayIndex + 1 == urls.length) nextUrlIndex = -1;
else nextUrlIndex = arrayIndex + 1;

// http://www.elijahmanor.com/2011/02/jquerydeferred-to-tell-when-certain.html
var deferred = $.Deferred();
iframe.load(deferred.resolve);

return deferred.promise();
}

$(document).ready(function() {
$('#loader').click(function(event) {
$.when(loadUrl(nextUrlIndex)).then(function() { getStatus(); });
event.preventDefault();
});
});

I'm not crazy about the design of this, but with the glue code in place the details can be refined. As I refine it I'll try to update this post. If you have another way to approach this that you believe is somehow an improvement, I'd love to hear it.

Resources:

Sunday, February 5, 2012

iOS, Private Browsing, and the HTML5 Application Cache

This problem makes sense, but it caught me off-guard during debugging.

If you have a web application that uses the HTML5 Application Cache feature, the cache will not function properly with iOS Safari's Private Browsing setting on. The various JavaScript snippets that help with debugging will catch the most general error, which doesn't tell you much.

So if you have an app that seems to be caching properly in your desktop browser but not your mobile device, be sure to check your privacy settings!