tags:

views:

2079

answers:

10

In Java, web apps are bundled in to WARs. By default, many servlet containers will use the WAR name as the context name for the application.

Thus myapp.war gets deployed to http://example.com/myapp.

The problem is that the webapp considers its "root" to be, well, "root", or simply "/", whereas HTML would consider the root of your application to be "/myapp".

The Servlet API and JSP have facilities to help manage this. For example, if, in a servlet, you do: response.sendRedirect("/mypage.jsp"), the container will prepend the context and create the url: http://example.com/myapp/mypage.jsp".

However, you can't do that with, say, the IMG tag in HTML. If you do <img src="/myimage.gif"/> you will likely get a 404, because what you really wanted was "/myapp/myimage.gif".

Many frameworks have JSP tags that are context aware as well, and there are different ways of making correct URLs within JSP (none particularly elegantly).

It's a nitty problem for coders to jump in an out of when to use an "App Relative" url, vs an absolute url.

Finally, there's the issue of Javascript code that needs to create URLs on the fly, and embedded URLs within CSS (for background images and the like).

I'm curious what techniques others use to mitigate and work around this issue. Many simply punt and hard code it, either to server root or to whatever context they happen to be using. I already know that answer, that's not what I'm looking for.

What do you do?

A: 

I by no means claim that the following is an elegant issue. In fact, in hindsight, I wouldn't recommend this issue given the (most likely) performance hit.

Our web app's JSPs were strictly XML raw data. This raw data was then sent into an XSL (server-side) which applied the right CSS tags, and spit out the XHTML.

We had a single template.xsl which would be inherited by the multiple XSL files that we had for different components of the website. Our paths were all defined in an XSL file called paths.xml:

<?xml version="1.0" encoding="UTF-8"?>
<paths>
    <path name="account" parent="home">Account/</path>
    <path name="css">css/</path>
    <path name="home">servlet/</path>
    <path name="icons" parent="images">icons/</path>
    <path name="images">images/</path>
    <path name="js">js/</path>
</paths>

An internal link would be in the XML as follows:

<ilink name="link to icons" type="icons">link to icons</ilink>

This would get processed by our XSL:

<xsl:template match="ilink">
    <xsl:variable name="temp">
        <xsl:value-of select="$rootpath" />
        <xsl:call-template name="paths">
            <xsl:with-param name="path-name"><xsl:value-of select="@type" /></xsl:with-param>
        </xsl:call-template>
        <xsl:value-of select="@file" />
    </xsl:variable>
     <a href="{$temp}" title="{@name}" ><xsl:value-of select="." /></a>
</xsl:template>

$rootPath was passed onto each file with ${applicationScope.contextPath} The idea behind us using XML instead of just hard-coding it in a JSP/Java file was we didn't want to have to recompile.

Again, the solution isn't a good one at all...but we did use it once!

Edit: Actually, the complexity in our issue arose because we weren't able to use JSPs for our entire view. Why wouldn't someone just use ${applicationScope.contextPath} to retrieve the context path? It worked fine for us then.

Swati
+1  A: 

