如何使用 DefaultServlet DefaultServletHttpRequestHandler 来处理静态资源 您所在的位置:网站首页 servlet处理静态资源 如何使用 DefaultServlet DefaultServletHttpRequestHandler 来处理静态资源

如何使用 DefaultServlet DefaultServletHttpRequestHandler 来处理静态资源

2023-08-27 13:24| 来源: 网络整理| 查看: 265

我们都知道 Tomcat 是 Servlet 容器, 而 DefaultServlet 就是 Tomcat 的 Servlet 实现, 能够处理对静态资源的 HttpServletRequest 请求 然而它既不是 Spring MVC 的组件, 也很难实例化(反正我是失败了)

如果能够使用 DefaultServlet 来提供容器的服务就好了, 经过研究发现 Spring 框架提供了一个类: DefaultServletHttpRequestHandler , 它能转发静态资源的请求 源代码如下:

package org.springframework.web.servlet.resource; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestHandler; import org.springframework.web.context.ServletContextAware; public class DefaultServletHttpRequestHandler implements HttpRequestHandler, ServletContextAware { /** Default Servlet name used by Tomcat, Jetty, JBoss, and GlassFish */ private static final String COMMON_DEFAULT_SERVLET_NAME = "default"; /** Default Servlet name used by Google App Engine */ private static final String GAE_DEFAULT_SERVLET_NAME = "_ah_default"; /** Default Servlet name used by Resin */ private static final String RESIN_DEFAULT_SERVLET_NAME = "resin-file"; /** Default Servlet name used by WebLogic */ private static final String WEBLOGIC_DEFAULT_SERVLET_NAME = "FileServlet"; /** Default Servlet name used by WebSphere */ private static final String WEBSPHERE_DEFAULT_SERVLET_NAME = "SimpleFileServlet"; private String defaultServletName; private ServletContext servletContext; /** * Set the name of the default Servlet to be forwarded to for static resource requests. */ public void setDefaultServletName(String defaultServletName) { this.defaultServletName = defaultServletName; } /** * If the {@code defaultServletName} property has not been explicitly set, * attempts to locate the default Servlet using the known common * container-specific names. */ @Override public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; if (!StringUtils.hasText(this.defaultServletName)) { if (this.servletContext.getNamedDispatcher(COMMON_DEFAULT_SERVLET_NAME) != null) { this.defaultServletName = COMMON_DEFAULT_SERVLET_NAME; } else if (this.servletContext.getNamedDispatcher(GAE_DEFAULT_SERVLET_NAME) != null) { this.defaultServletName = GAE_DEFAULT_SERVLET_NAME; } else if (this.servletContext.getNamedDispatcher(RESIN_DEFAULT_SERVLET_NAME) != null) { this.defaultServletName = RESIN_DEFAULT_SERVLET_NAME; } else if (this.servletContext.getNamedDispatcher(WEBLOGIC_DEFAULT_SERVLET_NAME) != null) { this.defaultServletName = WEBLOGIC_DEFAULT_SERVLET_NAME; } else if (this.servletContext.getNamedDispatcher(WEBSPHERE_DEFAULT_SERVLET_NAME) != null) { this.defaultServletName = WEBSPHERE_DEFAULT_SERVLET_NAME; } else { throw new IllegalStateException("Unable to locate the default servlet for serving static content. " + "Please set the 'defaultServletName' property explicitly."); } } } @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName); if (rd == null) { throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" + this.defaultServletName + "'"); } rd.forward(request, response); } }

我们可以写一个控制器, 提供一个注解了 @RequestMapping 但没有映射路径的方法, 它将成为除了 *.jsp 之外的所有请求的最后一道 Handler . (Spring 5.2.0 好像删除了该特性) .jsp 请求是不被 DispatcherServlet 处理的, 这将导致不由 Spring 控制的 404 等错误, 即使 Servlet Mapping 设置的是 "/". 要想 DispatcherServlet 真正意义上地处理所有请求, 可以加上 ".jsp" 映射, 不过这将导致 *.jsp 请求无法被编译, 它最多作为文本文件发送给用户.

需要注意的是 DefaultServletHttpRequestHandler 需要调用 setServletContext() 注入一个 ServletContext 实例, ServletContext 实例可以通过 WebApplicationContext 实例获得, 同时它们都是 Spring 框架的组件, 可以在组件链上自动填充.

package spring.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; import develon.lib.Log; import spring.tool.ContextTool; @Controller public class DefautlController { public static DefaultServletHttpRequestHandler defaultServletHandler = null; // 该对象可以转发静态资源请求到容器, 但是无法处理 .jsp 文件 { if (defaultServletHandler == null) { defaultServletHandler = new DefaultServletHttpRequestHandler(); defaultServletHandler.setServletContext(ContextTool.getServletContext()); } } @RequestMapping(name = "default") public void forwardToDefaultServlet(HttpServletRequest request, HttpServletResponse response) { try { defaultServletHandler.handleRequest(request, response); Log.d("代理: " + request.getRequestURI() + "->" + response.getStatus()); } catch (Exception e) { e.printStackTrace(); response.setStatus(500); } } }

现在我们甚至可以不需要显示配置默认 Servlet 的处理了,

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

看看效果如何:

我们可以做更多事情, 比如对静态资源请求增加判断, 判断文件是否存在, 存在再转发到 DefaultServlet 上

package spring.controller; import java.io.File; import java.util.HashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; import spring.tool.ContextTool; @Controller public class DefautlController { public static DefaultServletHttpRequestHandler defaultServletHandler = null; // 该对象可以转发静态资源请求到容器, 但是无法处理 .jsp 文件 public static HashMap staticFiles = new HashMap(); // 用哈希表存放请求文件是否存在的缓存, 避免每次都访问文件系统 { if (defaultServletHandler == null) { defaultServletHandler = new DefaultServletHttpRequestHandler(); defaultServletHandler.setServletContext(ContextTool.getServletContext()); } } @RequestMapping(name = "default") public void forwardToDefaultServlet(HttpServletRequest request, HttpServletResponse response) { try { String path = request.getServletPath(); Boolean isExists = staticFiles.get(path); // default null if (isExists == null) { boolean pathExists = new File(ContextTool.getServletContext().getRealPath(path)).exists(); isExists = pathExists; staticFiles.put(path, pathExists); } if (isExists) defaultServletHandler.handleRequest(request, response); else response.setStatus(404); switch (response.getStatus()) { case 200: case 301: case 302: case 304: case 404: break; default: // 将其它状态码统一为 502 response.setStatus(502); } } catch (Exception e) { e.printStackTrace(); response.setStatus(500); // 转发异常, 发送 500 状态码 } } }

这样就不会有多余的 Context 了

$ curl sm/Log/js/index.sfd -v * STATE: INIT => CONNECT handle 0x6000579a0; line 1404 (connection #-5000) * Added connection 0. The cache now contains 1 members * STATE: CONNECT => WAITRESOLVE handle 0x6000579a0; line 1440 (connection #0) * Trying 192.168.126.1... * TCP_NODELAY set * STATE: WAITRESOLVE => WAITCONNECT handle 0x6000579a0; line 1521 (connection #0) * Connected to sm (192.168.126.1) port 80 (#0) * STATE: WAITCONNECT => SENDPROTOCONNECT handle 0x6000579a0; line 1573 (connection #0) * Marked for [keep alive]: HTTP default * STATE: SENDPROTOCONNECT => DO handle 0x6000579a0; line 1591 (connection #0) > GET /Log/js/index.sfd HTTP/1.1 > Host: sm > User-Agent: curl/7.59.0 > Accept: */* > * STATE: DO => DO_DONE handle 0x6000579a0; line 1670 (connection #0) * STATE: DO_DONE => WAITPERFORM handle 0x6000579a0; line 1795 (connection #0) * STATE: WAITPERFORM => PERFORM handle 0x6000579a0; line 1811 (connection #0) * HTTP 1.1 or later with persistent connection, pipelining supported < HTTP/1.1 404 * Server Fast Tomcat is not blacklisted < Server: Fast Tomcat < Content-Length: 0 < Date: Mon, 16 Sep 2019 15:46:08 GMT < * STATE: PERFORM => DONE handle 0x6000579a0; line 1980 (connection #0) * multi_done * Connection #0 to host sm left intact * Expire cleared 更新

由于种种原因, 我还是选择了重写 DefaultServlet 来实现静态资源的处理, 这其实有多种好处(比如添加Context-Type字段)

package emcat import global import org.apache.catalina.servlets.DefaultServlet import java.util.HashMap import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse import java.io.File /** * 静态资源处理器 */ class StaticServlet(val webappDir: String = "./webapps") : DefaultServlet() { val list = HashMap() override fun service(req: HttpServletRequest, resp: HttpServletResponse) { global.log("静态请求 ${ req.getMethod() } ${ req.getRequestURI() }") val path = req.getServletPath() var isExists: Boolean? = list.get(path) if (isExists == null) { // 查询文件存在否 val file = File("${ webappDir }/${ path }") global.log(file.getAbsolutePath()) isExists = file.exists() list.put(path, isExists) } if (isExists) return super.service(req, resp) global.log("404 for ${ req.getServletPath() }") resp.setStatus(500) } }

嵌入式Tomcat + Spring + Kotlin 项目模板 : https://github.com/develon2015/MyCat



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有