Thursday, June 7, 2012

Tutorial on HTML5 Application Cache


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:
  1. Offline browsing - users can navigate your full site when they're offline
  2. Speed - cached resources are local, and therefore load faster.
  3. 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.

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.html
stylesheet.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
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg
*.html /offline.html

Lines starting with a '#' are comment lines, but can also serve another purpose. An application's cache is only updated when its manifest file changes. So for example, if you edit an image resource or change a javascript function, those changes will not be re-cached. You must modify the manifest file itself to inform the browser to refresh cached files. Creating a comment line with a generated version number, hash of your files, or timestamp is one way to ensure users have the latest version of your software. 


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:
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:
3. Here “homePage.jsp” is the home page for this application. The source code is as shown below.
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.


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 9PassPassFailIE 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 10PassPassPassIn offline mode, cached content renders properly except Ajax call
Google ChromePassPassPassIn offline mode, cached content renders properly except Ajax call
Apple Safari 5PassPassPassIn offline mode, cached content renders properly except Ajax call
Opera 11PassPassPassIn offline mode, cached content renders properly except Ajax call



Tomcat Server used to modify the server response and check behavior on desktop browsers
FIREFOXCHROMEOPERASAFARIIE
MF : not modified
Server Response Status : 304
CACHE ERROR event fired
no impact on rendering
works fineCACHE ERROR event fired
no impact on rendering
works finenot 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 MFworks fineworks fineworks finenot supported
MF : modified
Cache-Control : no-store
works fineworks fineworks fineworks finenot 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
  1. Safari desktop browser (Mac and Windows) have no limit
  2. Mobile Safari has a 10MB limit
  3. Older versions of Chrome had a 5MB limit. Newer version has more.
  4. Android browser has no limit to ApplicationCache size
  5. Firefox desktop has unlimited ApplicationCache size
  6. Opera’s ApplicationCache limit can be managed by the user, but has a default size of 50MB

Drawbacks of Application Cache:

  1. XHR(XmlHttpRequest) cannot be cached. (In other words, Ajax pages)
  2. 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:

  1. 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.
  2. 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.
  3. A snapshot of the Chrome’s web debugger requesting for manifest file(failing in offline mode) is as shown.

  1. 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.

Sunday, May 20, 2012

HTML5 WEB SQL = Amazing Concept

The Web SQL database API isn’t actually part of the HTML5 specification, but it is part of the suite of specifications that allows us developers to build fully fledged web applications.
  1. It is just like it says on the tin: an offline SQL database. Implementations today are based on SQLite, a general-purpose open-source SQL engine. It comes with all the pros and cons of traditional databases.
  2. On the one hand, you have a fully relational structure, allowing you to rapidly query and manipulate data via joins, e.g. "delete all saved game where the level is less than 10 and the game hasn't been loaded in the past 30 days". You can express that in a line of SQL, whereas you'd have to manually wander through the equivalent corpus of Web Storage data. And if you have a strong need for performance, you can also lean on several decades of research on database optimisation (though tuning would have to be done separately for each possible SQL engine being used).
  3. Furthermore, there's support for transactions, your database is protected from the kind of race conditions that can arise with Web Storage.

There are three core methods in the spec that I’m going to cover in this article:
  1. openDatabase
  2. transaction
  3. executeSql
Support is a little patchy at the moment. Only Webkit (Safari, SafariMobile and Chrome) and Opera support web databases.

Creating and opening databases

If you try to open a database that doesn’t exist, the API will create it on the fly for you. You also don’t have to worry about closing databases.
To create and open a database, use the following code:
var db = openDatabase('mydb', '1.0', 'my first database', 2 *1024 * 1024);

We have passed four arguments to the openDatabase method. These are:
  1. Database name
  2. Version number
  3. Text description
  4. Estimated size of database

transaction
Now that we’ve opened our database, we can create transactions. Transactions give us the ability to rollback. This means that if a transaction — which could contain one or more SQL statements — fails (either the SQL or the code in the transaction), the updates to the database are never committed — i.e. it’s as if the transaction never happened.
There are also error and success callbacks on the transaction, so you can manage errors, but it’s important to understand that transactions have the ability to rollback changes.
The transaction is simply a function that contains some code:

var db = openDatabase('mydb', '1.0', 'my first database', 2 *1024 * 1024);
db.transaction(function (tx) {
  // here be the transaction
  // do SQL magic here using the tx object
});

executeSql
executeSql is used for both read and write statements, includes SQL injection projection, and provides a callback method to process the results of any queries you may have written.
Once we have a transaction object, we can call executeSql:

var db = openDatabase('mydb', '1.0', 'my first database', 2 *1024 * 1024);
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE foo (id unique, text)');
});

This will now create a simple table called “foo” in our database called “mydb”. Note that if the database already exists the transaction will fail, so any successive SQL wouldn’t run. So we can either use another transaction, or we can only create the table if it doesn’t exist, which I’ll do now so I can insert a new row in the same transaction:



var db = openDatabase('mydb', '1.0', 'my first database', 2 *1024 * 1024);
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS foo (id unique, text)');
tx.executeSql('INSERT INTO foo (id, text) VALUES (1, "synergies")');
});

