在Web应用程序中处理上下文的任何聪明的方法?
在Java中,Web应用程序被捆绑到WAR中。 默认情况下,许多servlet容器将使用WAR名称作为应用程序的上下文名称。
因此myapp.war被部署到http://example.com/myapp 。
问题是,webapp认为它的“根”是“根”,或者简单地说是“/”,而HTML会认为你的应用程序的根是“/ myapp”。
Servlet API和JSP有帮助pipe理的function。 例如,如果在servlet中执行:response.sendRedirect(“/ mypage.jsp”),则容器将预先添加上下文并创buildurl: http : //example.com/myapp/mypage.jsp 。
但是,你不能用HTML中的IMG标签来做到这一点。 如果你使用<img src =“/ myimage.gif”/>你可能会得到一个404,因为你真正想要的是“/myapp/myimage.gif”。
许多框架都有JSP标签,它们也是上下文感知的,在JSP中有不同的方法来创build正确的URL(没有特别的优雅)。
对于编码人员来说,跳出何时使用“应用相对”url与绝对url是一个问题。
最后,Javascript代码的问题需要dynamic地创buildURL,并在CSS内embeddedURL(用于背景图像等)。
我很好奇别人用什么技术来缓解和解决这个问题。 许多人只是踢和硬编码,无论是服务器的根或任何上下文,他们碰巧正在使用。 我已经知道了答案,那不是我要找的。
你是做什么?
您可以使用JSTL来创buildurl。
例如, <c:url value="http://img.dovov.comheader.jpg" />
将作为上下文根的前缀。
对于CSS,这通常不是我的问题。
我有这样的networking根结构:
/ CSS
/图片
在CSS文件中,您只需要使用相对URL(..http://img.dovov.comheader.jpg),而不需要知道上下文根。;
至于JavaScript,对我来说最合适的是在页面头部包含一些常见的JavaScript,如下所示:
<script type="text/javascript"> var CONTEXT_ROOT = '<%= request.getContextPath() %>'; </script>
然后,您可以在所有脚本中使用上下文根(或者,您可以定义一个函数来构buildpath – 可能会更灵活一点)。
显然,这一切都取决于您使用的JSP和JSTL,但是我使用JSF和Facelets,所涉及的技术是相似的 – 唯一真正的区别是以不同的方式获取上下文根。
对于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)也将相对于基础path。 如果你有其中的任何一个,你可以改为相对于请求URL(URI)。 所以,改变一下
<a href="#identifier">jump</a>
至
<a href="${uri}#identifier">jump</a>
在JS中,只要您想将相对URL转换为绝对URL,就可以访问DOM中的<base>
元素。
var base = document.getElementsByTagName("base")[0].href;
或者,如果你做jQuery
var base = $("base").attr("href");
在CSS中,图片url与样式表本身的url相关。 所以,只需要将图像放在相对于样式表本身的某个文件夹中。 例如
/css/style.css /csshttp://img.dovov.comfoo.png
并参考如下
background-image: url('images/foo.png');
如果您想将图像放在与CSS文件夹相同的某个文件夹中
/css/style.css http://img.dovov.comfoo.png
然后使用../
转到共同的父文件夹
background-image: url('..http://img.dovov.comfoo.png');
也可以看看:
- build议使用
<base>
html标签吗?
Servlet API和JSP有帮助pipe理的function。 例如,如果在servlet中执行:response.sendRedirect(“/ mypage.jsp”),则容器将预先添加上下文并创buildurl: http : //example.com/myapp/mypage.jsp 。
啊,也许,也许不是 – 这取决于你的容器和servlet规范!
从Servlet 2.3开始:暴露的新function :
最后,经过一组专家的长时间辩论之后,Servlet API 2.3已经彻底澄清了res.sendRedirect(“/ index.html”)调用servlet在非根环境中执行的情况。 问题在于Servlet API 2.2需要一个不完整的path,比如“/index.html”被servlet容器翻译成一个完整的path,但是并没有说明如何处理上下文path。 如果进行调用的servlet位于path“/ contextpath”的上下文中,则redirectURI应该相对于容器根( http:// server:port / index.html )或上下文根( http:// server:port / contextpath / index.html )? 为了最大限度的便携性,定义行为是必要的。 经过漫长的辩论,专家们select了相对于容器根的翻译。 对于那些想要上下文相关的用户,可以将getContextPath()的输出前置到URI中。
所以不,2.3,你的path不会自动翻译成包含上下文path。
我同意迟到 。 我也注意到了filter,并在项目UrlRewriteFilter中find了解决scheme。 简单的configuration如下:
<rule> <from>^.+/resources/(.*)$</from> <to>/resources/$1</to> </rule>
有助于将* / resourcespath的所有请求转发到/ resources pass(包括上下文path前缀)。 所以我可以简单地把我所有的图像和CSS文件放在资源文件夹下,并在我的样式中继续使用相对URL来处理背景图像和其他情况。
我使用助手类来生成img标签等。这个助手类负责与应用程序的contextPath path的前缀 。 (这有用,但我不太喜欢,如果有人有更好的select,请告诉。)
对于css文件中的path等,我使用Ant构build脚本 ,在生产环境中使用site.production.css,在开发环境中使用site.development.css。
或者,我有时使用一个Ant脚本, 用不同的environents的正确数据replace@ token @ tokens。 在这种情况下,@ contextPAth @ token将被replace为正确的上下文path。
一种select是尽可能使用“平面”应用程序结构和相对URL。
通过“平坦”我的意思是没有你的应用程序根目录下的子目录,可能只是静态内容作为“图像/”几个目录。 您的所有JSP,操作URL,servlet直接位于根目录下。
这并不能完全解决你的问题,但大大简化了它。
Vilmantas在这里说了正确的词:相对URL。
所有你需要在你的IMG做的是使用
<img src="myimage.gif"/>
代替
<img src="/myimage.gif"/>
而且它将与应用程序上下文相关(因为浏览器正在解释要转到的URL)
除了特殊情况,我build议不要使用绝对URL这种方式。 永远。 当另一个web应用程序指向web应用程序中的某些内容时,绝对URL很有用。 在内部 – 当一个资源指向同一个上下文中的第二个资源时 – 资源应该知道它在哪里,所以它应该能够expression到第二个资源的相对path。
当然,你会编写模块化的组件,它们不知道包含它们的资源。 例如:
/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
的相对path呢? 很显然,链接将在/myapp/browse/profile.jsp
上断开,或者在/myapp/home.jsp
上/myapp/home.jsp
。 答案是,将所有URL保存在同一个平面文件path空间中。 也就是说,每个URL在/myapp/
之后都不应该有斜杠。
只要在URL和生成内容的实际文件之间有一些映射,这很容易实现。 (例如在Spring中,使用DispatcherServlet将URL映射到JSP文件或视图。)
有特殊情况。 例如,如果您正在使用Javascript编写浏览器端应用程序,那么维护一个平坦的文件path空间变得越来越困难。 在这种情况下,或者在其他特殊情况下,或者如果您有个人喜好,使用<%= request.getContextPath() %>
来创build绝对path并不是什么大事。
您可以使用request.getContextPath()构build绝对URL,这些URL不是硬编码到特定的上下文。 正如前面的答案所指出的那样,对于JavaScript,您只需在JSP顶部(或者最好在模板中)设置一个variables,并将前缀作为上下文。
这不适用于CSS图像replace,除非你想dynamic生成一个CSS文件,这可能会导致其他问题。 但是既然你知道你的CSS文件与你的图像有关,你可以逃脱相对的URL。
出于某种原因,我在处理相对URL的IE中遇到了麻烦,不得不退回到使用带有设置为上下文的JavaScriptvariables的expression式。 我只是将我的IE图像replace分离到他们自己的文件,并使用IEmacros来拉入正确的。 这不是什么大问题,因为我已经必须这样做来处理透明的PNG了。 这不是很漂亮,但它的工作原理。
我已经使用了大部分这些技术(保存XSLT体系结构)。
我认为问题的关键(和共识)是有一个可能有多个目录的网站。
如果你的目录深度(因为没有一个更好的术语)是不变的,那么你可以依靠像CSS这样的相关URL。
介意,布局不一定要完全平坦,一致。
例如,我们已经完成了/ css,/ js,/ common,/ admin,/ user等层次结构。 把适当的页面和资源放在正确的目录中。 像这样的结构对基于容器的authentication非常有效。
我还将* .css和* .js映射到JSP servlet,并使它们变成dynamic的,以便我可以即时创build它们。
我只是希望有一些我可能错过了。
我绝不认为以下是一个优雅的问题。 事实上,事后看来,我不会推荐这个问题(最有可能)的performance。
我们的Web应用程序的JSP是严格的XML原始数据。 这个原始数据然后被发送到应用了正确的CSS标签的XSL(服务器端),并吐出XHTML。
我们有一个template.xsl,可以通过我们为网站的不同组件获得的多个XSL文件inheritance。 我们的path都是在一个名为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>
使用${applicationScope.contextPath}
将$rootPath
传递到每个文件上我们使用XML的想法,而不是在JSP / Java文件中硬编码,我们不想重新编译。
再一次,解决scheme不是一个好的…但我们曾经使用它一次!
编辑 :事实上,我们问题的复杂性是因为我们无法使用JSP来实现整个视图。 为什么不会有人使用${applicationScope.contextPath}
来检索上下文path? 那对我们来说工作得很好。
当从头开始创build网站时,我会使用@Will的方式 – 旨在获得一致且可预测的url结构,以便您可以坚持使用相关参考。
但是如果你正在更新一个最初构build的,直接在站点根目录“/”下工作的站点(对于简单的JSP站点来说很常见),正式的Java EE打包(其中上下文根将是根)。
这可能意味着很多代码更改。
如果你想避免或推迟代码更改,但仍然确保正确的上下文根引用,我已经testing的技术是使用servletfilter。 这个filter可以被放到一个已经存在的项目中而不会改变任何东西(web.xml除外),并且会把出站HTML中的URL参考重新映射到正确的path,并且确保redirect被正确地引用。
一个示例站点和可用的代码在这里可用: EnforceContextRootFilter-1.0-src.zip注意:实际的映射规则在servlet类中被实现为正则expression式,并且提供了一个相当普遍的全部 – 但是你可能需要修改特定的情况。
顺便说一句,我分叉了一个稍微不同的问题来解决现有的代码从“/”迁移到非根上下文path
我倾向于写一个属性作为我的核心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>
现在,我已经初始化上下文,我可以从任何地方(js文件或jsp / html中)使用getUrl(path)
),它将返回上下文中给定inputstring的绝对path。
请注意以下两点都是故意的。 getUrl
将始终返回一个绝对path,因为相对path不需要您首先知道上下文。
var path = APP.getUrl("/some/path"); var path2 = APP.getUrl("some/path");