在 /* 上映射全局前端控制器 servlet 时如何访问静态资源

Posted

技术标签:

【中文标题】在 /* 上映射全局前端控制器 servlet 时如何访问静态资源【英文标题】:How to access static resources when mapping a global front controller servlet on /* 【发布时间】:2010-10-26 14:09:33 【问题描述】:

我已将 Spring MVC 调度程序映射为 /* 上的全局前端控制器 servlet。

<servlet>       
  <servlet-name>home</servlet-name>         
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>     
</servlet>  
<servlet-mapping>       
  <servlet-name>home</servlet-name>         
  <url-pattern>/*</url-pattern>     
</servlet-mapping>

但是,此映射会阻止对 /res/ 文件夹中的 CSS、JS、图像等静态文件的访问。

我怎样才能访问它们?

【问题讨论】:

【参考方案1】:

将控制器 servlet 映射到更具体的 url-pattern(如 /pages/*),将静态内容放入特定文件夹(如 /static)并创建一个 Filter 监听 /*,它透明地继续任何静态的链内容并将请求分派给控制器 servlet 以获取其他内容。

简而言之:

<filter>
    <filter-name>filter</filter-name>
    <filter-class>com.example.Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>controller</servlet-name>
    <servlet-class>com.example.Controller</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>controller</servlet-name>
    <url-pattern>/pages/*</url-pattern>
</servlet-mapping>

在过滤器的doFilter():

HttpServletRequest req = (HttpServletRequest) request;
String path = req.getRequestURI().substring(req.getContextPath().length());

if (path.startsWith("/static")) 
    chain.doFilter(request, response); // Goes to default servlet.
 else 
    request.getRequestDispatcher("/pages" + path).forward(request, response);

不,这不会在浏览器地址栏中以/pages 结尾。它是完全透明的。如有必要,您可以将"/static" 和/或"/pages" 设置为过滤器的init-param

【讨论】:

也许我遗漏了一些东西,但是你的控制器中的 doGet 有什么作用呢?在我的情况下,如果我在通过设置属性更新请求模型后将 RequestDispatcher 转发到“/pages/default.jsp”,我会得到一个无限循环。 @MStodd:只是不要转发回控制器,它已经完成了它的工作。将视图隐藏在/WEB-INF 的某个位置,例如/WEB-INF/pages/default.jsp。有关更多提示,请参阅 this 和 this 答案。 太棒了!我想我把我的 jsps 移出了那里,试图更早地以不同的方式解决这个问题,并没有将它们移回。 这是解决这个问题的绝妙方法。 很好的解决方案,谢谢。我不得不使用 request.getServletPath() 而不是 request.getRequestURI() 因为后者包含 servlet 上下文,因此不匹配 /static。【参考方案2】:

Spring 3.0.4.RELEASE 及更高版本您可以使用

<mvc:resources mapping="/resources/**" location="/public-resources/"/>

如Spring Reference 所示。

【讨论】:

【参考方案3】:

你要做的是在你的 web.xml 中添加一个欢迎文件

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>

然后将其添加到您的 servlet 映射中,这样当有人访问您的应用程序的根目录时,他们会在内部被发送到 index.html,然后映射会在内部将它们发送到您映射到的 servlet

<servlet-mapping>
    <servlet-name>MainActions</servlet-name>
    <url-pattern>/main</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>MainActions</servlet-name>
    <url-pattern>/index.html</url-pattern>
</servlet-mapping>

最终结果:您访问 /Application,但您会看到 /Application/MainActions servlet,而不会中断任何其他根请求。

明白了吗?因此,您的应用程序仍然位于子 url,但当用户转到您网站的根目录时会自动呈现。这使您可以让 /images/bob.img 仍然转到常规位置,但 '/' 是您的应用程序。

【讨论】:

我发现这个解决方案在 Tomcat 上运行良好,我希望自己的 index.template 文件由我自己的 Servlet 处理。我不知道这是对 Servlet 规范的必需解释 - 如果有的话,欢迎文件的第 10.10 节似乎要求容器在检查映射之前提供静态资源(如果存在)......但我'不确定我是否理解正确。 这也是一个相关的补充,在过去的日子里,JavaEE 规范预计“部署者”和“开发者”以及“主机管理员”之间的角色分离。如今,Docker 已成为“可部署单元”,因此,运行应用程序(或 Jetty 或...)的 Tomcat 实例本身就是开发时选择的组件。因此,如果它适用于 your servlet 容器,对于 your 应用程序,请感到高兴。如果您将 Servlet 作为可重用的组件部署到“任何地方”,您只需要担心互操作性。这种情况会发生......几乎从来没有。【参考方案4】:

如果你使用Tomcat,你可以将资源映射到默认的servlet:

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/static/*</url-pattern>
</servlet-mapping>

并使用 url http://context path/static/res/...

访问您的资源

也适用于 Jetty,不确定其他 servlet 容器。

【讨论】:

这是在 App Engine 上,正如最初的提问者所说,而不是在 Tomcat 或 Jetty 中。 这是 Tomcat 中的一个安全漏洞(可以通过这种方式访问​​ WEB-INF 和 META-INF 内容),它已在 7.0.4 中修复(并将移植到 5.x 和 6.x)。 x 也是)。 @BalusC 毫无价值,Tomcat 安全修复程序仍然允许这种技术工作,但映射应该是 /res/* 而不是 /static/res/* 并且应该使用 http 访问资源: //context path/res/(很高兴实际上更接近询问者正在寻找的内容,尽管在错误的 servlet 容器上)【参考方案5】:

在多个 servlet 映射定义中提供具有适当后缀的静态内容解决了已发布的答案之一中的一个 cmets 中提到的安全问题。引用如下:

这是 Tomcat 中的一个安全漏洞(可以通过这种方式访问​​ WEB-INF 和 META-INF 内容),它已在 7.0.4 中修复(也将移植到 5.x 和 6.x)。 – BalusC 2010 年 11 月 2 日 22:44

这对我帮助很大。 以下是我的解决方法:

<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>

【讨论】:

您可以将它添加到您的应用程序的 web.xml 中。您不必编辑 $TOMCAT_HOME/conf/web.xml 中的全局 web.xml。 是的,取决于您的需要。不过,我没有提到全局或应用程序配置。所以你的评论是无关紧要的。【参考方案6】:

我也遇到过这个问题,但从未找到一个很好的解决方案。我最终将我的 servlet 映射到 URL 层次结构的上一级:

<servlet-mapping>       
  <servlet-name>home</servlet-name>             
  <url-pattern>/app/*</url-pattern>     
</servlet-mapping>

现在基本上下文(以及 /res 目录中)的所有内容都可以由您的容器提供。

【讨论】:

我真的很想看到一个更好的解决方案。到处都被 /app/ 前缀卡住真是糟透了。 @pjesi 你可以使用 Tuckey URL Rewrite 来解决这个问题。 您的意思可能是tuckey.org/urlrewrite - 没关系,但在 appengine 上它会为您提供静态内容,因此会占用您的实例时间;我猜@Rahul 想要有一个 servlet 来服务“除了静态文件之外的所有东西”,这样只有在需要时才消耗实例时间【参考方案7】:

从 3.0.4 开始,您应该能够将 mvc:resourcesmvc:default-servlet-handler 结合使用,如 spring 文档中所述以实现此目的。

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-static-resources

【讨论】:

【参考方案8】:

冲突的原因似乎是因为默认情况下,上下文根“/”将由 org.apache.catalina.servlets.DefaultServlet 处理。此 servlet 旨在处理对静态资源的请求。

如果您决定使用自己的 servlet 将其排除在外,以处理动态请求,则该*** servlet 还必须执行由 catalina 原始“DefaultServlet”处理程序完成的任何任务。

如果您通读 tomcat 文档,他们会提到 True Apache (httpd) 在处理静态内容方面比 Apache Tomcat 更好,因为它是专门为此而构建的。我的猜测是因为 Tomcat 默认使用 org.apache.catalina.servlets.DefaultServlet 来处理静态请求。由于它全部包含在 JVM 中,并且 Tomcat 旨在作为 Servlet/JSP 容器,因此他们可能没有将该类编写为超级优化的静态内容处理程序。在那里。它完成了工作。够好了。

但这就是处理静态内容的东西,它位于“/”处。因此,如果您将其他任何东西放在那里,而那个东西不能处理静态请求,哎呀,您的静态资源就在那里。

我一直在寻找相同的答案,而我到处都得到的答案是“如果你不希望它这样做,就不要那样做”。

长话短说,您的配置正在用根本不是静态资源处理程序的东西替换默认的静态资源处理程序。您需要尝试不同的配置才能获得您想要的结果(我也一样)。

【讨论】:

【参考方案9】:

您的应用无法直接访问 App Engine 中的“静态”文件。您要么需要上传它们两次,要么自己提供静态文件,而不是使用静态处理程序。

【讨论】:

应用引擎还是这样吗? @Dave 是的。这是一项基本设计功能 - 静态文件由不涉及您的应用的路径提供。 @Nick - 上传两次是什么意思。我们是否需要单独的应用实例和单独的部署?【参考方案10】:

处理此问题的最佳方法是使用某种 URL 重写。通过这种方式,您可以拥有干净的 RESTful URL,而不是使用任何扩展名,即 abc.com/welcom/register 而不是 abc.com/welcome/resister.html

我使用Tuckey URL,这很酷。

它有关于如何设置你的 web 应用程序的说明。我已经用我的 Spring MVC web 应用程序设置了它。当然,在我想为 Spring 3 验证使用注释之前,一切都很好,比如 @Email@Null 用于域对象。

当我添加 Spring mvc 指令时:

< mvc:annotation-driven  /> 
< mvc:default-servlet-handler />

.. 它打破了良好的 ol Tuckey 代码。显然,&lt; mvc:default-servlet-handler /&gt; 取代了我仍在努力解决的 Tuckey。

【讨论】:

我编辑了这个更多的答案,就是这样。如果您想问一个新问题,请随意 - 但答案不是这样做的地方。如果问题得到解决,请回来更新。【参考方案11】:

我建议尽可能尝试使用过滤器而不是默认 servlet。

其他两种可能性:

自己编写一个 FileServlet。你会发现很多例子,它应该只是通过 URL 打开文件并将其内容写入输出流。然后,用它来服务静态文件请求。

实例化 Google App Engine 使用的 FileServlet 类,并在您需要在给定 URL 提供静态文件时调用该 FileServlet 上的 service(request, response)。

您可以将 /res/* 映射到 YourFileServlet 或其他任何内容以将其从 DispatcherServlets 的处理中排除,或者直接从 DispatcherServlet 调用它。

而且,我不得不问,Spring 文档对这次冲突有什么看法?我没用过。

【讨论】:

【参考方案12】:

将您不想触发 servlet 处理的文件夹添加到 appengine-web.xml 文件的 &lt;static-files&gt; 部分。

我刚刚这样做了,看起来一切都开始正常了。这是我的结构:

/

/pages/<.jsp>

/css

我在&lt;static-files&gt; 部分添加了“/pages/**”和“/css/**”,现在我可以从 servlet doGet 内部转发到 .jsp 文件,而不会导致无限循环。

【讨论】:

我不太明白你在这里的意思。对于我的问题,它是一个单页应用程序,我将编译的 js 文件放在我的 webapp 内的一个文件夹中,但是当我部署到谷歌云时,我无法让我的 index.html 不加载我的 js 和 css 文件。我只是不知道出了什么问题。我得到这些资源文件的 404。【参考方案13】:

在尝试过滤方法但没有成功(由于某种原因没有进入 doFilter() 函数)后,我稍微更改了设置,并找到了一个非常简单的根服务问题解决方案:

而不是提供“ / * ” 在我的主 Servlet 中,我现在只听专用的语言前缀 “EN”、“EN/*”、“DE”、“DE/*”

静态内容由默认 Servlet 提供,空的根请求转到 index.jsp,它使用默认语言调用我的主 Servlet:

(索引页上没有其他内容。)

【讨论】:

【参考方案14】:

我发现使用

<mvc:default-servlet-handler />

在 spring MVC servlet bean 定义文件中为我工作。它将任何未由注册的 MVC 控制器处理的请求传递给容器的原始默认处理程序,该处理程序应将其作为静态内容提供。只要确保您没有注册可以处理所有事情的控制器,它应该可以正常工作。不知道为什么@logixplayer 建议重写 URL;单独使用 Spring MVC 就可以达到他想要的效果。

【讨论】:

我同意,如果您使用的是 Spring,那么添加额外的 URL 重写(例如通过 Tuckey URLRewriteFilter)是浪费时间并添加不必要的配置来解决问题。【参考方案15】:

我找到了一个使用虚拟索引文件的更简单的解决方案。

创建一个映射到“/index.html”的Servlet(或使用您想要响应“/”的那个) (这里提到的解决方案是通过 XML 进行映射,我使用的是 3.0 版本,带有注解 @WebServlet) 然后在名为“index.html”的静态内容的根目录下创建一个静态(空)文件

我使用的是 Jetty,发生的事情是服务器识别文件而不是列出目录,但是当被要求提供资源时,我的 Servlet 代替了控制权。所有其他静态内容均不受影响。

【讨论】:

【参考方案16】:

在 Embedded Jetty 中,我设法通过在 web.xml 中添加“css”目录的映射来实现类似的功能。明确告诉它使用 DefaultServlet:

<servlet>
  <servlet-name>DefaultServlet</servlet-name>
  <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>DefaultServlet</servlet-name>
  <url-pattern>/css/*</url-pattern>
</servlet-mapping>

【讨论】:

【参考方案17】:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<mvc:default-servlet-handler/>
</beans>

如果您想使用基于注释的配置,请使用以下代码

@Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) 
        configurer.enable();
    

【讨论】:

【参考方案18】:

关于 Tomcat,很大程度上取决于特定的版本。有一个错误修复 https://bz.apache.org/bugzilla/show_bug.cgi?id=50026 这意味着与更高版本相比,默认 servlet 的 servlet 映射(除“/”外)在 Tomcat 6.0.29(及更早版本)中的行为不同。

【讨论】:

【参考方案19】:

在 Servlet 规范的“12.2 映射规范”部分中,它说:

仅包含'/'字符的字符串表示“默认”servlet 应用。

所以理论上,你可以让你的 Servlet 映射到 /* 做:

getServletContext().getNamedDispatcher("/").forward(req,res);

...如果您不想自己处理。

但是,在实践中,它不起作用。

在 Tomcat 和 Jetty 中,如果存在映射到 '/*' 的 servlet,则对 getServletContext().getNamedDispatcher('/') 的调用将返回 null

【讨论】:

/* 上的 servlet 覆盖 / 上的 servlet。 / 上的 servlet 只有在没有找到匹配的 servlet 时才会被调用。在这个特定问题中情况并非如此。另见 a.o. ***.com/q/4140448 @BalusC 在实际实现中似乎确实如此,但人们不会从规范中预测到这种行为。如果 `/' 是一个特殊的名字,它就是一个特殊的名字。

以上是关于在 /* 上映射全局前端控制器 servlet 时如何访问静态资源的主要内容,如果未能解决你的问题,请参考以下文章

如何防止静态资源被映射在 /* 上的前端控制器 servlet 处理

谁能解释 servlet 映射?

配置servlet时<url-pattern>的作用是

web应用程序servlet的映射名称的规则及请求过程

用于 RPC 调用的 Servlet 映射

Spring前端控制器