上篇文章已经实现了一个简易版的连接器,理解了那个连接器,再看Tomcat的默认连接器就会轻松很多,本章中的默认连接器指的是Tomcat4的默认连接器,尽管该连接器已经被弃用了,被另一个运行速度更快的连接器 Coyote 取代,但它仍然是一个很不错的学习工具。
Tomcat的连接器是一个独立的模块,可以被插入到servlet容器中。之所以说它独立,是因为市面上的连接器有很多,只要它满足了catalina中的连接器要求,那就可以放入Tomcat中使用而不用修改其他代码。类似于我们电脑中的内存条,只要主板支持,就有很多厂家的内存条供我们选择,如果空间不够用了还可以选择更大存储的内存条,是一个可插拔的设备。
那么catalina能使用的连接器,有哪些要求呢?
- 实现了org.apache.catalina.Connector接口
- 负责创建实现了org.apache.catalina.Request接口的request对象
- 负责创建实现了org.apache.catalina.Response接口的response对象
与我们的简易连接器相同,Tomcat默认连接器的功能也是接收socket请求,处理socket请求,建立request、response对象,调用容器的invoke方法这一套流程。
Tomcat的容器类应该是实现了org.apache.catalina.Container接口的类,该接口的关键方法为 void **invoke(Request request, Response response)**方法,此方法应该实现http请求的分发功能,将请求交给指定servlet进行处理。
Tomcat默认连接器与我们上章中简易连接器的最大区别就是支持并发请求。如何支持并发的呢?默认连接器的做法是
1.先将HttpProcessor类继承Runnable接口,将处理http请求的过程放到一个独立线程中来执行
2.创建多个HttpProcessor对象,也就是创建了多个线程,连接器中维护一个HttpProcessor对象池,每收到一个Socket连接就从对象池中取一个HttpProcessor对象,也就是选一个HttpProcessor线程来处理。

这个做法很像我们常说的线程池,我们现在都通过ThreadPoolExecutor来创建线程池了,为什么默认连接器不用这个方式呢?因为那会儿的jdk还是1.4版本,还没有ThreadPoolExecutor,ThreadPoolExecutor是jdk 1.5版本才引入的。也许现在的连接器已经使用上线程池了,但是了解默认连接器的工作方式仍然能让你对多线程编程多点体会。
在正式介绍连接器的代码设计之前,先说一下HTTP 1.1的几个新特性
HTTP 1.1的几个新特性
1.持久连接(长连接)
HTTP 1.1之前,浏览器与服务器建立的连接都是短连接,一次请求结束后,连接即刻关闭。但是随着网页内容越来越丰富,一个网页的展现往往需要多次请求后台,如果每次请求建立的连接都是用完即关的话,会很浪费资源,效率也不高。所以HTTP 1.1引入了长连接的概念,反映在HTTP请求头中就是 Connection: keep-alive ,当请求头或响应头中带上了这个长链接标识的话,就代表客户端或服务器开启了长链接,接下来该网页的请求都会使用者一个Socket连接来进行通信,效率大大提高。
那什么时候长连接会被关闭呢?
- 当一端的HTTP头部信息中带上了 Connection: close ,则代表该连接在本次请求后就需要被关闭了。
- 在服务器端,通常会设置连接的空闲超时时间(idle timeout)。如果连接在一段时间内没有活动,即没有新的请求-响应交互,服务器会主动关闭连接
2.块编码
这块内容在第二章中已经介绍过,不再重复介绍。
3.状态码100的使用
使用HTTP1.1的客户端可以在向服务端发送请求体之前,先发送一个这个消息
Expect:100-continue
这个通常用在客户端准备了一个特大消息体的时候,先问候一下服务端看它在不在,如果服务端不在,那你客户端拖着一个特大消息体扔给互联网,那属实是浪费所有资源。
当客户端接收到 " HTTP/1.1 100 Continue" 响应消息后,代表服务器是在岗的,就可以继续沟通了。
好,HTTP1.1的新特性已讲完,现在回到连接器的设计上。
Tomcat默认连接器的设计
来看下默认连接器的UML类图

