39

在 Java 中,Web 应用程序被捆绑到 WAR 中。默认情况下,许多 servlet 容器将使用 WAR 名称作为应用程序的上下文名称。

因此 myapp.war 被部署到http://example.com/myapp

问题是 webapp 认为它的“根”是“根”,或者只是“/”,而 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 和 CSS 中嵌入 URL(用于背景图像等)的 Javascript 代码问题。

我很好奇其他人使用什么技术来缓解和解决这个问题。许多人只是简单地对它进行平底船和硬编码,无论是服务器根目录还是他们碰巧使用的任何上下文。我已经知道那个答案了,这不是我要找的。

你做什么工作?

4

13 回答 13

24

您可以使用 JSTL 创建 url。

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

使用 CSS,这对我来说通常不是问题。

我有一个这样的网络根结构:

/css
/图像

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

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

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

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

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

于 2008-09-24T08:24:51.090 回答
8

对于 HTML 页面,我只是设置了 HTML<base>标签。每个相对链接(即不是以 scheme 或 开头/)都将变得相对于它。没有干净的方法可以立即获取它 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>

然而,这反过来有一个警告:锚(#identifierURL)也将相对于基本路径。如果您有其中任何一个,您希望将其设为相对于请求 URL (URI)。所以,改变像

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

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

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

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');

也可以看看:

于 2011-02-10T11:39:37.137 回答
4

我同意tardate。我也关注过滤器,并在UrlRewriteFilter项目面前找到了解决方案。简单的配置如下:

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

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

于 2011-02-10T09:46:31.960 回答
3

Servlet API 和 JSP 具有帮助管理这一点的工具。例如,如果在 servlet 中执行:response.sendRedirect("/mypage.jsp"),则容器将添加上下文并创建 url: http ://example.com/myapp/mypage.jsp "。

啊,也许,也许不是——这取决于你的容器和 servlet 规范!

Servlet 2.3 开始:公开的新功能

最后,经过一组专家的长时间辩论,Servlet API 2.3 已经一劳永逸地阐明了 res.sendRedirect("/index.html") 调用在非根上下文中执行的 servlet 会发生什么。问题是 Servlet API 2.2 需要像“/index.html”这样的不完整路径才能由 servlet 容器翻译成完整路径,但没有说明如何处理上下文路径。如果进行调用的 servlet 位于路径“/contextpath”的上下文中,则重定向 URI 应该相对于容器根 ( http://server:port/index.html ) 还是上下文根 ( http://服务器:端口/上下文路径/index.html)? 为了获得最大的可移植性,必须定义行为;经过长时间的争论,专家们选择了相对于容器根的翻译。对于那些想要上下文相关的人,您可以将 getContextPath() 的输出添加到您的 URI 中。

所以不,在 2.3 中,您的路径不会自动转换为包含上下文路径。

于 2008-10-04T04:16:23.240 回答
1

我使用了帮助类来生成 img 标签等。这个帮助类负责使用应用程序的 contextPath为路径添加前缀。(这行得通,但我不太喜欢它。如果有人有更好的选择,请告诉。)

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

或者,我有时会使用 Ant 脚本,将 @token@标记替换为不同环境的适当数据。在这种情况下,@contextPAth@ 标记将替换为正确的上下文路径。

于 2008-09-24T06:47:29.293 回答
1

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

“平面”是指您的应用程序根目录下没有子目录,可能只有几个静态内容目录为“images/”。您所有的 JSP、动作 URL、servlet 都直接位于根目录下。

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

于 2008-09-24T07:03:34.777 回答
1

Vilmantas 在这里说对了:相对 URL。

您在 IMG 中需要做的就是使用

<img src="myimage.gif"/>

代替

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

它将与应用程序上下文相关(因为浏览器正在解释要访问的 URL)

于 2008-09-24T17:36:38.640 回答
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() %>在那种情况下,或者在其他特殊情况下,或者只是如果您有个人喜好,使用创建绝对路径并不是什么大不了的事。

于 2008-09-24T17:59:50.940 回答
1

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

这不适用于 CSS 图像替换,除非您想动态生成 CSS 文件,否则可能会导致其他问题。但是,由于您知道 CSS 文件与图像相关的位置,因此您可以摆脱相对 URL。

出于某种原因,我在 IE 处理相对 URL 时遇到了麻烦,不得不回退到使用将 JavaScript 变量设置为上下文的表达式。我只是将我的 IE 图像替换拆分到他们自己的文件中,并使用 IE 宏来提取正确的。这没什么大不了的,因为无论如何我已经不得不这样做来处理透明的 PNG。它不漂亮,但它有效。

于 2008-09-25T07:22:53.943 回答
1

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

我认为问题的症结(和共识)是有一个可能包含多个目录的站点。

如果您的目录深度(因为没有更好的术语)是恒定的,那么您可以在 CSS 之类的东西中依赖相对 url。

请注意,布局不必完全平坦,只要保持一致即可。

例如,我们已经完成了像 /css、/js、/common、/admin、/user 这样的层次结构。将适当的页面和资源放在适当的目录中。拥有这样的结构非常适合基于容器的身份验证。

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

我只是希望还有一些我可能错过的东西。

于 2008-09-26T04:30:36.750 回答
0

绝不声称以下是一个优雅的问题。事实上,事后看来,考虑到(最有可能的)性能下降,我不会推荐这个问题。

我们的 Web 应用程序的 JSP 是严格的 XML 原始数据。然后将这些原始数据发送到应用正确 CSS 标记的 XSL(服务器端),并输出 XHTML。

我们有一个单独的 template.xsl,它将被我们为网站的不同组件拥有的多个 XSL 文件继承。我们的路径都定义在一个名为 paths.xml 的 XSL 文件中:

<?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>

内部链接将在 XML 中,如下所示:

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

这将由我们的 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被传递到每个文件中,${applicationScope.contextPath}我们背后的想法是使用 XML 而不是将其硬编码到 JSP/Java 文件中,我们不想重新编译。

同样,该解决方案根本不是一个好的解决方案……但我们确实使用过一次!

编辑:实际上,我们的问题之所以复杂,是因为我们无法在整个视图中使用 JSP。为什么有人不直接使用${applicationScope.contextPath}来检索上下文路径?那时对我们来说效果很好。

于 2008-09-24T04:29:30.967 回答
0

从头开始创建网站时,我支持@Will——目标是一致且可预测的 url 结构,以便您可以坚持使用相对引用。

但是,如果您将最初构建为直接在站点根“/”(简单 JSP 站点非常常见)下工作的站点更新为正式的Java EE打包(其中上下文根将是根下的某个路径),事情会变得非常混乱)。

这可能意味着大量的代码更改。

如果您想避免或推迟代码更改,但仍要确保正确的上下文根引用,我测试过的一种技术是使用 servlet 过滤器。过滤器可以在不更改任何内容(web.xml 除外)的情况下放入现有项目中,并将出站 HTML 中的任何 url 引用重新映射到正确的路径,并确保正确引用重定向。

此处提供示例站点和可用代码:EnforceContextRootFilter-1.0-src.zip 注意:实际映射规则在 servlet 类中实现为正则表达式,并提供了一个非常通用的包罗万象 - 但您可能需要针对特定​​情况进行修改。

顺便说一句,我提出了一个稍微不同的问题来解决将现有代码库从“/”迁移到非根上下文路径的问题

于 2008-09-27T03:28:14.027 回答
0

我倾向于编写一个属性作为我的核心 JavaScript 库的一部分。我不认为它是完美的,但我认为这是我设法达到的最好的。

首先,我有一个模块,它是我的应用程序核心的一部分,它始终可用

(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 || {});

然后,我使用具有公共标头的 apache 磁贴,该标头始终包含以下内容:

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

现在我已经初始化了我可以getUrl(path)从任何地方(js 文件或 jsp/html 中)使用的上下文,它将返回上下文中给定输入字符串的绝对路径。

请注意,以下两者都是有意等效的。getUrl将始终返回绝对路径,因为相对路径不需要您首先知道上下文。

var path = APP.getUrl("/some/path");
var path2 = APP.getUrl("some/path");
于 2016-09-08T15:27:53.190 回答