您的位置:首页 > 理论基础 > 计算机网络

深入剖析Spring Web源码(五) - DispatcherServlet的实现 - 通用Servlet和HTTP Servlet

2010-09-17 17:46 1111 查看

1.1.1 通用 Servlet 和 HTTP Servlet

HTTP( Hyper Text Transfer Protocol)是超文本传输协议的缩写,它用于传送 WWW方式的数据,关于 HTTP协议的详细内容请参考 RFC2616。 HTTP协议采用了请求 /响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、 URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于 MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容。

HTTP协议支持各种类型的方法,其中包括, GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE。

GET方法将请求参数放在请求的头中,请求服务器做某些服务操作并返回响应。

POST方法从客户机向服务器传送数据,并可能要求服务器做出某些服务操作进行响应。

PUT方法请求将一个资源放在服务器的某个路径下。

DELETE方法请求将服务器某路径下的一个资源删除。

HEAD要求服务器查找某对象的头信息,包括应该包含的请求体的长度,而不是对象本身。

OPTION方法用来查询服务器的实现信息。

TRACE多数情况下用在调试目操作上。

下图是 Servlet规范的接口和实现类的继承结构,每个类的方法包括实体方法,抽象方法或者占位符方法。抽象方法和占位符方法由子类实现。



图表 4‑2

从上图我们可以看出, Servlet接口定义了 3个重要的接口方法, init()方法是在 Sevlet初始化的时候调用的,提供给 Servlet组件进行初始化自己的机会。与此相对应, detroy()方法是在 Servlet析构时候调用的,提供给 Servlet组件进行释放使用过的资源的机会。而 sevice()方法是用来处理每一个 Web容器传递进来的请求与响应的。

通用 Servlet实现了 Servlet的接口方法 init(), 方法中保存了 Servlet容器传递过来的 ServletConfig对象。如下图程序片段所示,

public  void  init(ServletConfig config) throws  ServletException {
// 保存 Servlet 配置对象,对于处理一个 HTTP 请求的许多操作都需要的 Servlet 配置所包含的信息,例如 ,Servlet 名字, Servlet 配置的参数等等
this .config = config;

// 代理到另外一个无参数的 init 方法,这个方法是一个抽象方法,它是一个占位符,提供给子类重写并初始化的机会
this .init();
}


通用 Servlet的方法 service()是一个显示定义的抽象方法,要求实现类必须重写这个方法的实现。因为不同的 Servlet实现会依赖不同的协议,实现各不相同。 D

destroy()是一个方法占位符,子类可以有选择的实现进而进行资源的清理。

HTTP Servlet正如我们所愿,实现了通用 Servlet的 service()方法,根据 HTTP请求中所标识的方法,把 HTTP请求派遣到不同的处理方法中。如下图所示,



图表 4‑3

这些不同的方法有不同的实现,这些处理方法中的大部分是占位符,但是,它为 doOptions()和 doTrace()提供了具体实现,因为对于不同的 HTTP Servlet组件,这两个方法的行为基本是不变的。他们都是用于返回服务器信息和调试目的。如下图代码注释,

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);
}
}


从上面两个方法的实现中我们可以看到, HTTP Servlet根据不同的 HTTP方法进行了 HTTP请求的分发。这样,不同方法的请求会使用不同的处理方法进行处理。事实上 doGet() doPost(), doPut(), doDelete()都是占位符实现,子类应该有选择的重写这些方法来实现真正的服务逻辑。 Spring Web MVC就是通过重写这些方法,开始控制流的实现的。

下面是 doGet()方法的代码注释。

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);
}
}


doHead()方法的实现通过对 HTTP响应类进行包装,实现了 NoBodyReponse类,这个类忽略了对 HTTP响应体的输出。重用了 doGet()方法的实现,并且保留了 HTTP头信息的输出。所以,如果一个子类 Servlet重写了 doGet()方法,这个方法 doHead()是不需要重写的。代码注释如下,

protected  void  doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// 构造一个特殊的响应类,这个类内部忽略了所有的响应体的输出
NoBodyResponse response = new NoBodyResponse(resp);

// 重用 doGet() 处理器罗杰
doGet(req, response);

// 设置响应体的字节大小,尽管响应体并没有输出,但是客户端可能关系这个信息
response.setContentLength();
}


doPost(), doPut(), doDelete()方法的实现和 doGet()方法的实现是类似的,他们都是一个占位符的实现,子类 Servlet需要有选择的进行重写进而实现真正需要的 HTTP服务。

然而, doOptions()和 doTrace()对任何 Servlet的实现,基本是不变的,他们是用来查询服务器信息和调试所用,他们的实现如下,

// 这个方法 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 ;
}


从上面的代码注释中,我们可以看到 Servlet规范中的 HTTP Servlet的实现只是一个占位符实现,并不包含完全的服务实现,一些服务的实现是由子类 Servlet完成的。 Spring Web MVC就是通过实现这些占位符方法来派遣 HTTP请求到 Spring Web MVC的控制器组件方法的。

下面一节我们将深入剖析 Spring Web MVC 的控制器是如何进行派遣和处理 HTTP 请求的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