HTTPConnector类实现了org.apache.catalina.Connector接口,代表它是一个连接器。连接器必须持有一个实现了org.apache.catalina.Container接口的Servlet容器类的引用,本章中这个Servlet容器类为 ex04.pyrmont.core.SimpleContainer 。
HTTPConnector负责启动一个独立线程接收Socket连接,而具体的处理逻辑交给HttpProcessor线程去完成,由于HTTPConnector持有一个HttpProcessor线程池,所以此连接器能支持并发连接。
HttpProcessor处理器负责解析http请求,最终封装成org.apache.catalina.Request与org.apache.catalina.Response两个对象并作为参数调用Servlet容器的 invoke 方法。
SimpleContainer的invoke方法逻辑与前一章的ServletProcessor#process 方法功能差不多,都是加载指定servlet类,并调用该servlet类的service方法。
接下来看看具体的代码设计
先声明一下,org.apache包的代码都是Tomcat4的源码,本章介绍的默认连接器就是在这个包下

由于本章的连接器使用的是Tomcat的默认连接器,所以属于本章的代码量就很少了,而且由于源码也没有bug,我就不再重新编码了

Bootstrap类
这仍然是启动整个Web容器的启动类。该类创建了连接器与Servlet容器,并将两者做了个关联。在执行完HttpConnector的 initialize() 与 start() 两个方法后,Web容器就启动了。
java
package ex04.pyrmont.startup;
import ex04.pyrmont.core.SimpleContainer;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(String[] args) {
// 创建连接器与Servlet容器,并做一个关联
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
try {
connector.initialize();
connector.start();
// 接收键盘输入,在这里的目的是保活main线程
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
HttpConnector类
HttpConnector实现了org.apache.catalina.Connector接口,代表它是一个连接器,接口需要实现的主要方法有 getContainer() 、setContainer()、createRequest()、createResponse() 。
setContainer用于关联一个servlet容器。createRequest负责创建一个servlet容器需要的request对象,createResponse负责创建一个servlet容器需要的response对象。
HttpConnector实现了Runnable接口,代表它也是一个线程对象,以独立线程运行,主要逻辑在run方法中。另外,该线程以守护线程的方式启动,在Tomcat进程中只要还存在一个其他非守护线程在运行,连接器线程就不会退出,但是如果非守护线程都停止了,那么连接器线程也没存在的必要了,我想Tomcat的设计者也是这样考虑的吧。
HttpConnector实现了org.apache.catalina.Lifecycle接口,Lifecycle接口负责维护Catalina中各组件的生命周期,这个到第六章再做具体介绍,这里你且知道HttpConnector实例创建后就需要马上执行它的两个生命周期方法initialize()、start() 。
initialize方法的作用是给连接器创建一个ServerSocket对象。
start方法的作用是启动连接器线程,创建并启动最小数量的 processor 线程。start方法执行完后连接器就可以开始工作了。
HttpConnector线程的run方法中有一个while循环,其实就是ServerSocket循环等待新的Socket连接进来,然后将Socket连接通过 **processor.assign(socket)**方法交给HttpProcessor线程去异步执行处理逻辑。在介绍HttpProcessor类时我会详细介绍这个assign方法。
HttpConnector通过何种方式拿到一个空闲可用的HttpProcessor线程呢?答案就是HttpConnector内部维护了一个HttpProcessor对象池,**private Stack processors = new Stack();**存在于这个栈中的processor就是空闲可用的processor,使用的时候出栈即可,processor在完成一次任务后会将自己重新压入栈中。
由于HttpConnector源码代码量很大,我精简了一下,仅列出几个主要的属性及方法供你串通逻辑。
java
public final class HttpConnector implements Connector, Lifecycle, Runnable {
// ServerSocket的backlog参数,最大等待连接数量
private int acceptCount = 10;
// 关联的容器
protected Container container = null;
// 已经被创建的 processor 集合
private final Vector<HttpProcessor> created = new Vector<>();
// 空闲可用的 processor 集合
private Stack processors = new Stack();
// 当前已经创建的processors数量
private int curProcessors = 0;
// 最小processors数量
protected int minProcessors = 5;
// 最大processors数量,如果为负数,代表无限制
private int maxProcessors = 20;
// 监听TCP连接的 ServerSocket
private ServerSocket serverSocket = null;
// 连接器监听的端口
private int port = 8080;
// 运行连接器的线程
private Thread thread = null;
// 线程同步对象,加锁用
private Object threadSync = new Object();
// --------------------------------------------------------- Lifecycle Methods
/**
* 初始化连接器 (这个方法就创建了一个 ServerSocket)
*/
public void initialize() throws LifecycleException {
if (initialized) {
throw new LifecycleException(sm.getString("httpConnector.alreadyInitialized"));
}
this.initialized = true;
Exception eRethrow = null;
// Establish a server socket on the specified port
try {
serverSocket = open();
} catch (Exception ex) {
// 为了代码好看,这里将所有的异常合并了,源码中针对不同的异常打了不同的日志
log("httpConnector, have problem: ", ex);
eRethrow = ex;
}
if (eRethrow != null) {
throw new LifecycleException(threadName + ".open", eRethrow);
}
}
/**
* 开启连接器线程,创建最小数量的 processor 线程,这些线程创建完后就可以接收http请求了
*/
public void start() throws LifecycleException {
// 检查是否启动过了
if (started) {
throw new LifecycleException(sm.getString("httpConnector.alreadyStarted"));
}
threadName = "HttpConnector[" + port + "]";
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// 开启连接器线程
threadStart();
// 创建最小数量的 processor
while (curProcessors < minProcessors) {
if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) {
break;
}
HttpProcessor processor = newProcessor();
// 将 processor 入栈
recycle(processor);
}
}
/**
* 开启连接器线程,并且此线程为守护线程
*/
private void threadStart() {
thread = new Thread(this, threadName);
thread.setDaemon(true);
thread.start();
}
/**
* 通过连接器终止所有processor线程 --本章暂时没用到,简单看下就行
*/
public void stop() throws LifecycleException {
// Validate and update our current state
if (!started) {
throw new LifecycleException(sm.getString("httpConnector.notStarted"));
}
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
// Gracefully shut down all processors we have created
for (int i = created.size() - 1; i >= 0; i--) {
HttpProcessor processor = (HttpProcessor) created.elementAt(i);
if (processor != null) {
try {
((Lifecycle) processor).stop();
} catch (LifecycleException e) {
log("HttpConnector.stop", e);
}
}
}
synchronized (threadSync) {
// Close the server socket we were using
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
;
}
}
// Stop our background thread
threadStop();
}
serverSocket = null;
}
/**
* 关闭连接器线程
*/
private void threadStop() {
log(sm.getString("httpConnector.stopping"));
stopped = true;
try {
threadSync.wait(5000);
} catch (InterruptedException e) {
;
}
thread = null;
}
// ---------------------------------------------- Background Thread Methods
/**
* 连接器的后台线程run方法,负责监听 TCP/IP 连接,并将连接交给合适的processor去处理
*/
public void run() {
// 循环执行,直到 收到停止命令(stopped变为true)
while (!stopped) {
Socket socket;
try {
socket = serverSocket.accept();
// 设置 Socket 的超时时间。这个方法的作用是当 Socket 进行 I/O 操作时,
// 如果在指定的时间内没有完成读写操作,就会抛出 java.net.SocketTimeoutException 异常。
if (connectionTimeout > 0) {
socket.setSoTimeout(connectionTimeout);
}
// TCP_NODELAY为true时,代表关闭Nagle算法,大部分情况下能提高tcp通信效率
socket.setTcpNoDelay(tcpNoDelay);
} catch (AccessControlException ace) {
log("socket accept security exception", ace);
continue;
} catch (IOException e) {
try {
// 如果socket创建失败了,则重新创建一下ServerSocket,如果仍然失败的话,就退出连接器线程
synchronized (threadSync) {
if (started && !stopped) {
log("accept error: ", e);
}
if (!stopped) {
serverSocket.close();
serverSocket = open();
}
}
} catch (Exception ex) {
// 其实这里的日志打印 本来根据不同异常分的很细,这里为了代码好看,我且将它们省略了
log("socket reopen, have problem: ", ex);
// 又发生了异常,退出while循环
break;
}
continue;
}
// 获取一个 processor 来接管本次 socket 通信
HttpProcessor processor = createProcessor();
// 如果没有 processor 可用的话,则放弃本次 socket 通信
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
} catch (IOException e) {
}
continue;
}
// processor开始接管socket,processor处理完后会自动回收它自己(调用连接器的recycle方法),并准备处理下次http请求
processor.assign(socket);
}
// while 循环到此结束
// Notify the threadStop() method that we have shut ourselves down
synchronized (threadSync) {
threadSync.notifyAll();
}
}
// -------------------------------------------------------- Private Methods
/**
* 创建或者分配一个可用的 processor,如果 processor数量已经达到最大值则返回null
*/
private HttpProcessor createProcessor() {
synchronized (processors) {
if (processors.size() > 0) {
return ((HttpProcessor) processors.pop());
}
if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
return (newProcessor());
} else {
if (maxProcessors < 0) {
return (newProcessor());
} else {
return (null);
}
}
}
}
/**
* 创建一个新的 processor 用以处理 http 请求,
* 启动 processor 线程后,return 此 processor。
*/
private HttpProcessor newProcessor() {
HttpProcessor processor = new HttpProcessor(this, curProcessors++);
try {
// 启动 processor 线程
processor.start();
} catch (LifecycleException e) {
log("newProcessor", e);
return (null);
}
// 创建过的 processor 记录到一个集合中,连接器销毁时会用到(stop方法)
created.addElement(processor);
return processor;
}
// -------------------------------------------------------- Package Methods
/**
* 回收指定的 Processor, 以便于重新使用它
* 回收方式:将processor入栈,下次http请求到来时就可以直接分配给它了
*/
void recycle(HttpProcessor processor) {
processors.push(processor);
}
// --------------------------------------------------------- Public Methods
// 获取连接器关联的servlet容器
public Container getContainer() {
return (container);
}
// 将一个servlet容器与该连接器做关联
public void setContainer(Container container) {
this.container = container;
}
// 创建一个可以给Servlet容器使用的Request对象 --此方法给processor线程使用
public Request createRequest() {
HttpRequestImpl request = new HttpRequestImpl();
request.setConnector(this);
return (request);
}
// 创建一个可以给Servlet容器使用的Response对象 --此方法给processor线程使用
public Response createResponse() {
HttpResponseImpl response = new HttpResponseImpl();
response.setConnector(this);
return (response);
}
}
HttpProcessor类
HttpProcessor类要干的事和第三章中的HttpProcessor一样,都是处理socket连接,将http请求解析为request与response对象后,交给容器处理。不同的是,本章的HttpProcessor实现了Runnable接口,也就是说本章的HttpProcessor是要起一个独立线程来干事的,这样的话,连接器就不用阻塞等待processor处理完一个请求后再去处理下一个了;连接器拿到socket连接后直接丢给processor线程异步处理,就可以实现并发连接了。
连接器线程与处理器线程通过两个方法与一个属性值来沟通。两个方法是 assign与await,一个属性是socket。
来对比看看这两个方法

