Java Web Start is a technology for deploying and updating
desktop Java applications easily from a web server. It's much more
than a launching protocol, however. JWS provides a sandbox for
safely running applications, as well as new compression methods to
speed up downloads, and some useful extensions to make your
application feel at home on the user's desktop. The first part of this
series covered just the basics. This second half will show you
everything you need to do to make your application run safely and
feel professional.
The Java Web Start Sandbox
In the age of viruses, spyware, and Internet Explorer bugs,
security has become one of the most important issues we face as
software developers. Java has always had very good support for
writing secure applications, being designed with security in mind
from the beginning. The lack of memory access and the security
manager ensure that an entire class of security bugs have been
wiped out. Still, there is always the possibility that a malicious
programmer will write a program to purposely delete files or steal
personal information. To tackle this issue, Java Web Start provides
something known as the sandbox.
The sandbox is a restricted environment that Java Web Start
applications run inside by default. Using the security manager and
some custom classloaders, it prevents the program from accessing
any inappropriate resources or damaging the host computer in any
way. In this way, the user is protected from malicious or badly
written programs. If you have ever programmed applets before, you
may think that this is familiar. Well, you are right. The JWS
sandbox was specifically designed with the applet security model in
mind. In fact, with the right JNLP, you can turn any existing applet
into a JWS app without any modifications. The sandbox also provides
some extensions to let you do more than you can with plain applets,
like access local files, use the clipboard, and print.
In practical terms the sandbox restricts your program from
talking to the host environment in any way. Files, network access,
and most system functions are off-limits, as are various ways of
getting around these restrictions, like using custom classloaders,
Runtime.exec() calls, or native libraries.
Well, this all sounds very limiting. How are you supposed to
write an application that does anything useful if you can't
work with files, print, or talk to the network? JWS provides three
options:
You can work within the restrictions. Just like an
applet, the JWS sandbox will let you open a network connection back
to the server from which the application was downloaded. You can load and
save all data on the server, leaving nothing on the client machine
other than the application itself. This works out surprisingly well
for a number of application types. It also reduces maintenance and
support headaches--if there's no data on the client, it can't
become corrupted. If your application can work with the sandbox
restrictions, I highly recommend you go this route.
You can use the JWS Service APIs. The sandbox does allow
your program to have limited access to the unsafe resources as long
as it's under the control of the users. Using the Service APIs in
the javax.jnlp package, you can ask the user to
approve possibly unsafe actions like the opening of a file. The sandbox
will open a file dialog window asking the user to select a file.
Then the file will be passed to your application through a safe
API. This way, the user has control over which file to use, if any,
and your application can read files without using the unsafe
java.io.File API.
The JWS Service APIs give you access to files, the clipboard,
printing, and a limited form of data persistence. Java 1.6
(Mustang) will introduce a new SocketService to give your program
conditional network access. With these services, you can do almost
anything while staying inside the sandbox, provided the user grants
permission.
You can go outside of the sandbox. If your program really,
really needs unsafe access to the host computer (for example, your
program wants to use the JDIC native libraries), then you can ship
your program as a signed and trusted application. This means that
you must digitally sign the components of your program and place
them on your website with the same signature.
The first time a user downloads your signed program, she will be
presented with a dialog box asking her to accept the certificate
and continue with the installation. If she says no, the
installation will be aborted. If she say yes, then your program
will be installed and launched outside of the sandbox, giving you
access to the full Java APIs. This only works, of course, if your
application is signed, because users need to be able to trust that
the program really comes from you. Even when it is signed, you are asking
your users to trust you, and some of them may be hesitant to install
a program that could potentially do anything. I recommend going
with one of the other two options if at all possible, since they
involve less risk to the user's computer and don't display a
potentially scary security dialog box.
Using the Service APIs in an Unsigned Application
If you want your program to work inside of the sandbox but still
access local resources, you need to use the Service APIs. There are
APIs to cover all of the major resources you could need to access
(files, clipboard, printing, etc.), but they all operate pretty much
the same way. When you request an unsafe feature using a Service
API, the sandbox will show the user a dialog box asking
permission. If the user agrees, you will be able to access that
feature. If the user declines, you will get a null value and you'll
be unable to use that feature. For example, suppose the My
Pirate program from the previous installment of this series let users
select a photo from their hard drives to use as their icon in the
game. To do this safely, you would need to use
FileOpenService:
import javax.jnlp.*;
// more imports ...
public class FileOpener extends ActionListener {
public void actionPerformed(ActionEvent evt) {
try {
FileOpenService open = (FileOpenService)
ServiceManager.lookup(
"javax.jnlp.FileOpenService");
FileContents fc = open.openFileDialog(null,null);
// do stuff with the file contents
// like fc.getName();
} catch (Exception ex) {
System.out.println("exception: " + ex);
}
}
}
Above is an ActionListener that asks the user for
a pirate configuration file. First it obtains a
FileOpenService by looking it up from the
ServiceManager and then casting to the right type.
Then it calls the openFileDialog() method with
null for the directory and file type arguments. When
it makes the open call, the sandbox will show a dialog box asking
the user for the file. Once the user selects a file, the action
listener will receive a FileContents object, which has
safe methods for retrieving file information.
Note: Mac OS X supports the javax.jnlp.* APIs
but it does not provide a standalone .jar to compile against as part
of its JDK. You will need to download Sun's JDK for another
platform and compile against jnlp.jar from that.
Being 100 percent Java code, it will work fine on the Mac.
Use Signed Code for Trusted Access
If your program needs full access to the user's computer, then
you must use a signed application. To make your program signed, you
must first digitally sign the .jars in your program, and then change
your JNLP to request full access. Digitally signed .jars require a
keystore, so that's where we'll start.
First you need a keystore. This is a file on your development
computer that stores the actual digital keys to prove that your .jars are
coming from you. It requires a password for access, ensuring that
only you are signing your .jars and not someone who has gained
access to your computer. If you already have professional keys from
a trusted certificate authority (like VeriSign or Thawte), then you can use those. If you
are just doing development work, you can use a self-signed
certificate to try out the process. To generate your own keystore
with a self-signed certificate, use the keytool program
like this:
keytool -genkey -keystore mykeystore -alias mykey
The command above will generate a new key called
mykey and place it in a keystore file called
mykeystore. When you execute the above command, the keytool
application will ask you a series of questions to identify
you and your organization, and will also have you create a password to
protect the keystore. Once your keystore is ready, you can sign your
.jars like this:
jarsigner -keystore mykeystore temp.jar mykey
The jarsigner command above will open the keystore called
mykeystore, retrieve the key named mykey,
and use the key to sign the temp.jar file. Once your
.jars are signed, you can upload them to your web server.
The last step is to change the JNLP file to request full
security permissions, like this:
<?xml version="1.0" encoding="UTF-8"?>
<jnlp codebase="http://mypirate.com/downloads/"
href="mypirate.jnlp">
.... the rest of the JNLP file
<security>
<all-permissions/>
</security>
</jnlp>
With the new JNLP and the signed .jars, Java Web Start will ask
your users to approve the certificate (including the identifying
information you put into the keystore), and then run your
application will full permissions. If you will be signing your .jar
files over and over, you may want to use the signjar Ant
task to automate the process. If you are storing your code and
build files in revision control (like a java.net project, for
example) be sure to put the actual password in a private property
that no one else can see.
For more information on signed .jars, please read the
man page for keytool and this great article, which
shows more examples of using keytool and how to get a
free certificate that works with most browsers' built-in
authorities (meaning you won't get the invalid-certificate
screen).
Use Pack 200 for Faster Downloads
Once you have your program signed and running, you may want to
think about improving its download time. Software has a tendency to
grow larger over time, producing ever-larger files to download. To
speed up the downloads, Java 1.5 introduced a new form of
compression specifically designed for Java code: Pack 200.
Pack 200 was named for the JSR that defined it, and it can
compress some .jars by a factor of five, in addition to the typical
2x gzip compression that the Pack 200 tool also applies. This can
result in compressed .jars being ten or 15 percent of their uncompressed size. For a
widely deployed application, this will greatly speed up
installation time for your users and greatly reduce your bandwidth
bill. Pack 200 only works well on Java bytecode, so if your .jar has
a lot of things in it that aren't class files (like images) you
won't see as much compression. Pack 200 takes advantage of
duplicated information between classes, so larger .jars will compress
better.
Compress Your .Jars
Compressing your .jars with Pack 200 is very easy. Java 1.5 comes
with the pack200 command-line program, which converts a
standard .jar into a packed .jar. For example, to compress
mypirate.jar, you would do this from the command
line:
pack200 mypirate.jar.pack.gz mypirate.jar
This will compress mypirate.jar into a new
mypirate.jar.pack.gz file without removing the
original .jar file. Once packed, you should upload both files to the
web server where you will host your application. More on why you
should upload both in a second.
If you are using signed .jars, the process is slightly more
complicated. pack200 will rearrange your classfiles
and the signature depends on the bytes being in a particular order.
To sign a packed .jar, you will need to pack, unpack, and then
sign the .jar before repacking it. This will preserve the order of
the bytes for the signature to work. You can read more on this
process in the
Pack 200 section of the Java 1.5 deployment guide.
If you are building and deploying your application frequently,
you can use an Ant task from the open source java-pack200-ant-task
project on java.net to integrate Pack 200 into your build
process.
Set Up Your Web Server to Support Pack 200
The server side of Pack 200 is more complicated than the
compression phase. Older Java Web Start clients may not understand
Pack 200, or it may be turned off for some reason. To support all
kinds of JWS clients, Pack 200 has a special negotiation protocol
using HTTP headers to download the right .jar. This means you need
to set up your web server to support these special headers.
Depending on your web server, there are many ways you could do this,
but the easiest way is to use a special servlet that comes with the
1.5 JDK, called JnlpDownloadServlet. The following
instructions are for Tomcat, but you can adapt them for any J2EE
application server.
In the sample/jnlp/servlet/ directory of the Sun J2SE 1.5
JDK, you will find a README file and a couple of .jars. The
JnlpDownloadServlet is in the jnlp-servlet.jar
file. You need to copy this .jar to the WEB-INF/lib directory
of whatever servlet context you will use to serve up your
application .jar files. Then you must modify the web.xml file
to include the following lines:
The first section, servlet, defines the
JnlpDownloadServlet itself. The second section maps
all .jar files to this servlet. This means when the client
requests a .jar file, the request will go through the download
servlet. For any other files (say, image or .jnlp files), the
request will proceed as normal. The servlet will look at the HTTP
headers to detect if the client supports Pack 200. If the client
does support Pack 200 and there are packed .jar files available,
then the servlet will send out the packed versions; otherwise it
will send the normal .jars. This is why you must have both the
standard .jars and the packed ones on your server.
You don't need to modify your JNLP file to use Pack 200. Still
refer to your .jars by the normal name (for example,
mypirate.jar). The JWS client and the download servlet
will transparently use Pack 200 when possible, and of course, older
clients will still use the normal .jars.
To further improve download speed, the
JnlpDownloadServlet also supports .jar diffs for
application updates. This means it sends only the difference
between the old and new .jars instead of the entire new .jar. .Jar
diffs are beyond the scope of this article, but you can read more
about them in the
Download Servlet Guide.
Extra Polish
Java Web Start provides a lot of odds and ends to help you
polish your application. These extras can add a sense of
professionalism. It's worth taking the time to evaluate your
application and use them where appropriate.
Shortcuts
Java Web Start allows the user to create an icon for your
program if the user requests it. However, you can add hints to
prompt the user and request an icon on the desktop. If the user's
operating system has the concept of a Start menu, you can also
specify a particular place in the menu to put your program and any
extra resources. You can do this using the
<menu> element and submenu
attribute in your JNLP file. For example, if I would like the My
Pirate! program to be installed on the desktop and in the
Aye-Soft/My Pirate! menu, then I would need to change
my JNLP file to look like this:
<jnlp codebase="http://mypirate.com/downloads/"
href="mypirate.jnlp">
<information>
<title>My Pirate!</title>
<shortcut>
<!-- put a shortcut on the desktop -->
<desktop/>
<!-- put shortcut in start menu too -->
<menu submenu="Aye-Soft">
</shortcut>
</information>
... the rest of the JNLP
The <desktop> element inside of
<shortcut> asks Java Web Start to put an icon on
the desktop if the user allows it. The <menu
submenu="Aye-Soft"/> element will put a shortcut with the
icon and title of the application (as specified by the
<title> element above) in the
Aye-Soft menu.
Related Content
The newest version of Java Web Start supports links for related
content. Using the related-content element, you can
add a link to extra documentation or other resources that should be
installed in the start menu alongside your application.
<jnlp codebase="http://mypirate.com/downloads/"
href="mypirate.jnlp">
<information>
<title>My Pirate!</title>
<shortcut>
<menu submenu="My Pirate"/>
<desktop/>
</shortcut>
<related-content href="readme.html">
<title>Instructions</title>
</related-content>
... the rest of the JNLP
The <related-content> element in the JNLP
file above will put a link to the readme.html file in the
menu with the title Instructions.
File Associations
You can request that your application be registered to support
certain file types. This means when the user launches a file of the
type you set, your program will be launched. You must specify the
association using both an extension (the characters after the
. in a filename) and a MIME type.
<jnlp codebase="http://mypirate.com/downloads/"
href="mypirate.jnlp">
<information>
<title>My Pirate!</title>
<association extensions="pirate"
mime-type="x-application/pirate"/>
... the rest of the JNLP
The association element in the JNLP above would register the
application as a handler for all .pirate files and all
files with a MIME type of x-application/pirate. You can
set up more than one association by using multiple elements.
Conclusion
Java Web Start is a powerful technology for delivering
applications as easily as web apps, but with the rich interface and
features of standard desktop apps. It is secure, easy to use, and
gives your application fast updating capabilities. For more
information on Java Web Start, take a look at Sun's complete
Java Web Start Developers Guide. For any new application, or
even old ones that you would like to deploy widely, I recommend you
try out Java Web Start and see how it can improve your maintenance
and your users' experience.