Serlvet接口只定义了一个服务方法就是service,而HttpServlet类实现了该方法并且要求调用下列的方法之一:
doGet:处理GET请求
doPost:处理POST请求
doPut:处理PUT请求
doDelete:处理DELETE请求
doHead:处理HEAD请求
doOptions:处理OPTIONS请求
doTrace:处理TRACE请求
通常情况下,在开发基于HTTP的servlet时,开发者只需要关心doGet和doPost方法,其它的方法需要开发者非常的熟悉HTTP编程,因此这些方法被认为是高级方法。
而通常情况下,实现的servlet都是从HttpServlet扩展而来。
doPut和doDelete方法允许开发者支持HTTP/1.1的对应特性;
doHead是一个已经实现的方法,它将执行doGet但是仅仅向客户端返回doGet应该向客户端返回的头部的内容;
doOptions方法自动的返回servlet所直接支持的HTTP方法信息;
doTrace方法返回TRACE请求中的所有头部信息。
对于那些仅仅支持HTTP/1.0的容器而言,只有doGet, doHead 和 doPost方法被使用,因为HTTP/1.0协议没有定义PUT, DELETE, OPTIONS,或者TRACE请求。
另外,HttpServlet定义了getLastModified方法以支持有条件的(conditional)get操作。有条件的get操作是指使用GET方式请求资源并且在头部指定只有在资源内容在指定时间后被修改的情况下服务器才有必要回应请求并发送请求的内容。对于那些实现doGet方法并且在不同请求之间内容相同的servlet而言,它应该实现这个方法以提高网络资源的利用率。
Spring的参考实现:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { // 既然是 HTTP 协议绑定的 Serlvet, 强制转换到 HTTP 的领域模型 request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { // 如果传入的 HTTP 请求和 HTTP 响应不是 HTTP 的领域模型,则抛出 Servlet 异常,这个异常会被 Servlet 容器所处理 throw new ServletException( "non-HTTP request or response" ); } // 如果传入的请求和响应是预期的 HTTP 请求和 HTTP 响应,则调用 service() 方法。 service(request, response); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从 HTTP 请求中取得这次请求所使用的 HTTT 方法 String method = req.getMethod(); if (method.equals(METHOD_GET)) { // 如果这次请求使用 GET 方法 // 取得这个 Servlet 的最后修改的时间 long lastModified = getLastModified(req); if (lastModified == -1) { //-1 代表这个 Servlet 不支持最后修改操作,直接调用 doGet() 进行处理 HTTP GET 请求 doGet(req, resp); } else { // 如果这个 Servlet 支持最后修改操作,取得请求头中包含的请求的最后修改时间 long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < (lastModified / 1000 * 1000)) { // 如果请求头中包含的修改时间早于这个 Servlet 的最后修改时间,说明这个 Servlet 自从客户上一次 HTTP 请求已经被修改了 , 设置最新修改时间到响应头中 maybeSetLastModified(resp, lastModified); // 调用 doGet 进行进行处理 HTTP GET 请求 doGet(req, resp); } else { // 如果请求头中包含修改时间晚于这个 Servlet 的最后修改时间,说明这个 Servlet 自从请求的最后修改时间后没有更改过,这种情况下,仅仅返回一个 HTTP 响应状态 SC_NOT_MODIFIED resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { // 如果这次请求使用 POST 方法 // 如果这个 Servlet 支持最后修改操作,则设置这个 Servlet 的最后修改时间到响应头中 long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); // 和对 HTTP GET 方法处理不同的是,无论请求头中的修改时间是不是早于这个 Sevlet 的最后修改时间,都会发 HEAD 响应给客户,因为 HTTP HEAD 响应是用来查询 Servlet 头信息的操作 doHead(req, resp); } else if (method.equals(METHOD_POST)) { // 如果这次请求使用 POST 方法 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { // 如果这次请求使用 PUT 方法 doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { // 如果这次请求使用 DELETE 方法 doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { // 如果这次请求使用 OPTIONS 方法 doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { // 如果这次请求使用 TRACE 方法 doTrace(req,resp); } else { // 如果这次请求是其他未知方法,返回错误代码 SC_NOT_IMPLEMENTED 给 HTTP 响应,并且显示一个错误消息,说明这个操作是没有实现的 String errMsg = lStrings.getString( "http.method_not_implemented" ); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 取得请求头包含的 HTTP 协议版本 String protocol = req.getProtocol(); // 直接发送错误消息,可见,一个子类需要重写这些占位符方法 doGet(), doPost(), doPut(), doDelete() 中的一个或者多个 String msg = lStrings.getString( "http.method_get_not_supported" ); if (protocol.endsWith( "1.1" )) { // 如果是 HTTP 1.1, 发送 SC_METHOD_NOT_ALLOWED resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { // 如果是 HTTP 的更早版本则发送 SC_BAD_REQUEST resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 构造一个特殊的响应类,这个类内部忽略了所有的响应体的输出 NoBodyResponse response = new NoBodyResponse(resp); // 重用 doGet() 处理器罗杰 doGet(req, response); // 设置响应体的字节大小,尽管响应体并没有输出,但是客户端可能关系这个信息 response.setContentLength(); } // 这个方法 doOptions() 设置支持的 HTTP 方法名称到相应头中 protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 取得当前这个 Servlet 以及父类 Servlet 声明的所有方法,这些方法不包括本类 HTTP Servlet 所生命的方法 Method[] methods = getAllDeclaredMethods( this .getClass()); // 初始化的时候,假设它不支持任何 HTTP 方法 boolean ALLOW_GET = false ; boolean ALLOW_HEAD = false ; boolean ALLOW_POST = false ; boolean ALLOW_PUT = false ; boolean ALLOW_DELETE = false ; boolean ALLOW_TRACE = true ; boolean ALLOW_OPTIONS = true ; // 根据子类 Servlet 是否重写了 HTTP Servlet 的占位符方法,判断是否这个 Servlet 实现支持这种 HTTP 方法,例如,如果子类 Servlet 实现了 doGet(), 然后 HTTP GET 方法是支持的 for ( int i=0; i<methods.length; i++) { // 遍历得到的所有生命的方法 Method m = methods[i]; // 如果名字是 doGet(), doPost(), doPut() 或者 doDelete(), 它支持相应的方法 if (m.getName().equals( "doGet" )) { ALLOW_GET = true ; ALLOW_HEAD = true ; } if (m.getName().equals( "doPost" )) ALLOW_POST = true ; if (m.getName().equals( "doPut" )) ALLOW_PUT = true ; if (m.getName().equals( "doDelete" )) ALLOW_DELETE = true ; } // 把支持的 HTTP 方法名称拼接成逗号分割的字符串,例如, “GET, POST”, “GET, POST, PUT, DELETE” String allow = null ; if (ALLOW_GET) if (allow== null ) allow=METHOD_GET; if (ALLOW_HEAD) if (allow== null ) allow=METHOD_HEAD; else allow += ", " + METHOD_HEAD; if (ALLOW_POST) if (allow== null ) allow=METHOD_POST; else allow += ", " + METHOD_POST; if (ALLOW_PUT) if (allow== null ) allow=METHOD_PUT; else allow += ", " + METHOD_PUT; if (ALLOW_DELETE) if (allow== null ) allow=METHOD_DELETE; else allow += ", " + METHOD_DELETE; if (ALLOW_TRACE) if (allow== null ) allow=METHOD_TRACE; else allow += ", " + METHOD_TRACE; if (ALLOW_OPTIONS) if (allow== null ) allow=METHOD_OPTIONS; else allow += ", " + METHOD_OPTIONS; // 把支持的方法拼接成的字符串设置到 HTTP 协议的相应头中,这个值的 key 是 "Allow" resp.setHeader( "Allow" , allow); } // 这个方法返回一个字符串到 HTTP 响应体里面,这个字符串包含请求 URL, 版本信息以及请求的头信息,主要是用来调试 protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int responseLength; // 连接 URI 字符串和协议版本信息字符串 String CRLF = "\r\n" ; String responseString = "TRACE " + req.getRequestURI()+ " " + req.getProtocol(); Enumeration reqHeaderEnum = req.getHeaderNames(); // 遍历所有的请求头信息 while ( reqHeaderEnum.hasMoreElements() ) { String headerName = (String)reqHeaderEnum.nextElement(); // 拼接所有的请求头到字符串中,并且使用:分割名值对,每对头信息之间使用回车换行进行分隔 responseString += CRLF + headerName + ": " + req.getHeader(headerName); } // 附着回车换行符到字符串结尾 responseString += CRLF; // 取得字符串字节长度信息 responseLength = responseString.length(); // 设置响应类型为 message/http resp.setContentType( "message/http" ); // 设置响应体的长度 resp.setContentLength(responseLength); // 输出字符串消息到响应中 ServletOutputStream out = resp.getOutputStream(); out.print(responseString); // 关闭相应流,结束操作 out.close(); return ; }