问题
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?
回答1:
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.
回答2:
For HTML pages, I just set the HTML <base>
tag. Every relative link (i.e. not starting with scheme or /
) will become relative to it. There is no clean way to grab it immediately by HttpServletRequest
, so we need little help of JSTL here.
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<c:set var="req" value="${pageContext.request}" />
<c:set var="url">${req.requestURL}</c:set>
<c:set var="uri">${req.requestURI}</c:set>
<!DOCTYPE html>
<html lang="en">
<head>
<base href="${fn:substring(url, 0, fn:length(url) - fn:length(uri))}${req.contextPath}/" />
<link rel="stylesheet" href="css/default.css">
<script src="js/default.js"></script>
</head>
<body>
<img src="img/logo.png" />
<a href="other.jsp">link</a>
</body>
</html>
This has in turn however a caveat: anchors (the #identifier
URL's) will become relative to the base path as well. If you have any of them, you would like to make it relative to the request URL (URI) instead. So, change like
<a href="#identifier">jump</a>
to
<a href="${uri}#identifier">jump</a>
In JS, you can just access the <base>
element from the DOM whenever you'd like to convert a relative URL to an absolute URL.
var base = document.getElementsByTagName("base")[0].href;
Or if you do jQuery
var base = $("base").attr("href");
In CSS, the image URLs are relative to the URL of the stylesheet itself. So, just drop the images in some folder relative to the stylesheet itself. E.g.
/css/style.css
/css/images/foo.png
and reference them as follows
background-image: url('images/foo.png');
If you rather like to drop the images in some folder at same level as CSS folder
/css/style.css
/images/foo.png
then use ../
to go to the common parent folder
background-image: url('../images/foo.png');
See also:
- Is it recommended to use the <base> html tag?
回答3:
I agree with tardate. I have paid my attention at filters too and have found the solution in face of project UrlRewriteFilter. The simple configuration like following:
<rule>
<from>^.+/resources/(.*)$</from>
<to>/resources/$1</to>
</rule>
helps to forward all requests for */resources path to /resources pass (including context path prefix). So I can simply put all my images and CSS files under resources folder and continue using relative URLs in my styles for background images and other cases.
回答4:
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.
回答5:
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.
回答6:
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.
回答7:
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)
回答8:
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.
回答9:
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.
回答10:
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.
回答11:
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.
回答12:
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
回答13:
I tend to write a property as part of my core JavaScript library within my. I don't think it's perfect but I think it's the best I've managed to achieve.
First off, I have a module that's part of my app core that is always available
(function (APP) {
var ctx;
APP.setContext = function (val) {
// protect rogue JS from setting the context.
if (ctx) {
return;
}
val = val || val.trim();
// Don't allow a double slash for a context.
if (val.charAt(0) === '/' && val.charAt(1) === '/') {
return;
}
// Context must both start and end in /.
if (val.length === 0 || val === '/') {
val = '/';
} else {
if (val.charAt(0) !== '/') {
val = '/' + val;
}
if (val.slice(-1) !== '/') {
val += '/';
}
}
ctx = val;
};
APP.getContext = function () {
return ctx || '/';
};
APP.getUrl = function (val) {
if (val && val.length > 0) {
return APP.getContext() + (val.charAt(0) === '/' ? val.substring(1) : val);
}
return APP.getContext();
};
})(window.APP = window.APP || {});
I then use apache tiles with a common header that always contains the following:
<script type="text/javascript">
APP.setContext('${pageContext.request['contextPath']}');
// If preferred use JSTL cor, but it must be available and declared.
//APP.setContext('<c:url value='/'/>');
</script>
Now that I've initialized the context I may use getUrl(path)
from anywhere (js files or within jsp/html) which will return an absolute path for the given input string within the context.
Note that the following are both equivalent intentionally. getUrl
will always return an absolute path as a relative path does not need you to know the context in the first place.
var path = APP.getUrl("/some/path");
var path2 = APP.getUrl("some/path");
回答14:
Here's the best way : Context redirect filter Filter must be applied at pre-match extension point and thus use annotation @PreMatching.
Filters implementing this interface must be annotated with @Provider to be discovered by the JAX-RS runtime. Container request filter instances may also be discovered and bound dynamically to particular resource methods.
Explained with sample code:
http://writeulearn.com/java-filters/
来源:https://stackoverflow.com/questions/125359/any-clever-ways-of-handling-the-context-in-a-web-app