INTRODUCTION
It's becoming increasingly important for web-based applications to be accessible offline. Yes, all browsers have caching mechanisms, but they're unreliable and don't always work as you might expect. HTML5 addresses some of the annoyances of being offline with theApplicationCache interface.
Using the cache interface gives your application three advantages:
- Offline browsing - users can navigate your full site when they're offline
- Speed - cached resources are local, and therefore load faster.
- Reduced server load - the browser will only download resources from the server that have changed.
The Application Cache (or AppCache) allows a developer to specify which files the browser should cache and make available to offline users. Your app will load and work correctly, even if the user presses the refresh button while they're offline.
THE MANIFEST FILE
The cache manifest file is a simple text file that lists the resources the browser should cache for offline access.
Referencing a MANIFEST file
To enable the application cache for an app, include the manifest attribute on the document's html tag:
<html manifest="example.appcache">
...
</html>...
The manifest attribute should be included on every page of your web application that you want cached. The browser does not cache a page if it does not contain the manifest attribute (unless it is explicitly listed in the manifest file itself. This means that any page the user navigates to that include a manifest will be implicitly added to the application cache. Thus, there's no need to list every page in your manifest.
The manifest attribute can point to an absolute URL or relative path, but an absolute URL must be under the same origin as the web application. A manifest file can have any file extension, but needs to be served with the correct mime-type (see below).
<html manifest="http://www.example.com/example.mf">
...
</html>
A manifest file must be served with the mime-type text/cache-manifest....
</html>
Here we use tomcat server, so we need to configure tomcat to serve all manifest file (Say with *.manifest extension) with text/cache-manifest.
So we need to add the following xml data into “conf/web.xml” of the tomcat server.
<mime-mapping>
<extension>manifest</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>
Structure of a MANIFEST file
A simple manifest may look something like this:
CACHE-MANIFEST:index.htmlstylesheet.css
images/logo.png
scripts/main.js
This example will cache four files on the page that specifies this manifest file.
There are a couple of things to note:
- The CACHE MANIFEST string is the first line and is required.
- If the manifest file or a resource specified in it fails to download, the entire cache update process fails. The browser will keep using the old application cache in the event of failure.
Lets take a look at a more complex example:
CACHE MANIFEST
# 2010-06-18:v
# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js
# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com
# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
# 2010-06-18:v
# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js
# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com
# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg
*.html /offline.html
/main.py /static.html
images/large/ images/offline.jpg
*.html /offline.html
A manifest can have three distinct sections: CACHE, NETWORK, and FALLBACK.
CACHE:
This is the default section for entries. Files listed under this header (or immediately after the CACHE MANIFEST) will be explicitly cached after they're downloaded for the first time.
NETWORK:
Files listed under this section are white-listed resources that require a connection to the server. All requests to these resources bypass the cache, even if the user is offline. Wildcards may be used.
FALLBACK:
FALLBACK:
An optional section specifying fallback pages if a resource is inaccessible. The first URI is the resource, the second is the fallback. Both URIs must be relative and from the same origin as the manifest file. Wildcards may be used.
Example(Using tomcat server):
1. Total source files used in this example are 5 in number, 1 manifest file and few images.2. The directory structure maintained in tomcat(Using Eclipse IDE) is as shown:
4. As it can be seen from the above html code, there are references to 2 different files like accordion.css, main.js & prototype.js. prototype.js is a javascript library used for ajax calls. The other two files are custom code for this application.
5. The content in the css file “accordion.css”is as follows :
.accordion {
border-bottom: 1px solid #A6C8DB; margin: 1px; background: #b6c0d6; background: -moz-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b6c0d6), color-stop(100%, #e0e8f9) ); background: -webkit-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -o-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .accordion input { border-bottom: 1px solid #A6C8DB; margin: 1px; background: #b6c0d6; background: -moz-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b6c0d6), color-stop(100%,#e0e8f9)); background: -webkit-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); background: -o-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .accordion button { border-bottom: 1px solid #A6C8DB; margin: 1px; background: #b6c0d6; background: -moz-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b6c0d6), color-stop(100%,#e0e8f9)); background: -webkit-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); background: -o-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }
6. The content in “main.js” javascript file is as follows:
7. In the above javascript code, there is ajax call being made to “ajax/logos.jsp”. Code is as follows:
<%
int type=Integer.parseInt(request.getParameter("type"));
if(type==1){
%>
<table class="accordion" >
<tr>
<td><button onclick="displayInfo('mozilla')" type="button" style="width: 100px; height: 60px"><img src="images/mozilla.png"/>Mozilla</button></td>
<td><button onclick="displayInfo('chrome')" type="button" style="width: 100px; height: 60px"><img src="images/chrome.png"/>Chrome</button></td>
</tr>
<tr>
<td><button onclick="displayInfo('ie')" type="button" style="width: 100px; height: 60px"><img src="images/ie.png"/>IE</button></td>
<td><button onclick="displayInfo('safari')" type="button" style="width: 100px; height: 60px"><img src="images/safari.png"/>Safari</button></td>
</tr>
</table> <%
}
else{
%>
<table class="accordion" >
<tr>
<td><button onclick="displayInfo('android')" type="button" style="width: 100px; height: 60px"><img src="images/android.jpg" height="31px" width="30px"/>Android</button></td>
<td><button onclick="displayInfo('apple')" type="button" style="width: 100px; height: 60px"><img src="images/apple.jpg"/ height="31px" width="30px">Apple</button></td>
</tr>
<tr>
<td><button onclick="displayInfo('google')" type="button" style="width: 100px; height: 60px"><img src="images/google.jpg" height="31px" width="30px"/>Google</button></td>
<td><button onclick="displayInfo('micro')" type="button" style="width: 100px; height: 60px"><img src="images/micro.jpg" height="31px" width="30px"/>Microsoft</button></td>
</tr>
</table>
<% }
%>
<div style="display: none;" id="mozilla">
Mozilla is not a web browser!
Mozilla is a framework for building better web applications using web standards.
</div>
<div style="display: none;" id="chrome">
A Free Web Browser That Runs Apps & Websites With Lightning Speed.
</div>
<div style="display: none;" id="ie">
Internet Explorer 9 is Microsoft's most aggressive attempt yet at hanging onto its lead in the browser market.
</div>
<div style="display: none;" id="safari">
Safari for Mac and PC puts the emphasis on browsing not the browser. Innovative features make your experience on the web better than it ever was.
</div>
<div style="display: none;" id="android">
Android is a Linux-based operating system for mobile devices such as smartphones and tablet computers. It is developed by the Open Handset Alliance led by Google
</div>
<div style="display: none;" id="apple">
From Ipods to Iphones to MacBooks,Apple uses "think different" approach to reframe computing, communication and more.
</div>
<div style="display: none;" id="google">
Google's mission is to organize the world's information and make it universally accessible and useful.
</div>
<div style="display: none;" id="micro">
Microsoft's ambitions are anything but small. The world's #1 software company develops and sells a variety of products used by consumers and businesses.
</div>
8. Finally we come to the manifest file named in this example as “server.manifest”. The content of that file is as follows:
9. Call to ajax page is not cached and hence need a presence of internet or server. Other resources are cached.
5. The content in the css file “accordion.css”is as follows :
.accordion {
border-bottom: 1px solid #A6C8DB; margin: 1px; background: #b6c0d6; background: -moz-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #b6c0d6), color-stop(100%, #e0e8f9) ); background: -webkit-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -o-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .accordion input { border-bottom: 1px solid #A6C8DB; margin: 1px; background: #b6c0d6; background: -moz-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b6c0d6), color-stop(100%,#e0e8f9)); background: -webkit-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); background: -o-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .accordion button { border-bottom: 1px solid #A6C8DB; margin: 1px; background: #b6c0d6; background: -moz-linear-gradient(top, #b6c0d6 0%, #e0e8f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b6c0d6), color-stop(100%,#e0e8f9)); background: -webkit-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); background: -o-linear-gradient(top, #b6c0d6 0%,#e0e8f9 100%); -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }
6. The content in “main.js” javascript file is as follows:
7. In the above javascript code, there is ajax call being made to “ajax/logos.jsp”. Code is as follows:
<%
int type=Integer.parseInt(request.getParameter("type"));
if(type==1){
%>
<table class="accordion" >
<tr>
<td><button onclick="displayInfo('mozilla')" type="button" style="width: 100px; height: 60px"><img src="images/mozilla.png"/>Mozilla</button></td>
<td><button onclick="displayInfo('chrome')" type="button" style="width: 100px; height: 60px"><img src="images/chrome.png"/>Chrome</button></td>
</tr>
<tr>
<td><button onclick="displayInfo('ie')" type="button" style="width: 100px; height: 60px"><img src="images/ie.png"/>IE</button></td>
<td><button onclick="displayInfo('safari')" type="button" style="width: 100px; height: 60px"><img src="images/safari.png"/>Safari</button></td>
</tr>
</table> <%
}
else{
%>
<table class="accordion" >
<tr>
<td><button onclick="displayInfo('android')" type="button" style="width: 100px; height: 60px"><img src="images/android.jpg" height="31px" width="30px"/>Android</button></td>
<td><button onclick="displayInfo('apple')" type="button" style="width: 100px; height: 60px"><img src="images/apple.jpg"/ height="31px" width="30px">Apple</button></td>
</tr>
<tr>
<td><button onclick="displayInfo('google')" type="button" style="width: 100px; height: 60px"><img src="images/google.jpg" height="31px" width="30px"/>Google</button></td>
<td><button onclick="displayInfo('micro')" type="button" style="width: 100px; height: 60px"><img src="images/micro.jpg" height="31px" width="30px"/>Microsoft</button></td>
</tr>
</table>
<% }
%>
<div style="display: none;" id="mozilla">
Mozilla is not a web browser!
Mozilla is a framework for building better web applications using web standards.
</div>
<div style="display: none;" id="chrome">
A Free Web Browser That Runs Apps & Websites With Lightning Speed.
</div>
<div style="display: none;" id="ie">
Internet Explorer 9 is Microsoft's most aggressive attempt yet at hanging onto its lead in the browser market.
</div>
<div style="display: none;" id="safari">
Safari for Mac and PC puts the emphasis on browsing not the browser. Innovative features make your experience on the web better than it ever was.
</div>
<div style="display: none;" id="android">
Android is a Linux-based operating system for mobile devices such as smartphones and tablet computers. It is developed by the Open Handset Alliance led by Google
</div>
<div style="display: none;" id="apple">
From Ipods to Iphones to MacBooks,Apple uses "think different" approach to reframe computing, communication and more.
</div>
<div style="display: none;" id="google">
Google's mission is to organize the world's information and make it universally accessible and useful.
</div>
<div style="display: none;" id="micro">
Microsoft's ambitions are anything but small. The world's #1 software company develops and sells a variety of products used by consumers and businesses.
</div>
8. Finally we come to the manifest file named in this example as “server.manifest”. The content of that file is as follows:
EXPERIMENT REPORTS
HTML5 Application cache support in Desktop Browsers
Browsers
|
Loading page 1st time(Server Up)
|
Loading page 2nd time(Server up)
|
Loading page (Server down)
|
Comments or Observations
|
Internet Explorer 9 | Pass | Pass | Fail | IE 9 does not support Application Cache, it doesnot request for the manifest file Refer http://people.mozilla.com/~prouget/ie9/ for more information. |
Mozilla Firefox 10 | Pass | Pass | Pass | In offline mode, cached content renders properly except Ajax call |
Google Chrome | Pass | Pass | Pass | In offline mode, cached content renders properly except Ajax call |
Apple Safari 5 | Pass | Pass | Pass | In offline mode, cached content renders properly except Ajax call |
Opera 11 | Pass | Pass | Pass | In offline mode, cached content renders properly except Ajax call |
Tomcat Server used to modify the server response and check behavior on desktop browsers
FIREFOX | CHROME | OPERA | SAFARI | IE | |
MF : not modified Server Response Status : 304 | CACHE ERROR event fired no impact on rendering | works fine | CACHE ERROR event fired no impact on rendering | works fine | not supported |
MF : OBSOLETE Response Status : 404, 410 | appcache deleted | appcache deleted | appcache deleted | appcache deleted | not supported |
MF : modified Cache-Control : no-cache not specified | Not requesting for MF | works fine | works fine | works fine | not supported |
MF : modified Cache-Control : no-store | works fine | works fine | works fine | works fine | not supported |
How to send 304 Not modified status:
- First time when the server sends a response, it would send a Etag value in the header.
- Second time when the page is loaded, the GET request will contain a header called “If-none-match” with the value of the Etag. The server then checks the value and validates it. If the content is changed, a fresh response is sent by the server or else 304 Not modified status is sent to the client page.
Manifest file and AJAX calls :
- All ajax calls with query strings will not be put in the manifest file since we cannot calculate all possible permutations of the queries available
- AJAX calls with request for a static resource without any query string is no different than any other request but phantomJS is still not putting the resource URL in the Manifest file. Need to check.
- phantomJS only tracks requests which are made till the pageload event is triggered, we need to see the possibility of tracking the requests made after page load to track - static AJAX requests.
Viewing appcache contents on different browsers:
- Google Chrome: Contents of the appcache can be viewed on the following link. chrome://appcache-internals/
- Mozilla Firefox: Contents of the appcache can be viewed on the following link.
- Safari : Press Ctrl+Alt-C. A debugger will open(Occupies quarter or half of the browser page). Click on the “Resources” tab. Then at the left-bottom end you should be able to see an option called “Application Cache”. Open the tree to view which all resources have been cached.
Note: In Chrome, Same approach used for safari can be used. But instead of “Ctrl+Alt+C”, press “Ctrl+Shift+C”.
Upper limit of the Application Cache:
According to the statistics collected from this site: http://www.sitepoint.com/diving-deeper-into-html5-offline-browsing/#fbid=xnTJtxdd5Kh- Safari desktop browser (Mac and Windows) have no limit
- Mobile Safari has a 10MB limit
- Older versions of Chrome had a 5MB limit. Newer version has more.
- Android browser has no limit to ApplicationCache size
- Firefox desktop has unlimited ApplicationCache size
- Opera’s ApplicationCache limit can be managed by the user, but has a default size of 50MB
Drawbacks of Application Cache:
- XHR(XmlHttpRequest) cannot be cached. (In other words, Ajax pages)
- Basically POST is not an idempotent operation. So you cannot use it for caching. GET should be an idempotent operation, so it is commonly used for caching.
Conclusion on the observations made on different browsers:
- When the page is loaded for the second time, the manifest file is requested from the server and it is matched with the manifest file which was previously cached. If both are same, then previously cached contents will be rendered from cache itself. But all other white-list contents will be requested from the server.
- When the page is loaded in offline mode (Internet down or server down), the manifest file is requested from server and it fails. So the browser will render the webpage using the cached resources. All white-list contents will try to request server and fails.
- A snapshot of the Chrome’s web debugger requesting for manifest file(failing in offline mode) is as shown.
- Suppose say the page “homePage.jsp” is loaded for the first time in the browser. Based on the manifest file contents, the browser caches the resources. Before loading it for the second time, say there are few white spaces appended in the manifest file at the line where homePage.jsp is mentioned. Then the browser sends a request to the server for manifest file and compares it with the manifest file which was previously cached. Since white spaces are appended, the browser requests the server for the resource which it assumes that it would have changed at the server end. In this case, the browser requests only for homePage.jsp.
Please feel free to add comments on my blog post.