在Web应用程序中处理上下文的聪明方法有哪些?

39
在Java中,Web应用程序被打包成WAR文件。默认情况下,许多Servlet容器将使用WAR名称作为应用程序的上下文名称。
因此,myapp.war被部署到http://example.com/myapp
问题在于Web应用程序将其“根”视为“根”,即“/”,而HTML将应用程序的根视为“/myapp”。
Servlet API和JSP具有帮助管理此问题的功能。例如,在Servlet中,如果您执行:response.sendRedirect("/mypage.jsp"),则容器将添加上下文并创建URL:“http://example.com/myapp/mypage.jsp”。
但是,您无法在HTML中使用IMG标记等进行操作。如果您使用<img src="/myimage.gif"/>,您可能会收到404错误,因为您真正想要的是“/myapp/myimage.gif”。
许多框架也具有上下文感知的JSP标记,并且有不同的方法可以在JSP中创建正确的URL(没有特别优雅的方法)。
对于开发人员来说,何时使用“应用程序相对”URL和绝对URL是一个微妙的问题。
最后,还存在需要动态创建URL的JavaScript代码以及CSS中嵌入的URL(用于背景图像等)的问题。

我很好奇其他人用什么技术来减轻和解决这个问题。许多人只是绕过它并硬编码,无论是到服务器根目录还是到他们正在使用的任何上下文。我已经知道了那个答案,那不是我所寻找的。

你会怎么做?

13个回答

24

您可以使用JSTL创建URL。

例如,<c:url value="/images/header.jpg" />将添加上下文根前缀。

对于CSS,这对我通常不是问题。

我的Web根目录结构如下:

/css
/images

在CSS文件中,您只需要使用相对URL(../images/header.jpg),它就不需要知道上下文根。

至于JavaScript,对我有用的是在页面头部包含一些常见的JavaScript,如下所示:

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

然后,您可以在所有脚本中使用上下文根(或者,您可以定义一个函数来构建路径-可能更灵活一些)。

显然,这都取决于您是否使用JSP和JSTL,但我使用Facelets的JSF,并且所涉及的技术是类似的——唯一真正的区别是以不同的方式获取上下文根。


1
你知道除了使用JSP之外的方法吗?如果能在pom.xml或类似的文件中设置应用程序名称就太好了。 - Simon Bengtsson

8
对于 HTML 页面,我只需设置 HTML 的 <base> 标签。每个相对链接(即不以协议或 / 开头的链接)将成为相对于它的链接。无法通过 HttpServletRequest 立即抓取它,因此我们需要在此处使用 JSTL 进行帮助。
<%@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>

