Anyone who has deployed any large application using Java Web Start 1.5 or 1.6 over a slow WAN or through some kind of SSL proxy has noticed the extreme number of connections that can be initiated during application startup. The first deploy of the application works as expected and all the JAR files is downloaded. But the sencond time the user clicks to start the application, Java Web Start will flood the network with connections to the server.
Investigation the problem revelas an interesting piece of code in the class com.sun.javaw.LaunchDownload and the method updateCheck, this function is called once for every JAR file in the JNLP description file. The code does the update check by starting a new thread and doing a HTTP request to the web server to check if the JAR file has been updated.
This means that if we have a JNLP file with 200 JAR files, the code will start 200 threads, creating 200 individual connections to the server!
The solution can be found in the same function as well, as the first lines of the function reads:
// no update check for versioned resource
if (version != null) return;
This means that if we put version attributes on the JAR files we will not invoke the badly written code. To add the version attribute is simple, just add it to the JAR element of the JNLP file:
<jar href="/application/foo.jar" version="”1.0” />
When Java Web Start finds this attribute on a JAR element in the JNLP file it will send this version string along with the GET request to the server. Like:
GET /application/foo.jar?version-id=1.0
To handle this on the server we have two options, if we are running a J2EE environment the JDK contains a JNLP servlet that can respond to this request and also return the required x-java-jnlp-version-id custom header, or we could implement the same functionality in some other kind of server side language.
I choose to implement the JAR server in PHP/Apache since we have noticed that the J2EE container we are using is not very good at serving large amounts of data.
To have Apache invoke my script for every JAR file requested from the server, I added the following lines to the httpd.conf file.
AddType application/java-archive .jar
Action application/java-archive /cgi-bin/jar_send.php
The script is very simple; we have chosen to store the versioned JAR archives
as /application/foo_x_y.jar for version x.y. This makes the script very simple to implement, and make deploying very simple as well. Another strategy could be to store a complete version of the application under /x.y/application/foo.jar and have all JAR archives in the JNLP file reference the same version.
Here is the code for the jar_send.php script:
<?
// Make sure we have got a version-id argument
if (isset($_GET["version-id"])) {
$version = $_GET["version-id"];
} else {
$version = null;
}
// Retreive the requested file
$file = $_SERVER["PATH_TRANSLATED"];
// If not version is requested, or version is 1.0, send foo.jar
if ($version == null || $version == "1.0") {
$path = $file;
} else {
// If version 1.1 is requested, send foo_1_1.jar
$x = strrpos($file,".jar");
$path = substr($file,0,$x);
$path .= "_";
$path .= str_replace(".","_",$version);
$path .= ".jar";
}
// Make sure the file exists
if (!is_file($path)) {
header("HTTP/1.0 404 NOT FOUND");
print $path;
die;
}
// Open the file
$f = fopen($path, 'rb');
if ($f == null) {
header("HTTP/1.0 404 NOT FOUND");
print $path;
die;
}
// Send the JNLP custom header
header("x-java-jnlp-version-id: $version");
// Inform Apache about how much data we are going to send
header("Content-Length: ".(string)(filesize($path)));
// Send the data, 8K blocks
while(!feof($f) &&(connection_status()==0)) {
print(fread($f, 1024*8));
flush();
}
// Close the file
fclose($f);
?>
Tuesday, June 12, 2007
Java Web Start connection problem
Posted by
Stefan Gustafsson
at
8:59 AM
0
comments
Labels: java, java web start, php, ssl
Thursday, March 29, 2007
Template reports
Today I wrote a reporting tool for our Web 2.0 project. I wanted a simple solution that enabled the end customer to create his own reports based on the ones we creates during development.
To make the system flexible I used the Smarty template engine. Smarty works by compiling templates describing layout into PHP files that is when combined with data from an application into a presentation. The normal use of Smarty is to create HTML pages for viewing in a browser, but the code is well written and can be used in many more ways.
I also wanted the SQL queries to be configurable, to allow even more flexibility in the design of the reports. The solution was to use a feature in Smarty that is normally used to avoid hard coding in the templates, the config files. In my solution the report is a config file that specifies the content of the report. A simple report looks like this:
name = Users
desc = This report list all active users in the system.
[list]
Users = SELECT iuser.cn FROM iuser WHERE istatus = 'active'
[screen]
users = users_table.tpl
[print]
head = print_header.tpl
users = users_table.tpl
foot = print_footer.tpl
[excel]
users = users_table.tpl
The application can easily build a list of all the reports in the system by looking at all the files *.rep in the reports directory, using the glob PHP function. The reports supports to be rendered in three different ways, screen, print or excel.
To init the Smarty package, I use:
$smarty = new Smarty;
$smarty->compile_check = true;
$smarty->debugging = false;
$smarty->config_dir = "reports";
$smarty->template_dir = "reports";
$conf = new Config_File("reports");
And to get information about a specific report, I use the Smarty Config_File class to query the parameters from the .rep file:
$res["name"] = $conf->get($report_file,null,'name'); // Get report name
$res["desc"] = $conf->get($report_file,null,'desc'); // Get report description
To generate the report, the PHP code fetches and executes the different queries configured in the report. Two different kinds of queries are supported, queries returning lists and queries returning a single value.
$lists = $conf->get($report_file,'list');
foreach($lists as $name => $query) {
$res = iDbSelect($query);
$smarty->assign($name,$res);
}
$vars = $conf->get($report_file,'var');
foreach($vars as $name => $query) {
$res = iDbSelectOne($query);
$smarty->assign($name,$res);
}
After all the data has been read in from the database, Smarty is called to render the report. The $show variable holds the type of report to generate; this could be screen, print or excel. This makes in possible to have some parts of the report generated the same way independently if the report is intended for the printer, screen or for Excel.
$displays = $conf->get($report_file,$show);
foreach($displays as $display) {
$smarty->display($display);
}
By using this solution we can easily design reports made up from smaller parts that the customer later will be able to reuse in designing his own custom reports.
Posted by
Stefan Gustafsson
at
8:00 PM
0
comments
Monday, March 19, 2007
IE7 does not Excel
The statistical module on one of our web applications has a function to download the data directly into Microsoft Excel. This function has been working like a charm since the application was deployed last year, but now we got problem reports from IE7 users that were unable to use the function.
I did a quick test and found no problem, a not so uncommon situation for anyone involved in software development. I thought about reporting the usual "It works in development" back to the customer when I figured I should do another test using the full test environment. I now got the reported failure; IE7 fails to download the file and a popup is shown instead. Aha! Its HTTPS that's causing this.
After some digging around I found the answer in the header() documentation on php.net. To get file downloads to work for IE7 you must add two more headers to the response:
header('Pragma: private');
header('Cache-control: private, must-revalidate');
This will tell IE7 to allow the encrypted file to be saved locally, a requirement to be able to open the file using Excel. I have not found any pointers about why the functionality was changed between IE6 and IE7.
I hope this saves an hour for you!
Posted by
Stefan Gustafsson
at
11:33 AM
3
comments
