Introduction

I have talked in the past about the basics of building a performant Cloud Portal site. Today I would like to share an important feature in SAP HANA Cloud Portal which I have noticed some developers missing out on its advantages. The feature is called Shindig cache. Shindig is an open source component which serves as a opensocial server in Cloud portal. It provides code to render gadgets, make proxy requests, handle REST/RPC requests and more. In Cloud Portal’s implementation there is also support for user propagation for a smooth user experience.

Scenario

A very common usage of Cloud Portal from a developers point of view is to incorporate their UI5 application using an opensocial feature called ‘sap-xhrwrapper’. This feature adapts an application to the usage of Shindig which in turn solves the problem of same origin policy which arises due to the fact that the widget’s iframe is loaded by Cloud Portal server and its content is loaded from a certain backend server. More on how to use the ‘sap-xhrwrapper’ can be found here.

Problem/Solution

From the above, we can understand that the application calls are turned into Shindig requests, specifically proxy requests. There are usually many such requests and they are mainly for retrieving static content. In order to improve performance a Shindig server side cache was implemented in Cloud Portal. This cache can be enabled for most of Shindig components and is controlled by the Cache-Control field of the HTTP response header. In order to enable caching the cache control header should be set to public. This in turn will save many requests to your application server and speed up widget loading. As long as the resources are in the cache, even first time access is positively affected.

What is needed in order to enable the cache?

  1. Add a filter & mapping to the web.xml to alter response headers
    • Configure cache duration
    • Consider which requests to intercept
  2. Create the filter class

In detail

Add filter & mapping to the web.xml

<filter id=“cacheControlFilter”>

              <description>Changes the Cache-Control header for static resources</description>

              <display-name>Cache Control</display-name>

              <filter-name>cacheControlFilter</filter-name>

              <filter-class>com.sap.labs.odp.web.filter.CacheControlFilter</filter-class>

              <init-param>

                     <param-name>maxAge</param-name>

                     <param-value>31536000</param-value>

              </init-param>

       </filter>

       <filter-mapping>

              <filter-name>cacheControlFilter</filter-name>

              <url-pattern>*.css</url-pattern>

              <url-pattern>*.gif</url-pattern>

              <url-pattern>*.jpg</url-pattern>

              <url-pattern>*.js</url-pattern>

              <url-pattern>*.png</url-pattern>

              <url-pattern>*.properties</url-pattern>

       </filter-mapping>

Create the filter class / Sample cacheControlFilter impl


import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.ws.rs.core.HttpHeaders;
import org.apache.log4j.Logger;
/**
* This filter set cache response headers for static resources
*/
public class CacheControlFilter implements Filter {
                private static final Logger logger = Logger.getLogger(CacheControlFilter.class);
                private static final String CACHE_CONTROL_PREFIX = "public, max-age=";
                private static long DEFAULT_MAX_AGE = 86400L;
                /*
                * Value in seconds should be set via web.xml
                */
                private long maxAge = DEFAULT_MAX_AGE;
                private String filterName;
                @Override
                public void destroy() {
                }
                @Override
                public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
                                ((HttpServletResponse) resp).setHeader(HttpHeaders.CACHE_CONTROL, CACHE_CONTROL_PREFIX + maxAge);
                                long expires = System.currentTimeMillis() + maxAge * 1000;      
                                ((HttpServletResponse) resp).setDateHeader(HttpHeaders.EXPIRES, expires);
                                // blocking the default servlet put from putting ETag on all responses
                                chain.doFilter(req, new HttpServletResponseWrapper((HttpServletResponse) resp) {
                                                public void setHeader(String name, String value) {
                                                                if (!"etag".equalsIgnoreCase(name)) {
                                                                                super.setHeader(name, value);
                                                                }
                                                }
                                });
                }
                @Override
                public void init(FilterConfig filterConfig) throws ServletException {
                                filterName = filterConfig.getFilterName();
                                String contextPath = filterConfig.getServletContext().getContextPath();
                                if (contextPath != null && contextPath.length() > 0) {
                                                contextPath = contextPath.substring(1);
                                }
                                String maxAgeStr = filterConfig.getInitParameter("maxAge");
                                if (maxAgeStr != null && maxAgeStr.trim().length() > 0) {
                                                try {
                                                                maxAge = Long.parseLong(maxAgeStr);
                                                } catch (NumberFormatException e) {
                                                                logger.error("Failed to set expires value to : " + maxAgeStr + ". Will use a default: " + maxAge, e);
                                                }
                                }
                }
}

Roye Cohen

http://www.linkedin.com/in/royecohen

To report this post you need to login first.

1 Comment

You must be Logged on to comment or reply to a post.

Leave a Reply