Details
-
Bug
-
Status: Patch Available
-
Major
-
Resolution: Unresolved
-
2.5.1
-
None
-
None
Description
--------------------------------------------------
Problem
--------------------------------------------------
JavaScript served by the Shindig code loader is not being cached by the browser via provided ETag support when using the WAS Liberty Profile.
Note: Everything works as expected when using Tomcat.
--------------------------------------------------
Steps To Reproduce
--------------------------------------------------
(1) Run Shindig server using WAS Liberty Profile
(2) Open browser and track network traffic (via Firebug Net tab, etc.)
(3) Go to common container sample page
=> e.g. /containers/commoncontainer/index.html
=> Initial request to fetch Shindig JavaScript loads successfully
- http://localhost:9082/gadgets/js/container:open-views:opensearch:rpc:xmlutil:pubsub-2.js?c=1&debug=1&container=default
- HTTP status code: 200 OK
=> BUG: No Etag HTTP response header on fetch request - e.g. Etag: "d155f4b3dc36fb9b2cbe62e856f55fa3"
(4) Reload the page (F5)
=> BUG: Shindig JavaScript code is re-fetched instead of using a cached copy - HTTP status code: 200 OK
=> EXPECTED: HTTP status code: 304 Not Modified
--------------------------------------------------
Cause #1
--------------------------------------------------
URL pattern specified by ETag servlet filter is invalid and not being applied at all to incoming requests when using WAS Liberty Profile.
e.g.
<filter-mapping>
<filter-name>etagFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
According to Java Servlet Specification - Version 3.0:
12.2 Specification of Mappings
In the Web application deployment descriptor, the following syntax is used to define mappings:
- A string beginning with a ‘/’ character and ending with a ‘/*’ suffix is used for path mapping.
- A string beginning with a ‘*.’ prefix is used as an extension mapping.
- The empty string ("") is a special URL pattern that exactly maps to the application's context root, i.e., requests of the form http://host:port/<contextroot>/.
In this case the path info is ’/’ and the servlet path and context path is empty string (““). - A string containing only the ’/’ character indicates the "default" servlet of the
application. In this case the servlet path is the request URI minus the context path
and the path info is null. - All other strings are used for exact matches only
Also, see: http://stackoverflow.com/questions/9443975/servlet-mapping-in-web-xml-vs-any-pattern
Note: Tomcat tolerates the "*" pattern.
--------------------------------------------------
Solution #1
--------------------------------------------------
<filter-mapping>
<filter-name>etagFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
--------------------------------------------------
Cause #2
--------------------------------------------------
After fixing the path issue, the ETag servlet filter was then applied to each incoming request as expected.
However, the filter started throwing exceptions into the Java Console / server log for every incoming request.
e.g.
SRVE8094W: WARNING: Cannot set header. Response already committed.
Exception = com.ibm.wsspi.http.channel.exception.WriteBeyondContentLengthException
Source = com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter
probeid = 149
Stack Dump = com.ibm.wsspi.http.channel.exception.WriteBeyondContentLengthException
at com.ibm.wsspi.http.HttpOutputStream.writeToBuffers(HttpOutputStream.java:251)
at com.ibm.wsspi.http.HttpOutputStream.write(HttpOutputStream.java:575)
at com.ibm.ws.webcontainer.osgi.response.WCOutputStream.write(WCOutputStream.java:234)
at org.apache.shindig.gadgets.servlet.ETaggingHttpResponse.writeToOutput(ETaggingHttpResponse.java:170)
at org.apache.shindig.gadgets.servlet.ETagFilter.doFilter(ETagFilter.java:60)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:194)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:85)
at org.apache.shindig.auth.AuthenticationServletFilter.callChain(AuthenticationServletFilter.java:186)
at org.apache.shindig.auth.AuthenticationServletFilter.doFilter(AuthenticationServletFilter.java:97)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:194)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:85)
at org.apache.shindig.common.servlet.HostFilter.doFilter(HostFilter.java:38)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:194)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:85)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:949)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:1029)
at com.ibm.ws.webcontainer.webapp.WebApp.handleRequest(WebApp.java:4499)
at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost$2.handleRequest(DynamicVirtualHost.java:282)
at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:954)
at com.ibm.ws.webcontainer.osgi.DynamicVirtualHost$2.run(DynamicVirtualHost.java:252)
at com.ibm.ws.http.dispatcher.internal.channel.HttpDispatcherLink$TaskWrapper.run(HttpDispatcherLink.java:584)
at com.ibm.ws.threading.internal.Worker.executeWork(Worker.java:439)
at com.ibm.ws.threading.internal.Worker.run(Worker.java:421)
at java.lang.Thread.run(Thread.java:738)
When using WAS Liberty Profile the following method is called repeatedly (but not when using Tomcat):
org.apache.shindig.gadgets.servlet.ETaggingHttpResponse#flushBuffer
This turns off batching mode
e.g.
batching = false;
The subsequent method call causes the exception when batching mode is off:
org.apache.shindig.gadgets.servlet.ETaggingHttpResponse#writeToOutput
if (batching) {
...
} else if (bytes.length != 0) {
originalStream.write(bytes);
stream.getBuffer().clear();
}
--------------------------------------------------
Solution #2
--------------------------------------------------
Add a check to see whether the response has been committed already before trying to write to it.
if (batching) {
...
} else if ((bytes.length != 0) && !isCommitted()) {
originalStream.write(bytes);
stream.getBuffer().clear();
}
--------------------------------------------------
Patch
--------------------------------------------------
Attached.