下面通过一个时序图来展现下两个线程配合的过程

这就是一个正常的交互流程了,这个流程下其实只用到了下图中标号为1的 wait(),notifyAll()方法,那么标号为2的这一对是干啥用的呢?我理解的是:预防连接器在assign方法中执行完notifyAll又释放锁后,不知怎么着马上又执行到了同一个处理器的assign方法,并且先于该处理器线程获取到锁,这时候处理器线程还没有取走上一个socket请求呢,所以得等待处理器线程取走上一个socket。处理器线程取走socket后,notifyAll()通知连接器线程:你可以继续了,然后连接器线程结束等待,继续执行。

HttpProcessor中真正解析http请求的方法为process方法,也是解析请求行,请求头的一系列解析流程,最终封装成Request与Response两个对象交给Servlet容器去执行后续操作。
下面是我精简后的代码,仅保留了主要的几个方法
java
public final class HttpProcessor implements Lifecycle, Runnable {
// ----------------------------------------------------------- Constructors
public HttpProcessor(HttpConnector connector, int id) {
super();
this.connector = connector;
this.debug = connector.getDebug();
this.id = id;
this.proxyName = connector.getProxyName();
this.proxyPort = connector.getProxyPort();
this.request = (HttpRequestImpl) connector.createRequest();
this.response = (HttpResponseImpl) connector.createResponse();
this.serverPort = connector.getPort();
this.threadName = "HttpProcessor[" + connector.getPort() + "][" + id + "]";
}
// ----------------------------------------------------- Instance Variables
// 是否有一个新的socket可用了
private boolean available = false;
// 关联的连接器对象
private HttpConnector connector = null;
// 需要给到Servlet容器的request对象
private HttpRequestImpl request = null;
// 需要给到Servlet容器的response对象
private HttpResponseImpl response = null;
// 连接器监听的端口
private int serverPort = 0;
// 当前处理器线程正在处理的socket,这个socket对象用于 连接器线程与处理器线程之间的通信
private Socket socket = null;
// 处理器线程
private Thread thread = null;
// 线程的同步对象,加锁用
private Object threadSync = new Object();
// Keep alive 标志
private boolean keepAlive = false;
// 是否是HTTP/1.1 客户端.
private boolean http11 = true;
// 如果客户端请求接收请求确认,则为True。如果是这样,服务器将在成功解析请求报头之后,在开始读取请求实体体之前发送一个初步的100 Continue响应。
private boolean sendAck = false;
// 回应客户端的 "Expect:100-continue" 请求
private static final byte[] ack = (new String("HTTP/1.1 100 Continue\r\n\r\n")).getBytes();
private HttpRequestLine requestLine = new HttpRequestLine();
// Processor 当前状态
private int status = Constants.PROCESSOR_IDLE;
// ------------------------------------------------------ Lifecycle Methods
/**
* 开启processor线程,用以处理http请求
*/
public void start() throws LifecycleException {
if (started) {
throw new LifecycleException(sm.getString("httpProcessor.alreadyStarted"));
}
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
threadStart();
}
/**
* 结束processor线程 --本章暂时没用到这个方法
*/
public void stop() throws LifecycleException {
if (!started) throw new LifecycleException(sm.getString("httpProcessor.notStarted"));
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
started = false;
threadStop();
}
// ---------------------------------------------- Background Thread Methods
/**
* processor线程的run方法
*/
public void run() {
// processor线程会一直循环等待 处理请求,直到接收到一个停止信号
while (!stopped) {
// 阻塞等待下一个socket分配给当前processor
Socket socket = await();
if (socket == null) {
continue;
}
// 处理这个 socket 请求
try {
process(socket);
} catch (Throwable t) {
log("process.invoke", t);
}
// 处理完这次请求了,让连接器回收自己,准备接收下次socket请求
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
/**
* 启动processing线程
*/
private void threadStart() {
log(sm.getString("httpProcessor.starting"));
thread = new Thread(this, threadName);
thread.setDaemon(true);
thread.start();
if (debug >= 1) log(" Background thread has been started");
}
/**
* 停止processing线程
*/
private void threadStop() {
log(sm.getString("httpProcessor.stopping"));
stopped = true;
assign(null);
if (status != Constants.PROCESSOR_IDLE) {
// Only wait if the processor is actually processing a command
synchronized (threadSync) {
try {
threadSync.wait(5000);
} catch (InterruptedException e) {
;
}
}
}
thread = null;
}
// -------------------------------------------------------- Package Methods
/**
* 这个方法是给连接器线程调用的。用于将socket交给HttpProcessor线程进行异步处理,以便连接器可以同时处理多个请求。
*
*/
synchronized void assign(Socket socket) {
// 等待这个 Processor 获取上一个 Socket。
// ps:如果这个方法执行完释放锁后,处理线程processor不给力,又被连接器线程给截胡了(理论上不存在),
// 率先获取了processor对象锁,则连接器线程需要等待processor将上一个socket拿走后,才能给socket属性set新值
while (available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// 存储最新可用的 Socket,并 notify 当前这个processor对象对应的处理器线程
this.socket = socket;
available = true;
notifyAll();
if ((debug >= 1) && (socket != null)) {
log(" An incoming request is being assigned");
}
}
// -------------------------------------------------------- Private Methods
/**
* 等待连接器分配一个新的Socket过来。如何连接器正在关闭的话,连接器会分配一个为null的Socket过来
*/
private synchronized Socket await() {
// 等待连接器给分配一个新的 Socket
while (!available) {
try {
wait();
} catch (InterruptedException e) {
}
}
// 告诉连接器,你分配来的Socket我收到了
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null)) {
log(" The incoming request has been awaited");
}
return socket;
}
/**
* 处理socket连接中携带的http请求
*/
private void process(Socket socket) {
boolean ok = true;
boolean finishResponse = true;
SocketInputStream input = null;
OutputStream output = null;
// 结构化并初始化我们需要的对象
try {
input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize());
} catch (Exception e) {
log("process.create", e);
ok = false;
}
keepAlive = true;
// 循环接收处理此长链接上的请求,直到客户端发出关闭连接的请求
while (!stopped && ok && keepAlive) {
finishResponse = true;
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e);
ok = false;
}
// 解析请求
try {
if (ok) {
// 解析并记录本次请求对应连接的参数(客户端的地址与连接器监听的端口)
parseConnection(socket);
// 解析请求行,得到queryString、method、protocol、uri信息
parseRequest(input, output);
if (!request.getRequest().getProtocol().startsWith("HTTP/0")) {
// 解析请求头,并将请求头中特殊的信息放到request对象的对应字段中
parseHeaders(input);
}
if (http11) {
// Sending a request acknowledge back to the client if requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed()) {
response.setAllowChunking(true);
}
}
}
} catch (EOFException e) {
// 这很可能是客户端或服务器上的套接字断开连接了
ok = false;
finishResponse = false;
} catch (ServletException e) {
ok = false;
try {
((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception f) {
;
}
} catch (InterruptedIOException e) {
if (debug > 1) {
try {
log("process.parse", e);
((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception f) {
;
}
}
ok = false;
} catch (Exception e) {
try {
log("process.parse", e);
((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
} catch (Exception f) {
;
}
ok = false;
}
// 将组装好的请求对象交给Servlet容器去进一步处理
try {
((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
} catch (ServletException e) {
log("process.invoke", e);
try {
((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (Exception f) {
;
}
ok = false;
} catch (InterruptedIOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
try {
((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} catch (Exception f) {
;
}
ok = false;
}
// Finish up the handling of the request
if (finishResponse) {
try {
response.finishResponse();
} catch (IOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
ok = false;
}
try {
request.finishRequest();
} catch (IOException e) {
ok = false;
} catch (Throwable e) {
log("process.invoke", e);
ok = false;
}
try {
if (output != null) output.flush();
} catch (IOException e) {
ok = false;
}
}
// We have to check if the connection closure has been requested
// by the application or the response stream (in case of HTTP/1.0
// and keep-alive).
if ("close".equals(response.getHeader("Connection"))) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
// where循环结束
try {
shutdownInput(input);
socket.close();
} catch (IOException e) {
;
} catch (Throwable e) {
log("process.invoke", e);
}
socket = null;
}
/**
* 解析并记录本次请求对应连接的参数(客户端的地址与连接器监听的端口)
*/
private void parseConnection(Socket socket) throws IOException, 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);
}
/**
* 解析请求头,将解析出来的信息填充到request对象中
*/
private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
while (true) {
HttpHeader header = request.allocateHeader();
// Read the next header
input.readHeader(header);
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
} else {
throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
String value = new String(header.value, 0, header.valueEnd);
if (debug >= 1) {
log(" Header " + new String(header.name, 0, header.nameEnd) + " = " + value);
}
// Set the corresponding request headers
if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
request.setAuthorization(value);
} else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
parseAcceptLanguage(value);
} else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
Cookie cookies[] = RequestUtil.parseCookieHeader(value);
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(Globals.SESSION_COOKIE_NAME)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
request.setRequestedSessionId(cookies[i].getValue());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (debug >= 1)
log(" Requested cookie session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
}
}
if (debug >= 1) log(" Adding cookie " + cookies[i].getName() + "=" + cookies[i].getValue());
request.addCookie(cookies[i]);
}
} else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
int n = -1;
try {
n = Integer.parseInt(value);
} catch (Exception e) {
throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
}
request.setContentLength(n);
} else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
request.setContentType(value);
} else if (header.equals(DefaultHeaders.HOST_NAME)) {
int n = value.indexOf(':');
if (n < 0) {
if (connector.getScheme().equals("http")) {
request.setServerPort(80);
} else if (connector.getScheme().equals("https")) {
request.setServerPort(443);
}
if (proxyName != null) request.setServerName(proxyName);
else request.setServerName(value);
} else {
if (proxyName != null) request.setServerName(proxyName);
else request.setServerName(value.substring(0, n).trim());
if (proxyPort != 0) request.setServerPort(proxyPort);
else {
int port = 80;
try {
port = Integer.parseInt(value.substring(n + 1).trim());
} catch (Exception e) {
throw new ServletException(sm.getString("httpProcessor.parseHeaders.portNumber"));
}
request.setServerPort(port);
}
}
} else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
keepAlive = false;
response.setHeader("Connection", "close");
}
//request.setConnection(header);
/*
if ("keep-alive".equalsIgnoreCase(value)) {
keepAlive = true;
}
*/
} else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE)) sendAck = true;
else throw new ServletException(sm.getString("httpProcessor.parseHeaders.unknownExpectation"));
} else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
//request.setTransferEncoding(header);
}
request.nextHeader();
}
}
/**
* 解析请求行,得到 queryString、method、protocol、uri信息。如果uri中包含jsessionid的话,同时也把jsessionid解析出来
* 如果是HTTP/1.1 协议的话,默认采用持久化连接,更早版本的话默认采用非持久化连接。
* 无论什么版本HTTP协议,如果请求头中声明了Connection的值的话,则以这个值为准
*/
private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException {
// 解析请求行
input.readRequestLine(requestLine);
// When the previous method returns, we're actually processing a request
status = Constants.PROCESSOR_ACTIVE;
String method = new String(requestLine.method, 0, requestLine.methodEnd);
String uri;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
if (protocol.length() == 0) {
protocol = "HTTP/0.9";
}
// Now check if the connection should be kept alive after parsing the request.
if (protocol.equals("HTTP/1.1")) {
http11 = true;
sendAck = false;
} else {
http11 = false;
sendAck = false;
// For HTTP/1.0, connection are not persistent by default,
// unless specified with a Connection: Keep-Alive header.
keepAlive = false;
}
// Validate the incoming request line
if (method.length() < 1) {
throw new ServletException(sm.getString("httpProcessor.parseRequest.method"));
} else if (requestLine.uriEnd < 1) {
throw new ServletException(sm.getString("httpProcessor.parseRequest.uri"));
}
// Parse any query parameters out of the request URI
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
if (debug >= 1) log(" Query string is " + ((HttpServletRequest) request.getRequest()).getQueryString());
uri = new String(requestLine.uri, 0, question);
} else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
} else {
uri = uri.substring(pos);
}
}
}
// Parse any requested session ID out of the request URI
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
} else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
if (debug >= 1)
log(" Requested URL session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
} else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
// 标准化 URI,对非正常的URI进行修正
String normalizedUri = normalize(uri);
if (debug >= 1) {
log("Normalized: '" + uri + "' to '" + normalizedUri + "'");
}
// Set the corresponding request properties
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
} else {
((HttpRequest) request).setRequestURI(uri);
}
request.setSecure(connector.getSecure());
request.setScheme(connector.getScheme());
if (normalizedUri == null) {
log(" Invalid request URI: '" + uri + "'");
throw new ServletException("Invalid URI: " + uri + "'");
}
if (debug >= 1) {
log(" Request is '" + method + "' for '" + uri + "' with protocol '" + protocol + "'");
}
}
/**
* Send a confirmation that a request has been processed when pipelining.
* HTTP/1.1 100 Continue is sent back to the client.
*
* @param output Socket output stream
*/
private void ackRequest(OutputStream output) throws IOException {
if (sendAck) output.write(ack);
}
protected void shutdownInput(InputStream input) {
try {
int available = input.available();
// skip any unread (bogus) bytes
if (available > 0) {
input.skip(available);
}
} catch (Throwable e) {
;
}
}
}
SimpleContainer类
这个类就不是Tomcat源码中的类了,这是我们自定义的一个Servlet容器类,它实现了org.apache.catalina.Container方法但是这里我们仅对invoke方法做了具体实现,因为默认连接器会调用该方法。该方法与第三章中ServletProcessor#process方法类似,创建类加载器,加载相关servlet类,创建servlet对象并调用其service方法。
下面是精简后的代码,仅保留了invoke方法
java
public class SimpleContainer implements Container {
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
public void invoke(Request request, Response response) throws IOException, ServletException {
String servletName = ((HttpServletRequest) request).getRequestURI();
servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
} catch (IOException e) {
System.out.println(e.toString());
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
} catch (Exception e) {
System.out.println(e.toString());
} catch (Throwable e) {
System.out.println(e.toString());
}
}
}
你可能发现了,这里怎么没用门面类呢?额,我只能说书中源码中也没有,我也懒得加了😂,下一章详细介绍Servlet容器时再说。
结果展示
启动Bootstrap类,浏览器访问结果如下
PrimitiveServlet

ModernServlet

注意,本章的简易Servlet容器仍然不能返回静态资源。
好,Tomcat的默认连接器到此就介绍完了,后面章节中会一直使用这个连接器。下一章将具体研究下Servlet容器,敬请期待!
源码分享
https://gitee.com/huo-ming-lu/HowTomcatWorks
本章的代码在这两个包下