【问题标题】:Get rid of hard-coding the context path of web apps in external JavaScript files摆脱在外部 JavaScript 文件中硬编码 Web 应用程序的上下文路径
【发布时间】:2015-11-27 22:40:40
【问题描述】:

我有几个 WebSockets 端点,例如,

wss://localhost:8181/ContextPath/Push

所有此类端点 URL 都硬编码在单独的外部 JavaScript 文件 (.js) 中。这些 JavaScript 文件在需要时包含在相应的 XHTML 文件中。主机名和上下文路径应以编程方式进行评估,而不是在需要它们的地方进行硬编码。

主机名 (localhost:8181) 可以在 JavaScript 中使用 document.location.host 获取,但在 JavaScript 中没有标准/规范的方式来获取应用程序运行的上下文路径。


我正在做类似以下的事情。

在主模板上声明一个全局 JavaScript 变量,如下所示。

<f:view locale="#{bean.locale}" encoding="UTF-8" contentType="text/html">
        <f:loadBundle basename="messages.ResourceBundle" var="messages"/>

        <ui:param name="contextPath" value="#{request.contextPath}"/>
        <ui:insert name="metaData"></ui:insert>

        <h:head>
            <script type="text/javascript">var contextPath = "#{contextPath}";</script>
        </h:head>

        <h:body id="body">

        </h:body>
    </f:view>
</html>

其中主机名和上下文路径被硬编码的 JavaScript 文件包含在相应的模板客户端或模板的北、南、东和西的任何部分中,如下所示。

<html lang="#{bean.language}"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">

    <h:form>
        <h:outputScript library="default" name="js/websockets.js" target="head"/>
    </h:form>

仅出于观点考虑,websockets.js 如下所示(您可以直接忽略它)。

if (window.WebSocket) {
    // The global variable "contextPath" is unavailable here
    // because it is declared afterwards in the generated HTML.
    var ws = new WebSocket("wss://"+document.location.host + contextPath + "/Push");
    ws.onmessage = function (event) {
        // This handler is invoked, when a message is received through a WebSockets channel.
    };

    $(window).on('beforeunload', function () {
        ws.close();
    });
} else {}

现在,在主模板中声明的全局 JavaScript 变量 contextPath 预计将在包含的 JavaScript 文件中可用,即 websockets.js。然而这是不真实的。

发生的情况是包含的 JavaScript 文件,即 websockets.js,其中试图访问全局变量 contextPath,被放置在生成的 HTML 中硬编码的 &lt;script&gt; 标记之前之前 &lt;head&gt; 主模板中的标签。

换句话说,全局 JavaScript 变量 contextPath 实际上在被声明之前尝试在包含文件 websockets.js 中使用。

无论如何,如何摆脱在外部 JavaScript 文件中硬编码上下文路径?

这样做的唯一目的是与 CSS 文件不同,EL 不在外部 JavaScript 文件中进行评估。因此,#{} 的东西除非放在 XHTML 文件中,否则不会起作用。

