9.Tomcat的默认连接器
Tomcat连接器是一个可以插入servlet容器的独立模块,已经存在相当多的连接器了,包括Coyote, mod_jk, mod_jk2和mod_webapp
一个Tomcat连接器必须符合以下条件:
1.必须实现接口org.apache.catalina.Connector。
2.必须创建请求对象,该请求对象的类必须实现接口org.apache.catalina.Request。
3. 必须创建响应对象,该响应对象的类必须实现接口org.apache.catalina.Response。Tomcat4的默认连接器类似于前面的简单连接器。它等待前来的HTTP请求,创建request和response对象,然后把request和response对象传递给容器。连接器是通过调用接口org.apache.catalina.Container的invoke方法来传递request和response对象的。
invoke的方法签名如下所示:
public void invoke( org.apache.catalina.Request request, org.apache.catalina.Response response);
在invoke方法里边,容器加载servlet,调用它的service方法,管理会话,记录出错日志等等。
在很多地方使用字节数组来代替字符串
首先讨论HTTP1.1的三个新特性。理解它们是理解默认连接器内部工作机制的关键所在。
持久连接
在HTTP1.1之前,无论什么时候浏览器连接到一个web服务器,当请求的资源被发送之后,连接就被服务器关闭了。然而,一个互联网网页包括其他资源,例如图片文件,applet等等。因此,当一个页面被请求的时候,浏览器同样需要下载页面所引用到的资源。加入页面和它所引用到的全部资源使用不同连接来下载的话,进程将会非常慢。那就是为什么HTTP1.1引入持久连接的原因了。使用持久连接的时候,当页面下载的时候,服务器并不直接关闭连接。相反,它等待web客户端请求页面所引用的全部资源.
持久连接是HTTP1.1的默认连接方式。同样,为了明确这一点,浏览器可以发送一个值为keep-alive的请求头部connection: connection: keep-alive块编码
建立持续连接的结果就是,使用同一个连接,服务器可以从不同的资源发送字节流,而客户端可以使用发送多个请求。结果就是,发送方必须为每个请求或响应发送内容长度的头部,以便接收方知道如何解释这些字节。然而,大部分的情况是发送方并不知道将要发送多少个字节。例如,在开头一些字节已经准备好的时候,servlet容器就可以开始发送响应了,而不会等到所有都准备好。这意味着,在content-length头部不能提前知道的情况下,必须有一种方式来告诉接收方如何解释字节流。
即使不需要发送多个请求或者响应,服务器或者客户端也不需要知道将会发送多少数据。在HTTP1.0中,服务器可以仅仅省略content-length头部,并保持写入连接。当写入完成的时候,它将简单的关闭连接。在这种情况下,客户端将会保持读取状态,直到获取到-1,表示已经到达文件的尾部。HTTP1.1使用一个特别的头部transfer-encoding来表示有多少以块形式的字节流将会被发送。对每块来说,在数据之前,长度(十六进制)后面接着CR/LF将被发送。
状态100(持续状态)的使用
在发送请求内容之前,HTTP 1.1客户端可以发送Expect: 100-continue头部到服务器,并等待服务器的确认。这个一般发生在当客户端需要发送一份长的请求内容而未能确保服务器愿意接受它的时候。如果你 发送一份长的请求内容仅仅发现服务器拒绝了它,那将是一种浪费来的。 当接受到Expect: 100-continue头部的时候,假如乐意或者可以处理请求的话,服务器响应100-continue头部,后边跟着两对CRLF字符。 HTTP/1.1 100 Continue 接着,服务器应该会继续读取输入流。Connector接口Tomcat连接器必须实现org.apache.catalina.Connector接口。在这个接口的众多方法中,最重要的是getContainer,setContainer, createRequest和createResponse。 setContainer是用来关联连接器和容器用的。getContainer返回关联的容器。createRequest为前来的HTTP请求构造一个请求对象,而createResponse创建一个响应对象。
类org.apache.catalina.connector.http.HttpConnector是Connector接口的一个实现,
接口Lifecycle用来维护每个已经实现它的Catalina组件的生命周期。
实现Lifecycle,在创建HttpConnector实例之后,应该 调用它的initialize和start方法。这两个方法在组件的生命周期里必须只调用一次为HTTP请求服务
HttpConnector类在它的run方法中有其主要的逻辑。run方法在一个服务端套接字等待HTTP请求的地方存在一个while循环,一直运行直至HttpConnector被关闭了。
while(!stopped){
Socketsocket=null;
try{
socket=serverSocket.accept();
...
对每个前来的HTTP请求,会通过调用私有方法createProcessor获得一个HttpProcessor实例。
HttpProcessorprocessor=createProcessor();
然而,大部分时候createProcessor方法并不创建一个新的HttpProcessor对象。相反,它从池子中获取一个。如果在栈中已经存在一 个HttpProcessor实例,createProcessor将弹出一个。如果栈是空的并且没有超过HttpProcessor实例的最大数 量,createProcessor将会创建一个。然而,如果已经达到最大数量的话,createProcessor将会返回null。出现这样的情况的 话,套接字将会简单关闭并且前来的HTTP请求不会被处理。if (processor == null) {
try{
log(sm.getString("httpConnector.noProcessor"));
socket.close();
}
...continue;
如果createProcessor不是返回null,客户端套接字会传递给HttpProcessor类的assign方法:
processor.assign(socket);现在就是HttpProcessor实例用于读取套接字的输入流和解析HTTP请求的工作了。重要的一点是,assign方法不会等到 HttpProcessor完成解析工作,而是必须马上返回,以便下一个前来的HTTP请求可以被处理。
处理请求
关注HttpProcessor类的process方法,它是一个套接字赋给它之后,在HttpProcessor类的run方法中调用的。process方法会做下面这些工作:
解析连接
解析请求
解析头部解析连接
parseConnection方法从套接字中获取到网络地址并把它赋予HttpRequestImpl对象。它也检查是否使用代理并把套接字赋予请求对象
privatevoidparseConnection(Socketsocket)throwsIOException,ServletException{
if(debug>=2)
log("parseConnection:address="+socket.getInetAddress()+",port="+connector.getPort());
((HttpRequestImpl)request).setInet(socket.getInetAddress());
if(proxyPort!=0)
request.setServerPort(proxyPort);
else
request.setServerPort(serverPort);
request.setSocket(socket);
}解析头部
默认链接器的parseHeaders方法使用包org.apache.catalina.connector.http里边的HttpHeader和DefaultHeaders类。类HttpHeader指代一个HTTP请求头部。类HttpHeader不使用字符串,而是使用字符数据用来避免昂贵的字符串操作。类DefaultHeaders是一个final类,在字符数组中包含了标准的HTTP请求头部:
standardHTTPrequestheadersincharacterarrays:
staticfinalchar[]AUTHORIZATION_name="authorization".toCharArray();
static final char[] ACCEPT_LANGUAGE_NAME = "accept-language".toCharArray();static final char[] COOKIE_NAME = "cookie".toCharArray();