Tomcat 架构分析(四) Servlet和Jsp模块
前言:
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源码的分析
/**
*
* Copyright © 2017 http://blog.csdn.net/noseparte © Like the wind, like rain
* @author Noseparte
* @Comile 2017年12月8日--下午9:31:27
* @Version 1.0
* @Description HttpServlet源码分析
*/
@SuppressWarnings("unused")
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
private static final ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
/**
* 由服务器调用(通过服务方法)来允许servlet处理GET请求。
*
* 覆盖此方法以支持GET请求也会自动支持HTTP HEAD请求。HEAD请求是GET请求,在响应中不返回任何主体,只有请求头字段。
*
* 在覆盖该方法时,读取请求数据,编写响应头,获取响应的写入器或输出流对象,最后,编写响应数据。最好是包含内容类型和编码。当使用PrintWriter对象返回响应时,在访问PrintWriter对象之前设置内容类型。
*
* servlet容器必须在提交响应之前写头文件,因为在HTTP中,必须在响应主体之前发送头部。
*
* 在可能的情况下,标题设置content - length(ServletResponse.setContentLength(int)方法),允许servlet容器使用持久连接返回响应给客户端,改善性能。如果整个响应都在响应缓冲区内,则会自动设置内容长度。
*
* 当使用HTTP 1.1分块编码(这意味着响应有一个传输编码头)时,不要设置内容长度的标题。
*
* GET方法应该是安全的,也就是说,没有任何对用户负责的副作用。例如,大多数表单查询都没有副作用。如果客户机请求用于更改存储的数据,则请求应该使用其他的HTTP方法。
*
* GET方法也应该是幂等的,这意味着它可以安全地重复。有时,使方法安全也使其具有强大的功能。例如,重复查询既安全又有效,但在网上购买产品或修改数据既不安全,也不具有幂等功能。
*
* 如果请求格式不正确,doGet将返回HTTP "Bad Request" 消息。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
/**
* 返回HttpServletRequest对象最后一次修改的时间,从1970年1月1日午夜开始,时间为毫秒。如果时间未知,该方法返回一个负数(默认值)。
*
* 支持HTTP GET请求的servlet可以快速确定它们的最后修改时间,应该覆盖这个方法。这使得浏览器和代理缓存更加有效,减少了服务器和网络资源的负载。
*
* @param req
* @return
*/
protected long getLastModified(HttpServletRequest req) {
return - 1L;
}
/**
* 从受保护的服务方法接收HTTP HEAD请求并处理请求。客户端发送一个HEAD请求,当它只想看到响应的头,例如内容类型或内容长度。HTTP HEAD方法计算响应中的输出字节,以便准确地设置内容长度标题。
*
* 如果您覆盖了这个方法,您可以避免计算响应主体,并直接设置响应标头以提高性能。请确保您所编写的doHead方法既安全又具有强大的功能(也就是说,保护自己不被多次调用一个HTTP头请求)。
*
* 如果HTTP HEAD请求格式不正确,doHead将返回HTTP "Bad Request" 消息。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
if (DispatcherType.INCLUDE.equals(req.getDispatcherType())) {
doGet(req, resp);
} else {
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
}
}
/**
* 由服务器调用(通过服务方法)来允许servlet处理POST请求。HTTP POST方法允许客户端将无限长度的数据发送到Web服务器,并且在发布诸如信用卡号之类的信息时非常有用。
*
* 在覆盖该方法时,读取请求数据,编写响应头,获取响应的写入器或输出流对象,最后,编写响应数据。最好是包含内容类型和编码。当使用PrintWriter对象返回响应时,在访问PrintWriter对象之前设置内容类型。
*
* servlet容器必须在提交响应之前写头文件,因为在HTTP中,必须在响应主体之前发送头部。
*
* 在可能的情况下,标题设置content - length(ServletResponse.setContentLength(int)方法),允许servlet容器使用持久连接返回响应给客户端,改善性能。如果整个响应都在响应缓冲区内,则会自动设置内容长度。
*
* 当使用HTTP 1.1分块编码(这意味着响应有一个传输编码头)时,不要设置内容长度的标题。
*
* 这种方法不需要是安全的,也不需要强大的。通过POST请求的操作可以有副作用,例如,更新存储的数据或在线购买项目。
*
* 如果HTTP POST请求格式不正确,doPost将返回HTTP "Bad Request" 消息。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
/**
* 由服务器调用(通过服务方法)来允许servlet处理PUT请求。PUT操作允许客户端将文件放在服务器上,类似于通过FTP发送文件。
*
* 在覆盖此方法时,保留与请求发送的任何内容头(包括内容长度、内容类型、内容转换编码、内容编码、内容基础、内容语言、内容位置、内容-md5和内容范围)。
* 如果您的方法不能处理一个内容头,它必须发出一个错误消息(HTTP 501-没有实现)并丢弃该请求。有关HTTP 1.1的更多信息,请参阅RFC 2616。
*
* 这种方法不需要是安全的,也不需要强大的。doPut执行的操作可能会有一些副作用,用户可以对此负责。在使用这种方法时,可以在临时存储中保存受影响的URL的副本。
*
* 如果HTTP PUT请求格式不正确,则doPut将返回HTTP "Bad Request" 消息。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_put_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
/**
* 由服务器调用(通过服务方法)来允许servlet处理一个删除请求。删除操作允许客户端从服务器中删除文档或Web页面。
*
* 这种方法不需要是安全的,也不需要强大的。通过删除所请求的操作可以有副作用,用户可以对此负责。在使用这种方法时,可以在临时存储中保存受影响的URL的副本。
*
* 如果HTTP DELETE请求格式不正确,doDelete将返回HTTP "Bad Request" 消息。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_delete_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
/**
* 由服务器调用(通过服务方法)来允许servlet处理一个选项请求。选项请求确定服务器支持哪些HTTP方法,并返回一个适当的标头。
* 例如,如果一个servlet覆盖doGet,该方法将返回以下标题:允许:GET,HEAD,TRACE,选项
*
* 除非servlet实现了新的HTTP方法,否则没有必要覆盖这个方法,除了HTTP 1.1实现的方法之外。
*
* @param c
* @return
*/
private static Method[] getAllDeclaredMethods(Class < ?>c) {
if (c.equals(HttpServlet.class)) {
return null;
}
Method[] parentMethods = getAllDeclaredMethods(c.getSuperclass());
Method[] thisMethods = c.getDeclaredMethods();
if ((parentMethods != null) && (parentMethods.length > 0)) {
Method[] allMethods = new Method[parentMethods.length + thisMethods.length];
System.arraycopy(parentMethods, 0, allMethods, 0, parentMethods.length);
System.arraycopy(thisMethods, 0, allMethods, parentMethods.length, thisMethods.length);
thisMethods = allMethods;
}
return thisMethods;
}
/**
* 由服务器调用(通过服务方法)来允许servlet处理一个选项请求。选项请求确定服务器支持哪些HTTP方法,并返回一个适当的标头。
* 例如,如果一个servlet覆盖doGet,该方法将返回以下标题:允许:GET,HEAD,TRACE,选项
*
* 除非servlet实现了新的HTTP方法,否则没有必要覆盖这个方法,除了HTTP 1.1实现的方法之外。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
Method[] methods = getAllDeclaredMethods(getClass());
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;
Class < ?>clazz = null;
try {
clazz = Class.forName("org.apache.catalina.connector.RequestFacade");
Method getAllowTrace = clazz.getMethod("getAllowTrace", (Class[]) null);
ALLOW_TRACE = ((Boolean) getAllowTrace.invoke(req, (Object[]) null)).booleanValue();
} catch(ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException localClassNotFoundException) {}
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
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;
}
}
String allow = null;
if (ALLOW_GET) {
allow = "GET";
}
if (ALLOW_HEAD) {
if (allow == null) {
allow = "HEAD";
} else {
allow = allow + ", HEAD";
}
}
if (ALLOW_POST) {
if (allow == null) {
allow = "POST";
} else {
allow = allow + ", POST";
}
}
if (ALLOW_PUT) {
if (allow == null) {
allow = "PUT";
} else {
allow = allow + ", PUT";
}
}
if (ALLOW_DELETE) {
if (allow == null) {
allow = "DELETE";
} else {
allow = allow + ", DELETE";
}
}
if (ALLOW_TRACE) {
if (allow == null) {
allow = "TRACE";
} else {
allow = allow + ", TRACE";
}
}
if (ALLOW_OPTIONS) {
if (allow == null) {
allow = "OPTIONS";
} else {
allow = allow + ", OPTIONS";
}
}
resp.setHeader("Allow", allow);
}
/**
* 由服务器调用(通过服务方法)来允许servlet处理跟踪请求。
* 跟踪返回将跟踪请求发送到客户机的头,以便在调试中使用它们。没有必要覆盖这个方法。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String CRLF = "\r\n";
StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI()).append(" ").append(req.getProtocol());
Enumeration < String > reqHeaderEnum = req.getHeaderNames();
while (reqHeaderEnum.hasMoreElements()) {
String headerName = (String) reqHeaderEnum.nextElement();
buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
}
buffer.append(CRLF);
int responseLength = buffer.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(buffer.toString());
out.close();
}
/**
* 从公共服务方法接收标准HTTP请求,并将它们分派到这个类中定义的doMethod方法。
*
* 该方法是servlet.service(javax.servlet的一个特定于http的版本。
*
* ServletRequest,javax.servlet.ServletResponse)方法。
*
* 没有必要覆盖这个方法。
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
String method = req.getMethod();
if (method.equals("GET")) {
long lastModified = getLastModified(req);
if (lastModified == -1L) {
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch(IllegalArgumentException iae) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
doOptions(req, resp);
} else if (method.equals("TRACE")) {
doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
if (resp.containsHeader("Last-Modified")) {
return;
}
if (lastModified >= 0L) {
resp.setDateHeader("Last-Modified", lastModified);
}
}
/**
* 将客户端请求发送到受保护的服务方法。没有必要覆盖这个方法。
*/
public void service(ServletRequest req, ServletResponse res) throws ServletException,IOException {
HttpServletResponse response;
HttpServletRequest request;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch(ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
}
故而,我们在web容器里注册一个的继承HttpServlet的实现类,构建web.xml入口。
默认的servlet是既服务于静态资源又服务于目录列表(如果允许目录列表的话)的servlet。
它在$CATALINA_HOME/conf/web.xml中被全局声明。默认形式的声明是这样的: $CATALINA_HOME/conf/web.xml
JSP与Servlet的关系
JSP是Servlet的扩展,在没有JSP之前,就已经出现了Servlet技术。Servlet是利用输出流动态生成HTML页面,包括每一个HTML标签和每个在HTML页面中出现的内容。
JSP通过在标准的HTML页面中插入Java代码,其静态的部分无须Java程序控制,只有那些需要从数据库读取并根据程序动态生成信息时,才使用Java脚本控制。
事实上,JSP是Servlet的一种特殊形式,每个JSP页面就是一个Servlet实例——JSP页面由系统编译成Servlet,Servlet再负责响应用户请求。JSP其实也是Servlet的一种简化,使用JSP时,其实还是使用Servlet,因为Web应用中的每个JSP页面都会由Servlet容器生成对应的Servlet。
JSP的九大对象及其作用域:
HttpServletRequest
常用方法
获得客户机信息
-
getRequestURL() 方法返回客户端发出请求时的完整URL。
-
getRequestURI() 方法返回请求行中的资源名部分。
-
getQueryString() 方法返回请求行中的参数部分。
-
getRemoteAddr() 方法返回发出请求的客户机的IP地址
-
getRemoteHost() 方法返回发出请求的客户机的完整主机名
-
getRemotePort() 方法返回客户机所使用的网络端口号
-
getLocalAddr() 方法返回WEB服务器的IP地址。
-
getLocalName() 方法返回WEB服务器的主机名
-
getMethod() 得到客户机请求方式
获得客户机请求头
-
getHead(name)方法
-
getHeaders(String name)方法
-
getHeaderNames() 方法
获得客户机请求参数(客户端提交的数据)
-
getParameter(name):获取指定名称的参数值。这是最为常用的方法之一。
-
getParameterValues(String name):获取指定名称参数的所有值数组。它适用于一个参数名对应多个值的情况。如页面表单中的复选框,多选列表提交的值。
-
getParameterNames():返回一个包含请求消息中的所有参数名的Enumeration对象。通过遍历这个Enumeration对象,就可以获取请求消息中所有的参数名。
-
getParameterMap():返回一个保存了请求消息中的所有参数名和值的Map对象。Map对象的key是字符串类型的参数名,value是这个参数所对应的Object类型的值数组
-
调用RequestDispatcher.forward() 方法的请求转发过程结束后,浏览器地址栏保持初始的URL地址不变。
-
HttpServletResponse.sendRedirect() 方法对浏览器的请求直接作出响应,响应的结果就是告诉浏览器去重新发出对另外一个URL的访问请求;
-
RequestDispatcher.forward() 方法在服务器端内部将请求转发给另外一个资源,浏览器只知道发出了请求并得到了响应结果,并不知道在服务器程序内部发生了转发行为。
-
RequestDispatcher.forward() 方法的调用者与被调用者之间共享相同的request对象和response对象,它们属于同一个访问请求和响应过程;
-
而HttpServletResponse.sendRedirect() 方法调用者与被调用者使用各自的request对象和response对象,它们属于两个独立的访问请求和响应过程。
HttpSession获取Session,常用方法:
-
setAttribute(String name,Object value)将value对象以name名称绑定到会话
-
getAttribute(String name)取得name的属性值,如果属性不存在则返回null
-
removeAttribute(String name)从会话中删除name属性,如果不存在不会执行,也不会抛处错误.
-
getAttributeNames()返回和会话有关的枚举值
-
invalidate()使会话失效,同时删除属性对象
-
isNew()用于检测当前客户是否为新的会话
-
getCreationTime()返回会话创建时间
-
getLastAccessedTime()返回在会话时间内web容器接收到客户最后发出的请求的时间
-
getMaxInactiveInterval()返回在会话期间内客户请求的最长时间.秒
-
setMasInactiveInterval(int seconds)允许客户客户请求的最长时间
-
ServletContext getServletContext()返回当前会话的上下文环境,ServletContext对象可以使Servlet与web容器进行通信
-
getId()返回会话期间的识别号
About Me:
- Github地址:https://github.com/noseparte
- Email: noseparte@aliyun.com 有java与hadoop相关的技术问题,可以发私信与我交流。
- NPM地址: https://www.npmjs.com/~noseparte
- WebSite: http://www.noseparte.com/ Copyright © 2017 noseparte