【问题讨论】:

    标签: javascript jsf el


    【解决方案1】:

    发生的情况是,试图访问全局变量 contextPath 的名为 websockets.js 的包含的 JavaScript 文件被放置在硬编码的 &lt;script&gt; 标记之前主模板中生成的 HTML &lt;head&gt; 标签

    这是出乎意料的。您在 &lt;h:body&gt; 中声明了 &lt;h:outputScript&gt; 引用 websockets.js 文件和 target="head"。这应该在&lt;h:head&gt; 中已经声明的所有其他脚本资源之后结束。另见 a.o. How to reference CSS / JS / image resource in Facelets template? 毕竟,这似乎是由绑定 HeadRenderer 的 PrimeFaces 引起的,它旨在自动包含一些 CSS 资源并处理 &lt;facet name="first|middle|last"&gt;

    这是值得向 PF 人员报告的问题(如果尚未完成)。同时,最好的办法是通过在 faces-config.xml 中显式注册 JSF 实现自己的 HeadRenderer 来关闭它(前提是您使用的是 Mojarra)。

    <render-kit>
        <renderer>
            <component-family>javax.faces.Output</component-family>
            <renderer-type>javax.faces.Head</renderer-type>
            <renderer-class>com.sun.faces.renderkit.html_basic.HeadRenderer</renderer-class>
        </renderer>
    </render-kit>
    

    并在&lt;h:head&gt; 中明确包含PrimeFaces 主题特定的theme.css

    <h:outputStylesheet library="primefaces-aristo" name="theme.css" />
    

    回到真正的问题,

    无论如何,如何摆脱在外部 JavaScript 文件中硬编码上下文路径?

    将其设置为基本 URI(注意:HTML4 / IE6-8 不支持相对路径)。

    <h:head>
        <base href="#{request.contextPath}/" />
        ...
    </h:head>
    
    var baseURI = $("base").attr("href");
    

    或者将其设置为 HTML 根元素的数据属性。

    <!DOCTYPE html>
    <html lang="en" data-baseuri="#{request.contextPath}/" ...>
        ...
    </html>
    
    var baseURI = $("html").data("baseuri");
    

    与具体问题无关,作为建议,要透明地覆盖 http+ws 和 https+wss,请考虑使用location.protocol 而不是硬编码的wss

    var ws = new WebSocket(location.protocol.replace("http", "ws") + "//" + location.host + baseURI + "Push");
    

    【讨论】:

    • 我使用data-baseuri 选择了最后一种方法。尽管我忽略了旧的/错误的/晦涩的浏览器,但使用这种方法是否有任何警告?与此同时,我遇到了this 链接。从 PrimeFaces 3.0 开始,可以使用 name="first"name="middle"name="last" 定义 &lt;f:facet&gt;,这允许在生成的 HTML &lt;head&gt; 元素中按上述顺序放置资源。 (这里的replace()函数是替换协议名的特殊函数吗?看来不是简单的字符替换函数)。
    • jQuery 担心晦涩难懂的浏览器。回到头部排序,我刚刚意识到你观察到的一切都不是预期的行为。您使用的是哪个 JSF impl/version?我刚刚在 Mojarra 2.2.12 上再次尝试,并且在设置全局变量的脚本之后,以表单声明的脚本刚刚结束在头部的末尾(请注意,您也可以只使用 &lt;h:outputScript&gt;)。至于 JS 中的 String#replace(),它与 Java 中的 String#replaceAll() 基本相同,确实是基于正则表达式。但它最终应该以httpwshttps 替换为wss (保留s)。
    • 我使用相同的版本,Mojarra 2.2.12 和 PrimeFaces 5.2(社区版本)。
    • 尝试注册HeadRenderer时为什么会显示Unable to create a new instance of 'com.sun.faces.ren??derkit.html_basic.HeadRenderer': javax.faces.FacesException: com.sun.faces.ren??derkit.html_basic.HeadRenderer
    • faces-config.xml 文件中没有问号。它们仅显示在以java.lang.Exception: java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: java.lang.RuntimeException: com.sun.faces.config.ConfigurationException: Source Document: jndi:/server/Project-war/WEB-INF/faces-config.xml 开头的异常堆栈跟踪中。 HeadRenderer 类在上述包中可用。
    【解决方案2】:

    以下选项适合您吗?

    1. 在主模板中定义一个隐藏的 html-tag,类似于:

      <span id="pageContextPath" data="#{contextPath}" style="display:none;"></span>
      
    2. 将您的 JavaScript 代码更改为:

      jQuery(document).ready(function ($) {
          if (window.WebSocket) {
              contextPath = $("#pageContextPath").attr("data");
              var ws = new WebSocket("wss://" + document.location.host + contextPath + "/Push");
              //...
          } else {}
      });
      

    我在这里使用了 jQuery。你可以用纯 JavaScript 重写它。但是应该在“文档准备好”之后进行,以确保隐藏标签已经被渲染。否则 js 将找不到该元素。

    【讨论】:

      猜你喜欢
      • 2013-07-16
      • 1970-01-01
      • 2013-09-09
      • 2014-12-13
      • 2012-08-04
      • 2021-06-30
      • 2011-11-14
      • 2013-06-28
      • 2016-01-04
      相关资源
      最近更新 更多