当页面加载时防止资源添加后缀

14
我有一个JSF2应用程序运行良好,但是我在JSF中使用资源束时遇到了问题。所有资源都附加了.xhtml后缀。因此,当在浏览器中加载main.css时,它会变成main.css.xhtml。我想让资源不再附加.xhtml(不介意页面本身)。
有没有办法.xhtml附加到资源上?
理想情况下,我不想改变站点的内部工作方式。我列出了以下几种想法,但我必须说我并不真的喜欢这些方法。希望能找到解决方案?
我正在Glassfish 3.1.2.2上使用Majorra v.2.1.17。

当前Faces Servlet加载如下web.xml(已更新)

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

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

原因

你可能会问我为什么需要这个。嗯,我们正在将我们的应用程序移动到由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
@BalusC 如果在CSS文件中使用类似以下的代码:background: #14311b url("#{resource['templateImages:background.jpg']}") no-repeat 0 0;,由于JSF不会在“非XHTML”文件上运行,因此它将无法正常工作。那么,将“*.css”添加为JSF servlet的模式是否是一个好主意?还是只需使用静态获取即可?那么位于ln=javax.faces的资源呢? - blo0p3r
一个自定义资源处理程序将允许您将/javax.faces.resource/*添加到faces servlet映射中。 - BalusC
@BalusC。如果我这样做,我认为我应该能够像这样访问我的资源:http://localhost:8080/myApp/javax.faces.resource/main03.css?ln=styles。但是这不起作用,但是http://localhost:8080/myApp/javax.faces.resource/main03.css.xhtml?ln=styles可以。我知道我漏掉了什么。 - blo0p3r
不,这并不是微不足道的 :) - BalusC
显示剩余3条评论
2个回答

14
这可以通过自定义ResourceHandler来实现,它在createResource()中返回一个Resource,该资源在Resource#getRequestPath()上返回一个"未映射的"URL。您只需要将默认的JSF资源前缀/javax.faces.resource/*添加到FacesServlet映射的<url-pattern>列表中,以便触发它的执行。

此外,您需要覆盖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),但只有在加载组合组件时才会出现... 有什么想法吗? - blo0p3r
我已经更新了关于这个错误的更多信息,以及我在尝试解决时何时/如何遇到它(在尝试的解决方案中)。 - blo0p3r
1
你真是位绅士和学者。我希望我能说我早就到达了这里,但我错得离谱。不过我必须说,我在这个问题上学到了很多。非常感谢! - blo0p3r
6
FYI:这将在OmniFaces 1.4中实现:http://showcase.omnifaces.org/resourcehandlers/UnmappedResourceHandler - BalusC
1
这太棒了。我感觉这是一个真正的问题。我很高兴看到这可能会在未来帮助其他人。再次感谢你。强烈支持OmniFaces。 - blo0p3r
显示剩余4条评论

3
您可以查看重写。重写允许修改呈现在页面上的URL,并以任何您想要的方式进行修改。您可以像这样添加CDN到您的网站:
.addRule(CDN.relocate("{p}foo-{version}.css")
         .where("p").matches(".*")
         .where("version").matches(".*")
         .to("http://mycdn.com/foo-{version}.css"));

我认为使用 Rewrite 可以很容易地实现您的需求。
请查看示例配置了解 Rewrite 的功能。

仅仅重新编写URL的问题在于它不会被JSF处理。此外,我不想将我的文件放到CDN上,而是要使用我的服务器作为CDN的源。这些文件需要保留在我的服务器上。 - blo0p3r
使用Rewrite,您还可以构建自定义规则,为CDN的获取服务器(通过IP范围或类似方式识别)提供与以前相同的资源,并为所有其他用户正确地重写资源。只是一种想法。 :) - chkal
我可能没有解释清楚或者理解不够透彻。但是,我并没有获取CDN资源。我们的服务器将成为CDN资源。使用JSF,默认情况下,所有资源都会附加“*.xhtml”,因此您无法从动态资源中识别静态资源。 - blo0p3r

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