Skip to main content

Why can't we get rid of JNLP @codebase?

Posted by kohsuke on July 27, 2009 at 2:57 PM PDT

Java Web Start demands that the JNLP file contains the codebase attribute on the root <jnlp> tag, but this is really problematic.

The reason this is problematic is mainly because of the difficulty for a web application to know its own URL. Think about a web app like Hudson, which can be deployed anywhere, and thus doesn't have any a'priori knowledge of its URL. The closest it gets is the Host header and request URI line, but even this isn't necessarily what the browser has sent to you, for example if a reverse proxy is involved (and this is a very common set up.)

So I was thinking about filing a bug, and started to dig bit deeper into understanding why the codebase attribute is necessary. The first authoritative information I found was "Codebase Determination for JavaSE 6u10", and in it it simply says this is so "for technical reasons."

I eventually found out the root cause in the bug parade:

The Browser downloads the jnlp file
into a temp directory on the client. The browser then launches Java Web Start
with that file as an arg. Java Web Start has no way of knowing where that file
was downloaded from.

This also reminds me of another annoying usability issue with Java Web Start, namely it clutters your desktops with *.jnlp files.

It's bit hard to believe that there's no way to retrieve the original URL from the browser — I can see why it can't be done if JWS depends on the "associated application" mechanism, but shouldn't there be more/better ways to integrate with browsers? After all, this seems like a fairly generally useful capability.

In any case, until then, the only solution is for the application to ask the user to enter the URL. Bummer.

Related Topics >>

Comments

JNLP codebase optional now!

JNLP codebase for webstart application can be optional now, starting in 6u18 with the help of new deployment toolkit functions: http://bugs.sun.com/view_bug.do?bug_id=6866509 Also, codebase for JNLP-style browser based applets is always optional, since it's introduction in 6u10. Please try it out and let us know if you have any feedback. 6u18 is live on java.sun.com now.

Aaand poof, it's gone again

Almost as soon as they added it, it was declared a security vulnerability and removed. This presents a further problem, in that changes were made such that the JNLP file itself must be signed in order to avoid a security warning from JWS. If the codebase is required, you can't sign the JNLP file. Catch-22.

Using JSP to generate JNLP on the fly

You could also make a JSP that generates the JNLP dynamically with the current URL and than give the user the URL of the JSP instead of the JNLP.
This way you can also pass different parameters to the main class for each invocation (Passing them as parameters to the JSP and getting them from the args array of the main method).
(replace the "&amp_" to "&amp")

<%@ page contentType="application/x-java-jnlp-file" language="java" %>
<?xml version="1.0" encoding="utf-8"?>

<%

// When the user navigates to this JSP, the JNLP page is created and returned to the user.
// The browser recognizes that this is a JNLP because of the "application/x-java-jnlp-file" contentType.
// The JNLP is downloaded and saved in a temporary folder and is executed by the browser.
// When the JNLP is executed it uses the JNLP codebase and href to retrieve the JNLP and that is why
// the parameters are concatenated to the JNLP href.
// The parameters are also passed as arguments to the JNLP main class and method where they can be retrieved
// from the arguments array

// Generating the codebase for the JNLP. This URL will be used for downloading this jsp and the jar
String jnlpCodebase = "http://" + request.getLocalAddr() + ":" + request.getLocalPort() + request.getContextPath() + "/jnlp/cp";

// Getting the connection name and URL parameters from the request
final String CONNECTION_NAME_PARAM = "connectionName";
final String CONNECTION_URL_PARAM = "connectionUrl";
String connectionName = request.getParameter(CONNECTION_NAME_PARAM);
String connectionUrl = request.getParameter(CONNECTION_URL_PARAM);

%>

<jnlp spec="6.0+" codebase="<%=jnlpCodebase%>" href="sqlClientJnlp.jsp?<%=CONNECTION_NAME_PARAM + "=" + connectionName + "&" + CONNECTION_URL_PARAM + "=" + connectionUrl%>">
<information>
<title>PKLite SQL Client</title>
<vendor>sfn dot chris at sympatico.ca</vendor>
<homepage href="http://pklite.sourceforge.net"/>
<description>PKLite SQL Client</description>
<description kind="short">PKLite SQL Client is an Open Source Java SQL Client</description>
<offline-allowed/>
</information>
<security>
<all-permissions/>
</security>
<resources>
<jar href="pklite-jnlp.jar" main="true"/>
</resources>
<application-desc main-class="com.pk.PrettyKidJnlp">
<argument><%=connectionName%></argument>
<argument><%=connectionUrl%></argument>
</application-desc>
</jnlp>

Documentation about JNLP applets and more

Starting in release Java SE 6u10, applets can be deployed using JNLP and have capabilities similar to Java Web Start applications (without the hassle of specifying the codebase). If applets will satisfy your business needs, check out the newly released Deployment Tutorial to learn more. The Deployment Tutorial contains the following lessons:

