Introduction
Akamai enables one to not only cache pages based upon the URI requested (e.g. /en/download/index.jsp) but also with a flexible cache key. This permits many variations of a single URI to be cached by Akamai based upon some value. Effective caching of content thereby offloading serving content to Akamai is critical for Oracle – without it they cannot run a high-performance website that responds quickly for a distributed global user base.
To complicate caching; some pages on www.java.com (JCOM) render differently based upon the visitors “user-agent” header. For example a visitor on Windows must see a different download page than a visitor on Mac OS X (for the same URI).
By joining the URI of the page and the value from the ORA_FLEX_CACHE_KEY cookie we can create a flexible cache key that Akamai can use. Akamai can then cache many versions of the same URI thereby offloading traffic to Akamai rather than having it coming back to Oracle’s servers.
Example:
- URI = /en/download/index.jsp
- ORA_FLEX_CACHE_KEY = windows-chrome
- Akamai cache key = /en/download/index.jspwindows-chrome
Any visitor that then requests this URI who is on Windows and Chrome (regardless of if they are on XP, Win 7, Win 8 or Chrome 24, 25, 26…) will get the same version sent back to them from Akamai. As such, a simple wrapper was needed to be implemented on WC Sites that enabled this key feature.
Main Article
The team that is implementing www.java.com (JCOM) are implementing a custom groovy wrapper element that deals with determining the proper user-agent/cookie and consequently, allows the webpage Template to properly render the correct content for each visitor on each page. The current POC code can be seen on lines 22-70 as shown in the code snippet below. Within a few weeks they expect to have that logic pushed down into a separate CSElement (for easier re-use) and further, they won’t be manually grepping the user-agent. Instead they’ll be leveraging BrowserHawk a 3rd party licensed library that handles all current and future user-agent combinations. When they update the code, I’ll post revisions here. In the meantime, here is the current POC code:
import COM.FutureTense.Interfaces.ICS import COM.FutureTense.Interfaces.FTValList import COM.FutureTense.Interfaces.Utilities; import COM.FutureTense.Util.ftMessage import COM.FutureTense.Util.ftErrors import com.fatwire.system.Session import com.fatwire.system.SessionFactory import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; String queryString = ics.getIServlet().getQueryString(); // Get a general logger for this dispatcher ... String generalLogName = "com.oracle.pdit.jcom.groovy.jcomgroovydispatcher.general"; Log logGeneral = LogFactory.getLog(generalLogName); logGeneral.debug("BEGIN: jcom-groovy-dispatcher.groovy [" + queryString + "]"); // test if this is a detection request from Akamai if(queryString.contains("requesttype=detect")){ String akamaiLogName = "com.oracle.pdit.jcom.groovy.jcomgroovydispatcher.akamai"; Log logAkamai = LogFactory.getLog(akamaiLogName); String userAgent = ics.getIServlet().getServletRequest().getHeader("User-Agent").toLowerCase(); ArrayList<String> tokens = new ArrayList<String>(); // used to store the various values before we build and set the cookie // Browser checks... if(userAgent.contains("firefox")){ tokens.add("firefox"); }else if(userAgent.contains("chrome")){ tokens.add("chrome"); }else if(userAgent.contains("msie")){ tokens.add("ie"); }else if(userAgent.contains("opera")){ tokens.add("opera"); }else if(userAgent.contains("safari")){ tokens.add("safari"); } // OS Checks if(userAgent.contains("windows")){ tokens.add("win"); }else if(userAgent.contains("mac")){ tokens.add("mac"); }else if(userAgent.contains("linux")){ tokens.add("linux"); } // 32-bit vs 64-bit Checks if(userAgent.contains("wow64")){ tokens.add("wow64"); }else if(userAgent.contains("win64")){ tokens.add("win64"); } // build a cookie value String cookieVal = "desktop"; for (String token : tokens){ cookieVal += "-" + token; } // but if we couldn't detect any values then just set it to empty string if (tokens.size() == 0){ cookieVal = ""; } // set the cookie logAkamai.debug("Setting ORA_FLEX_CACHE_KEY cookie: [" + cookieVal + "], for User-Agent: [" + userAgent + "]"); ics.SetCookie("ORA_FLEX_CACHE_KEY", cookieVal, 3600*24*30*12, "/", "java.com " , false); }else{ // render the page as normal with layout template String callTemplateLogName = "com.oracle.pdit.jcom.groovy.jcomgroovydispatcher.calltemplate"; Log logCallTemplate = LogFactory.getLog(callTemplateLogName); // determine template name String tName = ""; if(ics.GetVar("c")==null || ics.GetVar("site")==null){ tName = ics.GetVar("childpagename"); }else{ tName = ics.GetVar("childpagename").replaceAll(ics.GetVar("site") + "/" + ics.GetVar("c") + "/", ""); } FTValList updateVal = new FTValList(); FTValList valueForCallTemplate = new FTValList(); valueForCallTemplate.setValString("SITE",ics.GetVar("site")); valueForCallTemplate.setValString("SLOTNAME","JCOMPage"); valueForCallTemplate.setValString("TID",ics.GetVar("eid")); valueForCallTemplate.setValString("TNAME",tName); valueForCallTemplate.setValString("C",ics.GetVar("c")); valueForCallTemplate.setValString("CID",ics.GetVar("cid")); valueForCallTemplate.setValString("TTYPE","CSElement"); valueForCallTemplate.setValString("PACKARGS",ics.GetVar("argslist")); valueForCallTemplate.setValString("locale",ics.GetVar("locale")); logCallTemplate.debug("Calling template: [" + tName + "], for [c:" + ics.GetVar("c") + ", cid:" + ics.GetVar("cid") + ", site:" + ics.GetVar("site") + "]"); final String s = ics.runTag("RENDER.CALLTEMPLATE", valueForCallTemplate); if (s != null){ ics.StreamText(s); } } logGeneral.debug("END: jcom-groovy-dispatcher.groovy [" + queryString + "]");
Note: Dolf Dijkstra from the A-Team helped them with the code regarding how to call a template on lines 75-101.
To complete the solution, in front of this groovy CSElement the JCOM team simply have an uncached SiteEntry set as a wrapper (see attached screenshot).
In summary, this is a nice, flexible pattern that can be adapted to many other projects!
Addendum:
BrowserHawk Overview
BrowserHawk is a 3rd party product licensed from Cyscape. It is used to detect a visitors environment characteristics such as OS and Browser.
BrowserHawk Product Documentation
- Product Docs: http://cyscape.com/products/bhawk/docs/browserhawk.htm
- Knowledge Base: http://cyscape.com/support/kb/XcInfoBase.asp?CatID=55
- What’s new in version 14: http://www.cyscape.com/products/bhawk/new.aspx
- The API javadocs can be found here.
All content listed on this page is the property of Oracle Corp. Redistribution not allowed without written permission