Tomcat Servlet模块HttpServlet源码分析

前言:


Servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http. 在javax.servlet包中定义了所有的Servlet类都必须实现或扩展的的通用接口和类.在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类.


Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这一接口.在Servlet接口中定义了5个方法,其中有3个方法代表了Servlet的声明周期:


init方法,负责初始化Servlet对象
service方法,负责相应客户的请求
destory方法,当Servlet对象退出声明周期时,负责释放占有的资源




当Web容器接收到某个Servlet请求时,Servlet把请求封装成一个HttpServletRequest对象,然后把对象传给Servlet的对应的服务方法.


     HTTP的请求方式包括DELETE,GET,OPTIONS,POST,PUT和TRACE,在HttpServlet类中分别提供了相应的服务方法,它们是,doDelete(),doGet(),doOptions(),doPost(), doPut()和doTrace(). 


HttpServlet的功能  


HttpServlet首先必须读取Http请求的内容。Servlet容器负责创建HttpServlet对象,并把Http请求直接封装到HttpServlet对象中,大大简化了HttpServlet解析请求数据的工作量。HttpServlet容器响应Web客户请求流程如下:


1)Web客户向Servlet容器发出Http请求;
2)Servlet容器解析Web客户的Http请求;
3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息;
4)Servlet容器创建一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;
7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户。


二、创建HttpServlet的步骤——“四部曲”


1)扩展HttpServlet抽象类;
2)覆盖HttpServlet的部分方法,如覆盖doGet()或doPost()方法;
3)获取HTTP请求信息。通过HttpServletRequest对象来检索HTML表单所提交的数据或URL上的查询字符串;
4)生成HTTP响应结果。通过HttpServletResponse对象生成响应结果,它有一个getWriter()方法,该方法返回一个PrintWriter对象。



下面是HttpServlet的继承关系树,关于其他类源码可以到API文档查看,地址: http://tomcat.apache.org/tomcat-8.5-doc/servletapi/



下面是对HttpServlet源码的分析