这反过来也有一个注意事项:锚点(#identifier URL)也将相对于基本路径。如果您有任何锚点,您希望将其相对于请求URL(URI)而不是基本路径。因此,请进行如下更改:

<a href="#identifier">jump</a>

为了

<a href="${uri}#identifier">jump</a>

在JS中,您可以随时从DOM访问<base>元素,以将相对URL转换为绝对URL。

var base = document.getElementsByTagName("base")[0].href;

如果你使用jQuery的话
var base = $("base").attr("href");

CSS中,图片URL是相对于样式表本身的URL。因此,只需将图像放置在与样式表本身相关的某个文件夹中即可。例如:

/css/style.css
/css/images/foo.png

并将它们作为以下方式的参考。
background-image: url('images/foo.png');

如果你更喜欢将图片放在与CSS文件夹同级别的某个文件夹中,可以这样做。
/css/style.css
/images/foo.png

然后使用../返回到共同的父文件夹

background-image: url('../images/foo.png');

参见:


4

我同意 tardate 的观点。我也曾关注过过滤器,并在项目UrlRewriteFilter中找到了解决方案。下面是一个简单的配置:

<rule>
    <from>^.+/resources/(.*)$</from>
    <to>/resources/$1</to>
</rule>

帮助将 */resources 路径的所有请求转发到 /resources 路径(包括上下文路径前缀)。因此,我可以将所有的图像CSS文件放在资源文件夹下,并继续在我的样式中使用相对URL来设置背景图片和其他情况。


3

Servlet API和JSP提供了帮助管理此类情况的工具。例如,在servlet中执行以下操作:response.sendRedirect("/mypage.jsp"),容器将添加上下文并创建URL:http://example.com/myapp/mypage.jsp

嗯,也许是这样,也许不是 - 这取决于您的容器和Servlet规范!

来自Servlet 2.3:公开的新功能

最终,在一组专家的长时间辩论后,Servlet API 2.3终于明确了对于在非根上下文中执行的servlet调用res.sendRedirect("/index.html")时会发生什么。问题在于,Servlet API 2.2要求不完整的路径如"/index.html"由servlet容器转换为完整路径,但没有说明上下文路径如何处理。如果进行调用的servlet位于路径为"/contextpath"的上下文中,重定向URI应该相对于容器根目录(http://server:port/index.html)还是上下文根目录(http://server:port/contextpath/index.html)?为了最大程度的可移植性,有必要定义这种行为;经过长时间的辩论,专家们选择相对于容器根目录进行转换。对于那些想要上下文相关的用户,可以在URI前面添加getContextPath()的输出。
所以,在2.3中,您的路径不会自动转换为包括上下文路径。

1

我使用辅助类来生成img标签等。这个辅助类负责在应用程序的contextPath前缀路径。(这个方法可行,但我并不是很喜欢。如果有更好的替代方案,请告诉我。)

对于css文件中的路径等,我使用一个Ant构建脚本,在生产环境中使用site.production.css作为site.css,在开发环境中使用site.development.css。

另外,我有时会使用一个Ant脚本,替换@token@标记以获取不同环境的正确数据。在这种情况下,@contextPAth@标记将被替换为正确的上下文路径。


1

一种选择是尽可能使用“平面”应用程序结构和相对URL。

所谓“平面”,就是在应用程序根目录下没有子目录,可能只有一些静态内容的目录,如“images/”。所有JSP、操作URL和servlet都直接放在根目录下。

这并不能完全解决你的问题,但大大简化了它。


如果你在子目录中,例如/ContextRoot/subdir/page.xhtml,这将无法工作。我发现最好总是使用相对于上下文根路径的前缀,这样无论页面位于何处都可以工作。 - user7094

1

Vilmantas在这里说了正确的话:相对URL。

你只需要在IMG中使用相对URL即可。

<img src="myimage.gif"/>

替代

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

这将与应用程序上下文相关(因为浏览器正在解释URL以进行导航)


4
虽然这是正确的,但也要结合现有路径来看。你最终可能会得到类似于 <img src="../../../myimage.gif"/> 的东西。 - Jesse

1

除了特殊情况外,我建议不要以这种方式使用绝对URL。永远不要。当其他Web应用程序指向您的Web应用程序中的某个内容时,绝对URL非常有用。在内部-当一个资源指向同一上下文中的第二个资源时-该资源应该知道它在哪里,因此应该能够表达到第二个资源的相对路径。

当然,您将编写模块化组件,这些组件不知道包含它们的资源。例如:

/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" />

那么,email.jsp 如何知道 sendmail.jsp 的相对路径呢?显然,链接将在 /myapp/browse/profile.jsp 或者 /myapp/home.jsp 上中断。答案是,在同一扁平文件路径空间中保留所有 URL。也就是说,每个 URL 在 /myapp/ 后面不应该有斜杠。

只要您有一些 URL 与生成内容的实际文件之间的映射(例如,在 Spring 中,使用 DispatcherServlet 将 URL 映射到 JSP 文件或视图),这就很容易实现。

还有特殊情况。例如,如果您正在使用 Javascript 编写浏览器端应用程序,则很难维护扁平文件路径空间。在这种情况下,或其他特殊情况下,或者仅仅因为您有个人偏好,使用 <%= request.getContextPath() %> 创建绝对路径并不是什么大问题。


1

您可以使用request.getContextPath()来构建绝对URL,而不是硬编码到特定的上下文中。正如早期的答案所示,对于JavaScript,您只需在JSP的顶部(或最好在模板中)设置一个变量,并将其作为上下文前缀。

这对于CSS图像替换不起作用,除非您想动态生成CSS文件,这可能会引起其他问题。但是,由于您知道CSS文件与图像的关系,因此可以使用相对URL。

出于某种原因,我在处理相对URL时遇到了IE的问题,不得不回退到使用表达式和JavaScript变量设置为上下文。我只是将我的IE图像替换拆分成它们自己的文件,并使用IE宏来拉入正确的文件。这并不是很美观,但它有效。


1

我已经使用了大部分这些技术(除了XSLT架构)。

我认为问题的关键(和共识)是拥有一个可能包含多个目录的网站。

如果你的目录深度(暂时没有更好的说法)是固定的,那么你可以依赖于CSS等内容中的相对URL。

注意,布局不一定要完全扁平化,只需要保持一致性即可。

例如,我们曾经创建过像/css、/js、/common、/admin和/user这样的层次结构。将适当的页面和资源放置在正确的目录中。这样的结构非常适合基于容器的身份验证。

我还将*.css和*.js映射到JSP servlet,并使其动态生成,以便我可以随时构建它们。

我只是希望能够找到可能遗漏的其他方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接