Java Web Start on Google App Engine

Java Web Start is cool technology, which allows to automatically update client application to the latest version. So the latest client application is placed on server side. Unfortunately this technology does not work by default on Google App Engine. This post contains information about how to run it in the Google clouds.

How Web Start checks updates?

“How Web Start checks updates?” is the first question, which is occurred. The answer is simple (but I did not find it in the specification :-(): Web Start getting HTTP ‘Last-Modified’ header for each jar from jnlp-file of your application and compare this date with cached date. If ‘Last-Modified’ more than cached then jar will be updated.

By the way I’d note that speed of “checking updates” depends on count of jars and did not depend on theirs size.

Default headers from Google App Engine

Next I have checked hearers, which Google App Engine returns for client’s jars and found that it did not return ‘Last-Modified’! So ‘Last-Modified’ is null for Java Web Start and thus your application will be not updated automatically.

For checking headers I have used the following code:

public static void main(String[] args) {
for (String each : jarUrls) {
System.out.print(“URL: ” + each);
try {
final URL url = new URL(each);
final URLConnection urlConnection = url.openConnection();
System.out.println(“… connected.”);

final Map> headerMap = urlConnection.getHeaderFields();
for (String name : headerMap.keySet()) {

System.out.println(name + “: ” + headerMap.get(name));
}
urlConnection.getInputStream().close();
System.out.println(“”);
} catch (MalformedURLException e) {
System.out.print(“Url can not be created.”);
e.printStackTrace(System.out);
} catch (IOException e) {
System.out.print(“Url can not be created.”);
e.printStackTrace(System.out);
}
}
}

where jarUrls contains all urls, which should be checked.

Add ‘Last-Modified’ header

The next step is manually add ‘Last-Modified’ header. On this step “How to create a simple but powerful CDN with Google App Engine (GAE)” helped me.

So I have created cacheheaders.py, modified it in accordance to my needs and configure app.yaml.

During creation of my cacheheaders.py I have problem with using os python-module. Thus I have modified way of getting full path to file:

rootpath = os.path.dirname(__file__)
fullPath = rootpath + ‘/../’ + path

Here are the full version of my cacheheaders.py:

class MainPage(webapp.RequestHandler):
def output_file(self, path, lastmod):
import datetime
try:
self.response.headers[‘Cache-Control’] = ‘public, max-age=60’
self.response.headers[‘Last-Modified’] = lastmod.strftime(“%a, %d %b %Y %H:%M:%S GMT”)
expires = lastmod + datetime.timedelta(minutes=1)
self.response.headers[‘Expires’] = expires.strftime(“%a, %d %b %Y %H:%M:%S GMT”)
fh = open(path, ‘r’)
self.response.out.write(fh.read())
fh.close
return
except IOError, e:
self.error(404)
return

def get(self, path):
self.response.headers[‘Content-Type’] = ‘application/java-archive’

rootpath = os.path.dirname(__file__)
fullPath = rootpath + ‘/../’ + path

try:

import datetime
info = os.stat(fullPath)
lastmod = datetime.datetime.fromtimestamp(info[8])
if self.request.headers.has_key(‘If-Modified-Since’):
dt = self.request.headers.get(‘If-Modified-Since’).split(‘;’)[0]
modsince = datetime.datetime.strptime(dt, “%a, %d %b %Y %H:%M:%S %Z”)
if modsince >= lastmod:
# The file is older than the cached copy (or exactly the same)
self.error(304)
return
else:
# The file is newer
self.output_file(fullPath, lastmod)
else:
self.output_file(fullPath, lastmod)
except OSError, e:
logging.getLogger().error(“Library from path %s can not be load: %s” %(path, e))
self.error(404)
return

def main():
application = webapp.WSGIApplication([(r’/(.*)’, MainPage)], debug=False)
wsgiref.handlers.CGIHandler().run(application)

if __name__ == “__main__”:
main()

Also you should configure your app.yaml for using this custom handers:

– url: /dist/.*
script: app/cacheheaders.py

where dist is directory, where your jars are placed.

Setup JNLP mime-type

In additional please don’t forget to setup mime-type for JNLP file – otherwise it will not correctly handled by IE. Here are configuration for app.yaml:

– url: /dist1/client.jnlp
static_files: dist1/client.jnlp
mime_type: application/x-java-jnlp-file
expiration: “1m”
upload: dist1/client.jnlp

Conclusion

In conclusion I’d say that Java Web Start is cool technology, which provides a lot of useful features like:

  • adding shortcuts on first start (supports all OS)
  • installation mode
  • mechanism for handling jar versions
  • etc.