I also had a problem with codebase when I tested JavaFX. As you're not allowed to distribute JavaFX, the solution is to distribute your JavaFX program with a JNLP file to start it. But JNLP doesn't accept codebase="." So you need to set it to codebase="file:///C:/Program%20Files/MyProgram" which of course you can't know before the user install it.

@opinali: agreed.

@kohsuke: To relaunch the app you can just use the icon that JAWS can be configured to create in your desktop. Picking a JNLP file from your download window, temp dir or browser history is not something we should expect from common users. It's a usability failure - the more clicks and confusing dialogs you need to launch a web application, the worse. The only good implementation is: (1) User clicks a Launch button, (2) User waits for the download, (3) App is running. Any extra step simply blows. BTW, Step 2 is also screwed up by JAWS's usage of two different temporary dialogs: first the "Java loading..." dialog, then a second "Downloading application..." dialog. This is a completely unnecessary transition; JAWS should present a single dialog, perhaps adding a download progress bar dynamically after checking that the app is not fully cached. Sun makes massive efforts like JDK 6u10 to fix "core" problems, but consistently fails to fix pretty basic usability issues... BTW, another suggestion: the JRE installer should also set Advanced>JNLP File/MIME Asociation to Always allow, by default. (Current default is Prompt user.) The configurations that produce smoother user experience should all be the defaults, except when there's some security tradeoff.

@gkbrown: like I wrote to malachid, generating JNLP file on the fly doesn't entirely fix the problem, in the presence of reverse proxy. @opinali: good point about the download window, although now that I think about it, I find it useful as I can quickly relaunch the app without going back to the page, so maybe it's not all bad.

@nzcarey: oh, so that's what that was all about. I saw a lot of posts about how to remove it, but never saw what it intended to do. That's a very good point --- and it's fairly expensive if we have to write an extension for every major browsers out there separately. OTOH another benefit of doing this as a browser extension is that JWS no longer has to maintain its own authentication, proxy, or cookie settings, so it improves the user experience. @cayhorstmann: JavaFX seems to be mostly concentrated on enhancing applets so far, so I'm not holding my breath, but indeed it'd be nice if they could improve this, too.

Another problem of JAWS is that the JNLP file will appear in the download window of your browser, and may request user confirmation to be launched. It's a disgrace of user experience even after 6u10. Suggestion: improve the Java Depolyment Toolkit and JavaFX Deployment Toolkit, so the webpage contains a call to a JavaScript function that will fetch the JNLP file and launch JAWS without requiring any temp file (perhaps just pass the whole content of the JNLP as a parameter, or with some other mechanism). Change the JRE installer so the option Advanced>Miscellaneous>Place Java icon in the system tray is disabled by default. Only developers need that. Eliminate JavaFX's stupid first-run EULA dialog. Make Java and JavaFX as transparent and non-intrusive as Flash and Silverlight.

IMO, this has been the biggest drawback to JNLP deployment since its inception. Developers shouldn't have to rely on dynamically generated JNLP to get this behavior.

Good post--this has been an annoying mess for many years. Indeed, one of the reasons that Adobe is eating our lunch is that they have put the engineering resources into a reliable and unobtrusive browser plugin, whereas we have two half-assed, fragile, and gratuitously different mechanisms (applets and JNLP). Historically, JNLP made sense, as a mechanism that Microsoft would have a harder time sabotaging. But those times are long gone. We have a browser plugin, i.e. the applet handler, and it could be engineered to handle out-of-browser applications in a sane way. Maybe now that the JavaFX folks share the pain, things will improve.

The correct solution seems obvious to me: the browser shouldn't be downloading the jnlp file at all, it should be delegating that to javaws. i.e. Whenever the browser sees a URL like jnlp+http://example.com/my-spiffy-app.jnlp it should let javaws handle it.

like

like <object type="jnlp" .../>

The only proper solution is to open .jnlp from browser plugin, instead of downloading it do disk and opening it like regular file. In this case, plugin is aware of .jnlp file URL. Or just declare JNLP application in HTML page with object tag (as applets), like,

The only proper solution is to open .jnlp from browser plugin, instead of downloading it do disk and opening it like regular file. In this case, plugin is aware of .jnlp file URL. Or just declare JNLP application in HTML page with object tag (as applets), like,

r_spilker: if you rely on the referer to pass in the URL of JNLP (for example as a query string), the downside is that it clutters the URL. malachid: it doesn't matter if you dynamically generate it or not. Consider reverse proxy.

I usually just have the JNLP dynamically generated anyway.

The moment someone downloads the application, you can have a small piece of javascript determine the location. You can then either encode that in the download URL of use ajax to send it to the server. That works even if a revered proxy is involved.