Now our table has a single row inside it. What if we want to capture the text from the user or some external source? We’d want to ensure it can’t compromise the security of our database (using something nasty like SQL injection). The second argument to executeSql maps field data to the query, like so:
tx.executeSql('INSERT INTO foo (id, text) VALUES (?, ?)', [id,userValue]);
id and userValue are external variables, and executeSql maps each item in the array argument to the “?”s.

Finally, if we want to select values from the table, we use a callback to capture the results:

tx.executeSql('SELECT * FROM foo', [], function (tx, results){
  var len = results.rows.length, i;
for (i = 0; i < len; i++) {
alert(results.rows.item(i).text);
}
});

(Notice that in this query, there are no fields being mapped, but in order to use the third argument, I need to pass in an empty array for the second argument.)

The callback receives the transaction object (again) and the results object. The results object contains a rows object, which is array-like but isn’t an array. It has a length, but to get to the individual rows, you need to use results.rows.item(i), where i is the index of the row. This will return an object representation of the row. For example, if your database has a name and an age field, the row will contain a name and an age property. The value of the age field could be accessed using results.rows.item(i).age.

Sample example to demonstrate the usage of WEB SQL api’s is as follows:

<html>
<head>
<script type="text/javascript">
var db;
try{
db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
}catch(err){
document.write("Your browser does not support WEB SQL");
}
function checkBrowser(){
if(!db){
document.getElementById('content').innerHTML="";
}
}
function insertToDb(){
var id=document.getElementById("id").value;
var message=document.getElementById("message").value;
db.transaction(function(tx){
var table=document.getElementById("table").value;
tx.executeSql('INSERT INTO '+table+' (id,message) VALUES (?, ?)',[id,message]);
});
displayDbContents();
}
function deleteRow(id){
db.transaction(function(tx){
var table=document.getElementById('table').value;
tx.executeSql('DELETE FROM '+table+' WHERE id=?',[id]);
displayDbContents();
});
}
function displayDbContents(){
db.transaction(function(tx){
var table=document.getElementById("table").value;
tx.executeSql('SELECT * FROM '+table,[],function(tx,results){
var i,len=results.rows.length;
var buffer="<table border='1px'><tr><th>ID</th><th>MESSAGE</th><th>DELETE</th></tr>";
for(i=0;i<len;i++){
buffer=buffer+"<tr><td>"+results.rows.item(i).id+"</td><td>"+results.rows.item(i).message+"</td><td><input id="+results.rows.item(i).id+" type='button' value='Delete' onclick='deleteRow(this.id)'/></td></tr>";
}
buffer=buffer+"</table>";
document.getElementById('result').innerHTML=buffer;
});
});
}
function createTable(){
document.getElementById('table').disabled=true;
document.getElementById('createid').disabled=true;
db.transaction(function (tx) {
var table=document.getElementById('table').value;
tx.executeSql('CREATE TABLE IF NOT EXISTS '+table+' (id unique, message)');
alert("Table created with 2 columns(id, message)");
document.getElementById('id').disabled=false;
document.getElementById('message').disabled=false;
document.getElementById('submit').disabled=false;
displayDbContents();
document.getElementById('drop').disabled=false;
});
}
function dropTable(){
db.transaction(function(tx){
var table=document.getElementById('table').value;
tx.executeSql('DROP TABLE '+table,[]);
clearAll();
document.getElementById('result').innerHTML="";
});
}
function clearAll(){
document.getElementById('table').disabled=false;
document.getElementById('table').value="";
document.getElementById('createid').disabled=false;
document.getElementById('id').disabled=true;
document.getElementById('id').value="";
document.getElementById('message').disabled=true;
document.getElementById('message').value="";
document.getElementById('submit').disabled=true;
}
</script>
</head>
<body onload="checkBrowser()">
<div id="content">
<table width="100%" border="1px">
<tr><td align="center">Create and Insert data</td><td align="center">Table Contents</td></tr>
<tr>
<td width="50%" align="center">
<table>
<tr><td>Create Table</td><td><input type="text" id="table" placeholder="Enter Table Name"/><input type="button" id="createid" value="Create Table" onclick="createTable()"/></td></tr>
<tr><td>ID</td><td><input type="text" id="id" placeholder="Enter ID" disabled/></td></tr>
<tr><td>Message</td><td><input type="text" id="message" placeholder="Enter Message" disabled/></td></tr>
<tr><td><input type="button" value="Submit" id="submit" onclick="insertToDb()" disabled/></td><td></td></tr>
</table>
</td>
<td width="50%" align="center">
<div id="result">
</div>
<input type="button" value="Drop table" onclick="dropTable()" id="drop" disabled/>
</td>
</tr>
</table>
</div>
</body>
</html>


Observations on different browsers:

Internet Explorer


Mozilla Firefox


Opera



Safari



Google Chrome



Browser compatibilities

From the observation made in the test applications, the conclusions are as follows.
Browser
WEB SQL support
Internet ExplorerNo
Mozilla FirefoxNo
OperaYes
SafariYes
Google ChromeYes