zoukankan      html  css  js  c++  java
  • 如何使用 DefaultServlet DefaultServletHttpRequestHandler 来处理静态资源

    我们都知道 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<String, Boolean> 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<String, Boolean>()
    	
    	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

  • 相关阅读:
    extern--C#调用C++等其他非托管代码
    unhandledException详细介绍
    MySql如何安装?
    Mindoc搭建流程
    反射_IsDefined判断方法上有自定义的标签
    WebApi_返回Post格式数据
    编码
    IP地址与MAC地址
    Tcp/Ip:Telnet指令
    create-react-app使用的问题
  • 原文地址:https://www.cnblogs.com/develon/p/11524872.html
Copyright © 2011-2022 走看看