I've used helper classes to generate img tags etc. This helper class takes care of prefixing paths with the application's contextPath. (This works, but I don't really like it. If anyone has any better alternatives, please tell.)

For paths in css files etc. I use an Ant build script that uses a site.production.css for site.css in production environment and site.development.css in development encironment.

Alternatively I sometimes use an Ant script that replaces @token@ tokens with proper data for different environents. In this case @contextPAth@ token would be replaced with the correct context path.

kosoant
+1  A: 

One option is to use "flat" application structure and relative URLs whenever possible.

By "flat" I mean that there are no subdirectories under your application root, maybe just few directories for static content as "images/". All your JSP's, action URLs, servlets go directly under the root.

This doesn't completely solve your problem but simplifies it greatly.

Vilmantas Baranauskas
This won't work if you're in a subdirectory, i.e. /ContextRoot/subdir/page.xhtml. I find it's always best to prefix with a path relative to the context root, so your page will work wherever it is located
Phill Sacre
+4  A: 

You can use JSTL for creating urls.

For example, <c:url value="/images/header.jpg" /> will prefix the context root.

With CSS, this usually isn't an issue for me.

I have a web root structure like this:

/css
/images

In the CSS file, you then just need to use relative URLs (../images/header.jpg) and it doesn't need to be aware of the context root.

As for JavaScript, what works for me is including some common JavaScript in the page header like this:

<script type="text/javascript">
var CONTEXT_ROOT = '<%= request.getContextPath() %>';
</script>

Then you can use the context root in all your scripts (or, you can define a function to build paths - may be a bit more flexible).

Obviously this all depends on your using JSPs and JSTL, but I use JSF with Facelets and the techniques involved are similar - the only real difference is getting the context root in a different way.

Phill Sacre
+2  A: 

Vilmantas said the right word here: relative URLs.

All you need to do in your IMG is to use

<img src="myimage.gif"/>

instead of

<img src="/myimage.gif"/>

and it'll be relative to the app context (as the browser is interpreting the URL to go to)

Scott Stanchfield
While this is true, it is also relative to the existing path. You may end up with something like <img src="../../../myimage.gif"/>
Jesse
+1  A: 

Except for special cases, I'd recommend against using absolute URLs this way. Ever. Absolute URLs are good for when another webapp is pointing at something in your webapp. Internally -- when one resource is pointing at a second resource in the same context -- the resource should know where it lives, so it should be able to express a relative path to the second resource.

Of course, you'll write modular components, which don't know the resource that's including them. For example:

/myapp/user/email.jsp:
Email: <a href="../sendmail.jsp">${user.email}</a>

/myapp/browse/profile.jsp:
<jsp:include page="../user/email.jsp" />

/myapp/home.jsp:
<jsp:include page="../user/email.jsp" />

So, how does email.jsp know the relative path of sendmail.jsp? Clearly the link will break on either /myapp/browse/profile.jsp or it will break on /myapp/home.jsp . The answer is, keep all your URLs in the same flat filepath space. That is, every URL should have no slashes after /myapp/ .

This is pretty easy to accomplish, as long as you have some kind of mapping between URLs and the actual files that generate the content. (e.g. in Spring, use DispatcherServlet to map URLs to JSP files or to views.)

There are special cases. e.g. if you're writing a browser-side application in Javascript, then it gets harder to maintain a flat filepath space. In that case, or in other special cases, or just if you have a personal preference, it's not really a big deal to use <%= request.getContextPath() %> to create an absolute path.

Travis Wilson
A: 

You can use request.getContextPath() to build absolute URLs that aren't hard-coded to a specific context. As an earlier answer indicated, for JavaScript you just set a variable at the top of your JSP (or preferably in a template) and prefix that as the context.

That doesn't work for CSS image replacement unless you want to dynamically generate a CSS file, which can cause other issues. But since you know where your CSS file is in relation to your images, you can get away with relative URLs.

For some reason, I've had trouble with IE handling relative URLs and had to fall back to using expressions with a JavaScript variable set to the context. I just split my IE image replacements off into their own file and used IE macros to pull in the correct ones. It wasn't a big deal because I already had to do that to deal with transparent PNGs anyway. It's not pretty, but it works.

Brian Deterling
+1  A: 

I've used most of these techniques (save the XSLT architecture).

I think the crux (and consensus) of the problem is having a site with potentially multiple directories.

If your directory depth (for lack of a better term) is constant, then you can rely on relative urls in things like CSS.

Mind, the layout doesn't have to be completely flat, just consistent.

For example, we've done hierarchies like /css, /js, /common, /admin, /user. Putting appropriate pages and resources in the proper directories. Having a structure like this works very well with Container based authentication.

I've also mapped *.css and *.js to the JSP servlet, and made them dynamic so I can build them on the fly.

I was just hoping there was something else I may have missed.

Will Hartung
A: 

When creating a site from scratch, I side with @Will - aim for a consistent and predictable url structure so that you can stick with relative references.

But things can get really messy if you are updating a site that was originally built to work directly under the site root "/" (pretty common for simple JSP sites) to formal Java EE packaging (where context root will be some path under the root).

That can mean a lot of code changes.

If you want to avoid or postpone the code changes, but still ensure correct context root referencing, a technique I've tested is to use servlet filters. The filter can be dropped into an existing proejct without changing anything (except web.xml), and will remap any url references in the outbound HTML to the correct path, and also ensure redirects are correctly referenced.

An example site and usable code available here: EnforceContextRootFilter-1.0-src.zip NB: the actual mapping rules are implemented as regex in the servlet class and provide a pretty general catch-all - but you may need to modify for particular circumstances.

btw, I forked a slightly different question to address migrating existing code base from "/" to a non-root context-path

tardate
A: 

The Servlet API and JSP have facilities to help manage this. For example, if, in a servlet, you do: response.sendRedirect("/mypage.jsp"), the container will prepend the context and create the url: http://example.com/myapp/mypage.jsp".

Ah, maybe, maybe not - it depends on your container and the servlet spec!

From Servlet 2.3: New features exposed:

And finally, after a lengthy debate by a group of experts, Servlet API 2.3 has clarified once and for all exactly what happens on a res.sendRedirect("/index.html") call for a servlet executing within a non-root context. The issue is that Servlet API 2.2 requires an incomplete path like "/index.html" to be translated by the servlet container into a complete path, but doesn't say how context paths are handled. If the servlet making the call is in a context at the path "/contextpath," should the redirect URI translate relative to the container root (http://server:port/index.html) or the context root (http://server:port/contextpath/index.html)? For maximum portability, it's imperative to define the behavior; after lengthy debate, the experts chose to translate relative to the container root. For those who want context relative, you can prepend the output from getContextPath() to your URI.

So no, with 2.3 your paths are not automatically translated to include the context path.

tardate