防止在页面加载时将后缀添加到资源中

Posted

技术标签:

【中文标题】防止在页面加载时将后缀添加到资源中【英文标题】:Prevent suffix from being added to resources when page loads 【发布时间】:2013-02-04 12:06:03 【问题描述】:

我有一个 JSF2 应用程序正在运行并且工作没有问题。我在 JSF 中遇到的问题是资源包。所有资源都附加了.xhtml 后缀。所以main.css 在浏览器中加载时变为main.css.xhtml。我想拥有它,这样.xhtml 就不会附加到资源中(不要介意页面本身)。

有没有办法让我们.xhtml 附加到资源中?

理想情况下,我不必更改网站的内部运作方式。我在下面列出了一些想法,但我不得不说我不是很喜欢这些。希望在某个地方找到解决方案?

我在 Glassfish 3.1.2.2 上使用 Majorra v.2.1.17。

在 web.xml 中加载当前 Faces Servlet(更新)

<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/javax.faces.resource/*</url-pattern>
</servlet-mapping>

为什么这个问题与其他问题不同

JSF 2 resources with CDN?。我不希望将我的资源放在 CDN 上,而是让我的资源保留在我的服务器上,但被推送到 CDN。 Change /javax.faces.resource prefix of resource URLs。我不想更改前缀。我只想更改后缀。我希望 &lt;link type="text/css" rel="stylesheet" href="/javax.faces.resource/main03.css.xhtml?ln=styles"&gt; 成为:&lt;link type="text/css" rel="stylesheet" href="/javax.faces.resource/main03.css?ln=styles"&gt; 没有 .xhtml 扩展。 Changing JSF prefix to suffix mapping forces me to reapply the mapping on CSS background images。因为我没有加载资源的问题。该网站正常工作,我们只是很难区分网页和资源(因为我们只查看扩展名)。

推理

当然你可能会问我为什么需要这个。好吧,我们正在将我们的应用程序转移到由 Akamai CDN 提供服务。

我们在网站集成方面遇到的问题是我们试图在边缘服务器上缓存静态内容。这是通过匹配文件扩展名(即:.js、.doc、.png、css 等)来完成的。我们无法匹配xhtml,因为这将缓存所有页面以及静态内容。这会导致会话等问题。

尝试的解决方案

根据 BalusC 的回答,我已经按照建议实现了资源处理程序。我不会在这里重写代码,因为它在下面的答案中。

但是,我在加载复合组件时遇到错误。我收到这样的错误:

WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
    at com.sun.faces.application.ApplicationImpl.createComponent(ApplicationImpl.java:975)
    at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler.createComponent(CompositeComponentTagHandler.java:162)
    at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.createComponent(ComponentTagHandlerDelegateImpl.java:494)
    at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:169)
...

复合组件已正确加载,因为如果我“取消注册”我们刚刚创建的新 ResourceHandler,它将加载。堆栈跟踪使我相信它试图在 java 类中找到这个组件,而不是在资源中找到它。根据grepcode,这将是发生错误的最后一行(975):

String packageName = componentResource.getLibraryName();
String className = componentResource.getResourceName();
className = packageName + '.' + className.substring(0, className.lastIndexOf('.'));

意味着resourceName,又名classNamenull,因为我得到的错误是java.lang.NullPointerException。我似乎无法弄清楚 ResourceHandler 相对于复合组件的调用方式/位置。对解决最后一个问题有帮助吗?

【问题讨论】:

创建自定义资源处理程序。 @BalusC 然后会产生类似的东西(例如在 css 文件中):background: #14311b url("#resource['templateImages:background.jpg']") no-repeat 0 0; 不起作用,因为 jsf 不会在“非 xhtml”文件上运行。那么添加“*.css”作为 jsf servlet 的模式是否是个好主意?还是简单地使用静态获取?位于ln=javax.faces 的资源呢? 自定义资源处理程序允许您将 /javax.faces.resource/* 添加到 faces servlet 映射。 @BalusC。如果我这样做,我想我可以像这样访问我的资源:@​​987654346@。这不起作用,但http://localhost:8080/myApp/javax.faces.resource/main03.css.xhtml?ln=styles 可以。我知道我错过了什么。 更正。所以这是我的误解,只是添加前缀我仍然会以同样的方式使用它。所以/javax.faces.resource/* 被添加到servlet 映射中。但是现在要去:javax.faces.resource/resources/styles/main03.css 访问这个表,样式表仍然没有被 jsf 处理 【参考方案1】:

这可以通过自定义ResourceHandler 来实现,它在createResource() 中返回一个Resource,而Resource 又在Resource#getRequestPath() 上返回一个“未映射”的 URL。您只需要将默认的 JSF 资源前缀 /javax.faces.resource/* 添加到 FacesServlet 映射的 &lt;url-pattern&gt; 列表中,就可以触发它。

此外,您需要覆盖 isResourceRequest() 以检查 URL 是否以 JSF 资源前缀开头以及 handleResourceRequest() 以定位和流式传输正确的资源。

总而言之,应该这样做:

public class UnmappedResourceHandler extends ResourceHandlerWrapper 

    private ResourceHandler wrapped;

    public UnmappedResourceHandler(ResourceHandler wrapped) 
        this.wrapped = wrapped;
    

    @Override
    public Resource createResource(final String resourceName, final String libraryName) 
        final Resource resource = super.createResource(resourceName, libraryName);

        if (resource == null) 
            return null;
        

        return new ResourceWrapper() 

            @Override
            public String getRequestPath() 
                ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
                String mapping = externalContext.getRequestServletPath();

                if (externalContext.getRequestPathInfo() == null) 
                    mapping = mapping.substring(mapping.lastIndexOf('.'));
                

                String path = super.getRequestPath();

                if (mapping.charAt(0) == '/') 
                    return path.replaceFirst(mapping, "");
                
                else if (path.contains("?")) 
                    return path.replace(mapping + "?", "?");
                
                else 
                    return path.substring(0, path.length() - mapping.length());
                
            

            @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
            public String getResourceName() 
                return resource.getResourceName();
            

            @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
            public String getLibraryName() 
                return resource.getLibraryName();
            

            @Override // Necessary because this is missing in ResourceWrapper (will be fixed in JSF 2.2).
            public String getContentType() 
                return resource.getContentType();
            

            @Override
            public Resource getWrapped() 
                return resource;
            
        ;
    

    @Override
    public boolean isResourceRequest(FacesContext context) 
        return ResourceHandler.RESOURCE_IDENTIFIER.equals(context.getExternalContext().getRequestServletPath());
    

    @Override
    public void handleResourceRequest(FacesContext context) throws IOException 
        ExternalContext externalContext = context.getExternalContext();
        String resourceName = externalContext.getRequestPathInfo();
        String libraryName = externalContext.getRequestParameterMap().get("ln");
        Resource resource = context.getApplication().getResourceHandler().createResource(resourceName, libraryName);

        if (resource == null) 
            super.handleResourceRequest(context);
            return;
        

        if (!resource.userAgentNeedsUpdate(context)) 
            externalContext.setResponseStatus(HttpServletResponse.SC_NOT_MODIFIED);
            return;
        

        externalContext.setResponseContentType(resource.getContentType());

        for (Entry<String, String> header : resource.getResponseHeaders().entrySet()) 
            externalContext.setResponseHeader(header.getKey(), header.getValue());
        

        ReadableByteChannel input = null;
        WritableByteChannel output = null;

        try 
            input = Channels.newChannel(resource.getInputStream());
            output = Channels.newChannel(externalContext.getResponseOutputStream());

            for (ByteBuffer buffer = ByteBuffer.allocateDirect(10240); input.read(buffer) != -1; buffer.clear()) 
                output.write((ByteBuffer) buffer.flip());
            
        
        finally 
            if (output != null) try  output.close();  catch (IOException ignore) 
            if (input != null) try  input.close();  catch (IOException ignore) 
        
    

    @Override
    public ResourceHandler getWrapped() 
        return wrapped;
    


faces-config.xml注册如下:

<application>
    <resource-handler>com.example.UnmappedResourceHandler</resource-handler>
</application>

使用ResourceHandler.RESOURCE_IDENTIFIER 扩展FacesServlet URL 模式:

<servlet-mapping>
    <servlet-name>facesServlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
    <url-pattern>/javax.faces.resource/*</url-pattern>
</servlet-mapping>

【讨论】:

这太棒了。我想说我很接近,但只是在结构上。我从没想过要覆盖handleResourceRequest,我也不知道怎么做。我收到此错误:WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception java.lang.NullPointerException at com.sun.faces.application.ApplicationImpl.createComponent(ApplicationImpl.java:974) 但仅在加载复合组件时...有什么想法吗? 我已经更新了问题,提供了有关此错误以及我何时/如何得到它的更多信息(在尝试的解决方案中)。 您好先生是一位绅士和一位学者。我希望我说我会到达这里,但我已经走了。我不得不说我在这个问题上学到了很多东西。非常感谢!! 仅供参考:这将在 OmniFaces 1.4 中:showcase.omnifaces.org/resourcehandlers/UnmappedResourceHandler 这太棒了。我觉得好像这是一个真正的问题。我很高兴看到这可能会在未来帮助其他人。再次感谢你。 OmniFaces 的坚定支持者。【参考方案2】:

你可以看看Rewrite。重写允许修改呈现到页面的 URL 并以您想要的任何方式修改它们。您可以执行以下操作将 CDN 添加到您的网站:

.addRule(CDN.relocate("pfoo-version.css")
         .where("p").matches(".*")
         .where("version").matches(".*")
         .to("http://mycdn.com/foo-version.css"));

我认为使用 Rewrite 实现您的要求应该很容易。

看看example configurations,了解重写的特点。

【讨论】:

只重写 URL 的问题是它不会被 JSF 处理。另外,我不希望将文件放入 CDN,而是将我的服务器用作 CDN 的来源。这些文件需要保留在我的服务器上。 借助 Rewrite,您还可以构建自定义规则,像以前一样为 CDN 的获取服务器(由 IP 范围或类似的东西标识)提供资源,并为所有其他用户正确重写资源。只是一个想法。 :) 我可能没有正确解释自己或没有正确理解。但是,我没有获取 CDN 资源。我们的服务器将成为 CDN 资源。使用 JSF,默认情况下,*.xhtml 附加到所有资源,因此您无法从动态资源中识别静态资源。

以上是关于防止在页面加载时将后缀添加到资源中的主要内容,如果未能解决你的问题,请参考以下文章

在页面加载时将正斜杠附加到url的末尾

页面加载时将对象隐藏在屏幕外

表单自动仅提交一次并重新加载相同的页面

如何在codebehind的pageload事件被触发之前,在页面加载时将一个会话存储值传递到文本框中?

vue-router路由懒加载

用于加载联系人的Phonegap Jquery Pagination - 当用户到达最后一页时如何动态添加页面?