[java]  view plain  copy
  1. /** 
  2.  *  
  3.  * Copyright © 2017 http://blog.csdn.net/noseparte © Like the wind, like rain 
  4.  * @author Noseparte 
  5.  * @Comile 2017年12月8日--下午9:31:27 
  6.  * @Version 1.0 
  7.  * @Description     HttpServlet源码分析 
  8.  */  
  9. @SuppressWarnings("unused")  
  10. public abstract class HttpServlet extends GenericServlet {  
  11.       
  12.     private static final long serialVersionUID = 1L;  
  13.     private static final String METHOD_DELETE = "DELETE";  
  14.     private static final String METHOD_HEAD = "HEAD";  
  15.     private static final String METHOD_GET = "GET";  
  16.     private static final String METHOD_OPTIONS = "OPTIONS";  
  17.     private static final String METHOD_POST = "POST";  
  18.     private static final String METHOD_PUT = "PUT";  
  19.     private static final String METHOD_TRACE = "TRACE";  
  20.     private static final String HEADER_IFMODSINCE = "If-Modified-Since";  
  21.     private static final String HEADER_LASTMOD = "Last-Modified";  
  22.     private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";  
  23.     private static final ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");  
  24.   
  25.     /** 
  26.      * 由服务器调用(通过服务方法)来允许servlet处理GET请求。 
  27.      *  
  28.      * 覆盖此方法以支持GET请求也会自动支持HTTP HEAD请求。HEAD请求是GET请求,在响应中不返回任何主体,只有请求头字段。 
  29.      *  
  30.      * 在覆盖该方法时,读取请求数据,编写响应头,获取响应的写入器或输出流对象,最后,编写响应数据。最好是包含内容类型和编码。当使用PrintWriter对象返回响应时,在访问PrintWriter对象之前设置内容类型。 
  31.      *  
  32.      * servlet容器必须在提交响应之前写头文件,因为在HTTP中,必须在响应主体之前发送头部。 
  33.      *  
  34.      * 在可能的情况下,标题设置content - length(ServletResponse.setContentLength(int)方法),允许servlet容器使用持久连接返回响应给客户端,改善性能。如果整个响应都在响应缓冲区内,则会自动设置内容长度。 
  35.      *  
  36.      * 当使用HTTP 1.1分块编码(这意味着响应有一个传输编码头)时,不要设置内容长度的标题。 
  37.      *  
  38.      * GET方法应该是安全的,也就是说,没有任何对用户负责的副作用。例如,大多数表单查询都没有副作用。如果客户机请求用于更改存储的数据,则请求应该使用其他的HTTP方法。 
  39.      *  
  40.      * GET方法也应该是幂等的,这意味着它可以安全地重复。有时,使方法安全也使其具有强大的功能。例如,重复查询既安全又有效,但在网上购买产品或修改数据既不安全,也不具有幂等功能。 
  41.      *  
  42.      * 如果请求格式不正确,doGet将返回HTTP "Bad Request" 消息。 
  43.      *  
  44.      * @param req 
  45.      * @param resp 
  46.      * @throws ServletException 
  47.      * @throws IOException 
  48.      */  
  49.     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  50.     IOException {  
  51.         String protocol = req.getProtocol();  
  52.         String msg = lStrings.getString("http.method_get_not_supported");  
  53.         if (protocol.endsWith("1.1")) {  
  54.             resp.sendError(405, msg);  
  55.         } else {  
  56.             resp.sendError(400, msg);  
  57.         }  
  58.     }  
  59.   
  60.     /** 
  61.      * 返回HttpServletRequest对象最后一次修改的时间,从1970年1月1日午夜开始,时间为毫秒。如果时间未知,该方法返回一个负数(默认值)。 
  62.      *  
  63.      * 支持HTTP GET请求的servlet可以快速确定它们的最后修改时间,应该覆盖这个方法。这使得浏览器和代理缓存更加有效,减少了服务器和网络资源的负载。 
  64.      *  
  65.      * @param req 
  66.      * @return 
  67.      */  
  68.     protected long getLastModified(HttpServletRequest req) {  
  69.         return - 1L;  
  70.     }  
  71.   
  72.     /** 
  73.      * 从受保护的服务方法接收HTTP HEAD请求并处理请求。客户端发送一个HEAD请求,当它只想看到响应的头,例如内容类型或内容长度。HTTP HEAD方法计算响应中的输出字节,以便准确地设置内容长度标题。 
  74.      *  
  75.      * 如果您覆盖了这个方法,您可以避免计算响应主体,并直接设置响应标头以提高性能。请确保您所编写的doHead方法既安全又具有强大的功能(也就是说,保护自己不被多次调用一个HTTP头请求)。 
  76.      *  
  77.      * 如果HTTP HEAD请求格式不正确,doHead将返回HTTP "Bad Request" 消息。 
  78.      *  
  79.      * @param req 
  80.      * @param resp 
  81.      * @throws ServletException 
  82.      * @throws IOException 
  83.      */  
  84.     protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  85.     IOException {  
  86.         if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {  
  87.             doGet(req, resp);  
  88.         } else {  
  89.             NoBodyResponse response = new NoBodyResponse(resp);  
  90.             doGet(req, response);  
  91.             response.setContentLength();  
  92.         }  
  93.     }  
  94.   
  95.     /** 
  96.      * 由服务器调用(通过服务方法)来允许servlet处理POST请求。HTTP POST方法允许客户端将无限长度的数据发送到Web服务器,并且在发布诸如信用卡号之类的信息时非常有用。 
  97.      *  
  98.      * 在覆盖该方法时,读取请求数据,编写响应头,获取响应的写入器或输出流对象,最后,编写响应数据。最好是包含内容类型和编码。当使用PrintWriter对象返回响应时,在访问PrintWriter对象之前设置内容类型。 
  99.      *  
  100.      * servlet容器必须在提交响应之前写头文件,因为在HTTP中,必须在响应主体之前发送头部。 
  101.      *  
  102.      * 在可能的情况下,标题设置content - length(ServletResponse.setContentLength(int)方法),允许servlet容器使用持久连接返回响应给客户端,改善性能。如果整个响应都在响应缓冲区内,则会自动设置内容长度。 
  103.      *  
  104.      * 当使用HTTP 1.1分块编码(这意味着响应有一个传输编码头)时,不要设置内容长度的标题。 
  105.      *  
  106.      * 这种方法不需要是安全的,也不需要强大的。通过POST请求的操作可以有副作用,例如,更新存储的数据或在线购买项目。 
  107.      *  
  108.      * 如果HTTP POST请求格式不正确,doPost将返回HTTP "Bad Request" 消息。 
  109.      *  
  110.      * @param req 
  111.      * @param resp 
  112.      * @throws ServletException 
  113.      * @throws IOException 
  114.      */  
  115.     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  116.     IOException {  
  117.         String protocol = req.getProtocol();  
  118.         String msg = lStrings.getString("http.method_post_not_supported");  
  119.         if (protocol.endsWith("1.1")) {  
  120.             resp.sendError(405, msg);  
  121.         } else {  
  122.             resp.sendError(400, msg);  
  123.         }  
  124.     }  
  125.   
  126.     /** 
  127.      * 由服务器调用(通过服务方法)来允许servlet处理PUT请求。PUT操作允许客户端将文件放在服务器上,类似于通过FTP发送文件。 
  128.      *  
  129.      * 在覆盖此方法时,保留与请求发送的任何内容头(包括内容长度、内容类型、内容转换编码、内容编码、内容基础、内容语言、内容位置、内容-md5和内容范围)。 
  130.      * 如果您的方法不能处理一个内容头,它必须发出一个错误消息(HTTP 501-没有实现)并丢弃该请求。有关HTTP 1.1的更多信息,请参阅RFC 2616。 
  131.      *  
  132.      * 这种方法不需要是安全的,也不需要强大的。doPut执行的操作可能会有一些副作用,用户可以对此负责。在使用这种方法时,可以在临时存储中保存受影响的URL的副本。 
  133.      *  
  134.      * 如果HTTP PUT请求格式不正确,则doPut将返回HTTP "Bad Request" 消息。 
  135.      *  
  136.      * @param req 
  137.      * @param resp 
  138.      * @throws ServletException 
  139.      * @throws IOException 
  140.      */  
  141.     protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  142.     IOException {  
  143.         String protocol = req.getProtocol();  
  144.         String msg = lStrings.getString("http.method_put_not_supported");  
  145.         if (protocol.endsWith("1.1")) {  
  146.             resp.sendError(405, msg);  
  147.         } else {  
  148.             resp.sendError(400, msg);  
  149.         }  
  150.     }  
  151.   
  152.     /** 
  153.      * 由服务器调用(通过服务方法)来允许servlet处理一个删除请求。删除操作允许客户端从服务器中删除文档或Web页面。 
  154.      *  
  155.      * 这种方法不需要是安全的,也不需要强大的。通过删除所请求的操作可以有副作用,用户可以对此负责。在使用这种方法时,可以在临时存储中保存受影响的URL的副本。 
  156.      *  
  157.      * 如果HTTP DELETE请求格式不正确,doDelete将返回HTTP "Bad Request" 消息。 
  158.      *  
  159.      * @param req 
  160.      * @param resp 
  161.      * @throws ServletException 
  162.      * @throws IOException 
  163.      */  
  164.     protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  165.     IOException {  
  166.         String protocol = req.getProtocol();  
  167.         String msg = lStrings.getString("http.method_delete_not_supported");  
  168.         if (protocol.endsWith("1.1")) {  
  169.             resp.sendError(405, msg);  
  170.         } else {  
  171.             resp.sendError(400, msg);  
  172.         }  
  173.     }  
  174.   
  175.     /** 
  176.      * 由服务器调用(通过服务方法)来允许servlet处理一个选项请求。选项请求确定服务器支持哪些HTTP方法,并返回一个适当的标头。 
  177.      * 例如,如果一个servlet覆盖doGet,该方法将返回以下标题:允许:GET,HEAD,TRACE,选项 
  178.      *  
  179.      * 除非servlet实现了新的HTTP方法,否则没有必要覆盖这个方法,除了HTTP 1.1实现的方法之外。 
  180.      *  
  181.      * @param c 
  182.      * @return 
  183.      */  
  184.     private static Method[] getAllDeclaredMethods(Class < ?>c) {  
  185.         if (c.equals(HttpServlet.class)) {  
  186.             return null;  
  187.         }  
  188.         Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());  
  189.         Method[] thisMethods = c.getDeclaredMethods();  
  190.         if ((parentMethods != null) && (parentMethods.length > 0)) {  
  191.             Method[] allMethods = new Method[parentMethods.length + thisMethods.length];  
  192.   
  193.             System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);  
  194.   
  195.             System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);  
  196.   
  197.             thisMethods = allMethods;  
  198.         }  
  199.         return thisMethods;  
  200.     }  
  201.   
  202.     /** 
  203.      * 由服务器调用(通过服务方法)来允许servlet处理一个选项请求。选项请求确定服务器支持哪些HTTP方法,并返回一个适当的标头。 
  204.      * 例如,如果一个servlet覆盖doGet,该方法将返回以下标题:允许:GET,HEAD,TRACE,选项 
  205.      *  
  206.      * 除非servlet实现了新的HTTP方法,否则没有必要覆盖这个方法,除了HTTP 1.1实现的方法之外。 
  207.      *  
  208.      * @param req 
  209.      * @param resp 
  210.      * @throws ServletException 
  211.      * @throws IOException 
  212.      */  
  213.     protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  214.     IOException {  
  215.         Method[] methods = getAllDeclaredMethods(getClass());  
  216.   
  217.         boolean ALLOW_GET = false;  
  218.         boolean ALLOW_HEAD = false;  
  219.         boolean ALLOW_POST = false;  
  220.         boolean ALLOW_PUT = false;  
  221.         boolean ALLOW_DELETE = false;  
  222.         boolean ALLOW_TRACE = true;  
  223.         boolean ALLOW_OPTIONS = true;  
  224.   
  225.         Class < ?>clazz = null;  
  226.         try {  
  227.             clazz = Class.forName("org.apache.catalina.connector.RequestFacade");  
  228.             Method getAllowTrace = clazz.getMethod("getAllowTrace", (Class[]) null);  
  229.             ALLOW_TRACE = ((Boolean) getAllowTrace.invoke(req, (Object[]) null)).booleanValue();  
  230.         } catch(ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException localClassNotFoundException) {}  
  231.         for (int i = 0; i < methods.length; i++) {  
  232.             Method m = methods[i];  
  233.             if (m.getName().equals("doGet")) {  
  234.                 ALLOW_GET = true;  
  235.                 ALLOW_HEAD = true;  
  236.             }  
  237.             if (m.getName().equals("doPost")) {  
  238.                 ALLOW_POST = true;  
  239.             }  
  240.             if (m.getName().equals("doPut")) {  
  241.                 ALLOW_PUT = true;  
  242.             }  
  243.             if (m.getName().equals("doDelete")) {  
  244.                 ALLOW_DELETE = true;  
  245.             }  
  246.         }  
  247.         String allow = null;  
  248.         if (ALLOW_GET) {  
  249.             allow = "GET";  
  250.         }  
  251.         if (ALLOW_HEAD) {  
  252.             if (allow == null) {  
  253.                 allow = "HEAD";  
  254.             } else {  
  255.                 allow = allow + ", HEAD";  
  256.             }  
  257.         }  
  258.         if (ALLOW_POST) {  
  259.             if (allow == null) {  
  260.                 allow = "POST";  
  261.             } else {  
  262.                 allow = allow + ", POST";  
  263.             }  
  264.         }  
  265.         if (ALLOW_PUT) {  
  266.             if (allow == null) {  
  267.                 allow = "PUT";  
  268.             } else {  
  269.                 allow = allow + ", PUT";  
  270.             }  
  271.         }  
  272.         if (ALLOW_DELETE) {  
  273.             if (allow == null) {  
  274.                 allow = "DELETE";  
  275.             } else {  
  276.                 allow = allow + ", DELETE";  
  277.             }  
  278.         }  
  279.         if (ALLOW_TRACE) {  
  280.             if (allow == null) {  
  281.                 allow = "TRACE";  
  282.             } else {  
  283.                 allow = allow + ", TRACE";  
  284.             }  
  285.         }  
  286.         if (ALLOW_OPTIONS) {  
  287.             if (allow == null) {  
  288.                 allow = "OPTIONS";  
  289.             } else {  
  290.                 allow = allow + ", OPTIONS";  
  291.             }  
  292.         }  
  293.         resp.setHeader("Allow", allow);  
  294.     }  
  295.   
  296.     /** 
  297.      * 由服务器调用(通过服务方法)来允许servlet处理跟踪请求。 
  298.      * 跟踪返回将跟踪请求发送到客户机的头,以便在调试中使用它们。没有必要覆盖这个方法。 
  299.      *  
  300.      * @param req 
  301.      * @param resp 
  302.      * @throws ServletException 
  303.      * @throws IOException 
  304.      */  
  305.     protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  306.     IOException {  
  307.         String CRLF = "\r\n";  
  308.         StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI()).append(" ").append(req.getProtocol());  
  309.   
  310.         Enumeration < String > reqHeaderEnum = req.getHeaderNames();  
  311.         while (reqHeaderEnum.hasMoreElements()) {  
  312.             String headerName = (String) reqHeaderEnum.nextElement();  
  313.             buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));  
  314.         }  
  315.         buffer.append(CRLF);  
  316.   
  317.         int responseLength = buffer.length();  
  318.   
  319.         resp.setContentType("message/http");  
  320.         resp.setContentLength(responseLength);  
  321.         ServletOutputStream out = resp.getOutputStream();  
  322.         out.print(buffer.toString());  
  323.         out.close();  
  324.     }  
  325.   
  326.     /** 
  327.      * 从公共服务方法接收标准HTTP请求,并将它们分派到这个类中定义的doMethod方法。 
  328.      *  
  329.      * 该方法是servlet.service(javax.servlet的一个特定于http的版本。 
  330.      *  
  331.      * ServletRequest,javax.servlet.ServletResponse)方法。 
  332.      *  
  333.      * 没有必要覆盖这个方法。 
  334.      *  
  335.      * @param req 
  336.      * @param resp 
  337.      * @throws ServletException 
  338.      * @throws IOException 
  339.      */  
  340.     protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException,  
  341.     IOException {  
  342.         String method = req.getMethod();  
  343.         if (method.equals("GET")) {  
  344.             long lastModified = getLastModified(req);  
  345.             if (lastModified == -1L) {  
  346.                 doGet(req, resp);  
  347.             } else {  
  348.                 long ifModifiedSince;  
  349.                 try {  
  350.                     ifModifiedSince = req.getDateHeader("If-Modified-Since");  
  351.                 } catch(IllegalArgumentException iae) {  
  352.                     ifModifiedSince = -1L;  
  353.                 }  
  354.                 if (ifModifiedSince < lastModified / 1000L * 1000L) {  
  355.                     maybeSetLastModified(resp, lastModified);  
  356.                     doGet(req, resp);  
  357.                 } else {  
  358.                     resp.setStatus(304);  
  359.                 }  
  360.             }  
  361.         } else if (method.equals("HEAD")) {  
  362.             long lastModified = getLastModified(req);  
  363.             maybeSetLastModified(resp, lastModified);  
  364.             doHead(req, resp);  
  365.         } else if (method.equals("POST")) {  
  366.             doPost(req, resp);  
  367.         } else if (method.equals("PUT")) {  
  368.             doPut(req, resp);  
  369.         } else if (method.equals("DELETE")) {  
  370.             doDelete(req, resp);  
  371.         } else if (method.equals("OPTIONS")) {  
  372.             doOptions(req, resp);  
  373.         } else if (method.equals("TRACE")) {  
  374.             doTrace(req, resp);  
  375.         } else {  
  376.             String errMsg = lStrings.getString("http.method_not_implemented");  
  377.             Object[] errArgs = new Object[1];  
  378.             errArgs[0] = method;  
  379.             errMsg = MessageFormat.format(errMsg, errArgs);  
  380.   
  381.             resp.sendError(501, errMsg);  
  382.         }  
  383.     }  
  384.   
  385.     private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {  
  386.         if (resp.containsHeader("Last-Modified")) {  
  387.             return;  
  388.         }  
  389.         if (lastModified >= 0L) {  
  390.             resp.setDateHeader("Last-Modified", lastModified);  
  391.         }  
  392.     }  
  393.   
  394.     /** 
  395.      * 将客户端请求发送到受保护的服务方法。没有必要覆盖这个方法。 
  396.      */  
  397.     public void service(ServletRequest req, ServletResponse res) throws ServletException,IOException {  
  398.         HttpServletResponse response;  
  399.         HttpServletRequest request;  
  400.         try {  
  401.             request = (HttpServletRequest) req;  
  402.             response = (HttpServletResponse) res;  
  403.         } catch(ClassCastException e) {  
  404.             throw new ServletException("non-HTTP request or response");  
  405.         }  
  406.         service(request, response);  
  407.     }  
  408. }  


故而,我们在web容器里注册一个的继承HttpServlet的实现类,构建web.xml入口。


默认的servlet是既服务于静态资源又服务于目录列表(如果允许目录列表的话)的servlet。

它在$CATALINA_HOME/conf/web.xml中被全局声明。默认形式的声明是这样的: $CATALINA_HOME/conf/web.xml


转载自:Tomcat 架构分析(四) Servlet和Jsp模块




  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值