Enabling JSON Compression (and Gibraltar) in Windows Azure

A friend of mine posted to Twitter a bit ago that he was having trouble dealing with HttpCompression on Windows Azure. Specifically, only certain default MimeTypes were being compressed (like ASPX and CSS), while other important ones like JSON and SOAP were not.

During another Azure project, I also ran into an issue where logins stopped working after upgrading to Azure OS 2.X Azure SDK 1.3. It turns out that there is a bug in that release that doesn’t allow configuration inheritance for the MachineKey, and the workaround was to manipulate the server configuration in the RoleEntryPoint.

Since I’m already having to manipulate the server config to synchronize the MachineKey settings, I thought I’d whip up some code real quick to solve this problem during the same process, using the httpConpression topic on IIS.net as a guide.

If you don’t already have a RoleEntryPoint in your app… well, you should. Copy the code below into a new class file in your main application, and add a reference to Microsoft.Web.Administration.dll, as specified here. You’ll also notice Gibraltar-specific code peppered throughout… this is all that you need to get Gibraltar working on Windows Azure (as long as you’re also using the tip from my previous post as well).

(The code is long, and it’s in VB… because that’s the language this particular app was in. If you need it converted to C#, you can do so here.

UPDATE: My colleage Kendall pointed out that the role event handlers should be declared at the very beginning, and that, even though the RoleInstance’s OnStart event is called on a separate thread from the main app, it should still run the WarmUp loop on a separate thread, so that the function can return MyBase.OnStart() and exit properly. Good call.

Imports System.Linq
  Imports Microsoft.WindowsAzure.Diagnostics
  Imports Microsoft.WindowsAzure.ServiceRuntime
  Imports Microsoft.Web.Administration
  Imports System.Threading

  Namespace Fon.Apps

  Public Class WebRole Inherits RoleEntryPoint

  Public Overrides Function OnStart() As Boolean

  ' For information on handling configuration changes
  ' see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
  AddHandler RoleEnvironment.Changing, AddressOf RoleEnvironment_Changing
  AddHandler RoleEnvironment.Stopping, AddressOf RoleEnvironment_Stopping

  Gibraltar.Agent.Log.SendSessionsOnExit = True

  Using server As New ServerManager()

  'Cycle through the sites and set the MachineKey for each one.
  For Each site In server.Sites

  Dim siteConfig = site.GetWebConfiguration()

  'Get the appSettings section
  Dim appSettings = siteConfig.GetSection("appSettings").GetCollection().ToDictionary(Function(e) DirectCast(e("key"), String), Function(e) DirectCast(e("value"), String))

  'Reconfigure the machine key
  Dim machineKeySection = siteConfig.GetSection("system.web/machineKey")
  machineKeySection.SetAttributeValue("validationKey", appSettings("validationKey"))
  machineKeySection.SetAttributeValue("validation", appSettings("validation"))
  machineKeySection.SetAttributeValue("decryptionKey", appSettings("decryptionKey"))
  machineKeySection.SetAttributeValue("decryption", appSettings("decryption"))

  Next

  'Turn on compression for JSON and SOAP.
  Dim config As Configuration = server.GetApplicationHostConfiguration()
  Dim httpCompressionSection As ConfigurationSection = config.GetSection("system.webServer/httpCompression")
  Dim dynamicTypesCollection As ConfigurationElementCollection = httpCompressionSection.GetCollection("dynamicTypes")
  
  Dim e1 As ConfigurationElement = dynamicTypesCollection.CreateElement("add")
  e1("mimeType") = "application/json"
  e1("enabled") = True
  dynamicTypesCollection.AddAt(0, e1)

  Dim e2 As ConfigurationElement = dynamicTypesCollection.CreateElement("add")
  e2("mimeType") = "application/json;charset=utf-8"
  e2("enabled") = True
  dynamicTypesCollection.AddAt(0, e2)

  Dim e3 As ConfigurationElement = dynamicTypesCollection.CreateElement("add")
  e3("mimeType") = "application/soap+xml"
  e3("enabled") = True
  dynamicTypesCollection.AddAt(0, e3)

  server.CommitChanges()

  End Using

  'Poor man's Application Warm-up here
  Tasks.Task.Factory.StartNew(Sub()
  Try
  While (True)
  Trace.WriteLine("Warming up web site...")
  Dim webClient As New Net.WebClient()
  webClient.DownloadString("http://mysite.com/WarmUp.ashx?WarmUp=True")
  Thread.Sleep(TimeSpan.FromMinutes(9))
  End While

  Catch ex As Exception
  Gibraltar.Agent.Log.RecordException(ex, "WindowsAzure.WebRole.Startup", True)

  End Try
  End Sub)

  Return MyBase.OnStart()

  End Function

  Private Sub RoleEnvironment_Stopping(sender As Object, e As RoleEnvironmentStoppingEventArgs)
  Gibraltar.Agent.Log.EndSession(Gibraltar.Agent.SessionStatus.Normal, "Azure's configuration settings have changed.")
  End Sub

  Private Sub RoleEnvironment_Changing(sender As Object, e As RoleEnvironmentChangingEventArgs)

  ' If a configuration setting is changing
  If e.Changes.Any(Function(change) TypeOf change Is RoleEnvironmentConfigurationSettingChange) Then

  ' Set e.Cancel to true to restart this role instance
  Gibraltar.Agent.Log.EndSession(Gibraltar.Agent.SessionStatus.Normal, "Azure's configuration settings have changed.")
  e.Cancel = True
  End If

  End Sub

  End Class

  End Namespace

You might have noticed the While loop that cycles every 9 minutes and makes a request to a specific website. Though the actual site details have been abstracted out, this is a trick that I use to keep my application “warmed up,” and is a variation on the technique used by Christian Weyer.

For the MachineKey part, you’ll also need to add the following entries to the appSettings section of each of the web.configs in your Role, being sure to replace your values where appropriate:

  <add key="decryption" value="Auto" />
  <add key="decryptionKey" value="YOURVALUEHERE" />
  <add key="validation" value="SHA1" />
  <add key="validationKey" value="YOURVALUEHERE" />

You’ll also notice that it’s also pretty easy to add additional MimeTypes to compress. Just continue to copy the pattern for any of the items you think are missing. Be sure not to add ones that don’t already exist, or you’ll likely crash your role.

Anyways, I I hope that helps some of you that are trying to resolve various Azure issues.

Rock solid centralized logging

Unlimited applications, unlimited errors, scalable from solo startup to enterprise.