Thursday, May 28, 2009

Caching portlets in ALUI 6.x

I've been developping portlets (gadgets) for nearly ten years now, starting with Plumtree Portal Server 3.5. A the time, there was a very nice document called "The Gadget Book" with all the details about the task of developping gadgets as they were called then. This PDF has been replaced by other versions since, but I still remember a few good practices for having a fast and reliable portal.

Among other things, there was the caching strategies to implement on the portlet side. The portal uses the standard HTTP mechanisms for calling content from the portlet server, as described in RFC 2616. Using the HTTP ETag and Last-Modified headers, we could prevent rendering a whole portlet when its content would remain unchanged. It proceeded like this:

- First call of the portlet by the portal. No special header is passed;
- The portlet returns the content to display, and sets the ETag and/or Last-Modified header;
- On the next access to the portlet, when the minimum cache time specified in the portlet configuration is over, and the maximum time not being reached, the portal calls the portlet giving back the content of the previous header;
- The portlet checks if the content has changed since the last call (in my case by comparing the timestamp passed in the Last-Modified header with a timestamp stored in a DB). If the content should be regenerated, I send the full content and give the new value for the headers. If not, I simply return an HTTP error 304 (Not Modified), and the portal would in that case display the content stored in its cache.

This worked perfectly for ages, until version 6.0 was released. On that version, when the 304 error was returned, the portal would display an error instead of displaying the cache (even when the setting "Suppress errors where possible (show cached content instead)" was checked.

I had several emails going back and forth with the Plumtree/BEA support and they finally acknowledge this as a bug. But in version 6.5, which is the one we are currently running (on http://www.myschool.lu/), that bug is still unresolved.

So, I disabled that part of my code, waiting for a solution to come eventually. I'm still waiting ;-) And my portlet is used in even more places than before (it displays content stored in a DB in many community pages) and the cache is key to allowing proper rendering times. The content would change once in a while, but hundreds of users would see the unchanged content in the meantime.

So, I tried to find a solution to this, and I have implemented the following workaround:

- Configure the portlet in this way (like in old times when caching worked):

- In my caching routine, I check the "CSP-Aggregation-Mode" header to see if I'm called as a portlet in a page, or as a standalone page (inside or outside of the gateway). This header can be empty (e.g. when called from outside of the gateway), can contain "Multiple" when displayed as a portlet in a page, or "Single" or "Hosted" when in an independent page accessed through the gateway.
- In the "Multiple" case, I do not return the 304 error but instead a Service Unavailable error (503). As per the setting above, the error is not displayed and the cached content is shown.
- In any other case, I return the 304 error, as a standalone page is properly processed by the browser. In such cases, the caching is done on the browser side and not the portal.

So far, so good. The performance has increased a lot, as could be expected, and the portlet server has more time to do other things than constantly rendering the same content...

Key lines of code:

Const c_sDateFormat As String = "yyyy-MM-dd HH:mm:ss"
Dim bFromPortal As Boolean = (Request.Headers("CSP-Aggregation-Mode") = "Multiple")

If dBrowserDate.ToString(c_sDateFormat) = dObjectDate.ToString(c_sDateFormat) Then
If bFromPortal Then
Response.StatusCode = System.Net.HttpStatusCode.ServiceUnavailable
Else
Response.StatusCode = System.Net.HttpStatusCode.NotModified
End If
Response.End()
End If

Response.Cache.SetCacheability(HttpCacheability.Public)
Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches)
Response.Cache.SetLastModified(dObjectDate)

Wednesday, May 27, 2009

IE8 not recognized in ASP.Net 1.1 applications

As always, I install the latest versions of anything when they become available (sometimes even betas). So did I for Internet Explorer 8.0 when it was released... And all of a sudden, my trusty applications made with ASP.Net 1.1 went berzerk. Things that used to work with IE7 and Firefox simply stopped working. This included external components we bought, like the ComponentArt suite. NavBars could not be clicked, drop-down menus behaved in strange ways, and so on.

I dug a bit and found the problem. My browser was not detected as it should. The
"Request.Browser.Browser" command returned "Unknown" instead of the expected "IE". But why?

I remarked that on my Vista x64 machine, the 32 bits browser would have these issues, but not the 64 bits version of IE. I compared the "User-Agent" HTTP header and saw these values:

32 bits:
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; WOW64; Trident/4.0; GTB6; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; InfoPath.2; .NET CLR 1.1.4322; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618; FDM; OfficeLiveConnector.1.3; OfficeLivePatch.0.0)

64 bits:
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Win64; x64; Trident/4.0; .NET CLR 2.0.50727; SLCC1; Media Center PC 5.0; .NET CLR 3.5.30729)

I checked the browscap.ini and machine.config files on the server, but couldn't change them in any useful way.

Could the difference in length be the problem? In a Microsoft article on TechNet (http://technet.microsoft.com/en-us/library/bb496341.aspx) they say that the length of this header should remain shorter than 200 characters. In the first case, it is definitely longer...

So I dug further, looking for ways to shorten that User-Agent string. Many articles and blog talked about the following key to change in the registry:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\User Agent\Post Platform

I did this, but only the User-Agent of the 64 bits instance of IE seemed to care. I searched through the registry for the "OfficeLivePatch" key I can only see in the 32 bits instance, and found it it the following key:
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform

As you can see, there is "Wow6432Node" in there, which corresponds to 32 bits applications running on a 64 bits OS. Exactly my case ;-)

I renamed the "Post Platform" key into "Post Platform-" and restarted the browser and... bingo :-) The browser is detected as IE 8.0 and everything works fine...

Now I need to find out why, when I open a new tab or a new instance of IE 8, the content is not loaded, as it continuously shows "Connecting...". I need to open one or more tabs before a connection can be made. It also happens when opening popup windows, which is even more annoying (in that case, I need to reopen the popup with Ctrl-N until it works).

To be continued...

Thursday, May 7, 2009

Monday, May 4, 2009

Les Compagnons du Champeau

Concert des 50 ans des Compagnons du Champeau, en l'église St Loup, à Namur, le samedi 2 mai 2009...