• 深入剖析Tomcat(四) 剖析Tomcat的默认连接器


    上篇文章已经实现了一个简易版的连接器,理解了那个连接器,再看Tomcat的默认连接器就会轻松很多,本章中的默认连接器指的是Tomcat4的默认连接器,尽管该连接器已经被弃用了,被另一个运行速度更快的连接器 Coyote 取代,但它仍然是一个很不错的学习工具。

    Tomcat的连接器是一个独立的模块,可以被插入到servlet容器中。之所以说它独立,是因为市面上的连接器有很多,只要它满足了catalina中的连接器要求,那就可以放入Tomcat中使用而不用修改其他代码。类似于我们电脑中的内存条,只要主板支持,就有很多厂家的内存条供我们选择,如果空间不够用了还可以选择更大存储的内存条,是一个可插拔的设备。

    那么catalina能使用的连接器,有哪些要求呢?

    1. 实现了org.apache.catalina.Connector接口
    2. 负责创建实现了org.apache.catalina.Request接口的request对象
    3. 负责创建实现了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容器就启动了。

    1. package ex04.pyrmont.startup;
    2. import ex04.pyrmont.core.SimpleContainer;
    3. import org.apache.catalina.connector.http.HttpConnector;
    4. public final class Bootstrap {
    5. public static void main(String[] args) {
    6. // 创建连接器与Servlet容器,并做一个关联
    7. HttpConnector connector = new HttpConnector();
    8. SimpleContainer container = new SimpleContainer();
    9. connector.setContainer(container);
    10. try {
    11. connector.initialize();
    12. connector.start();
    13. // 接收键盘输入,在这里的目的是保活main线程
    14. System.in.read();
    15. } catch (Exception e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. }

    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源码代码量很大,我精简了一下,仅列出几个主要的属性及方法供你串通逻辑。

    1. public final class HttpConnector implements Connector, Lifecycle, Runnable {
    2. // ServerSocket的backlog参数,最大等待连接数量
    3. private int acceptCount = 10;
    4. // 关联的容器
    5. protected Container container = null;
    6. // 已经被创建的 processor 集合
    7. private final Vector created = new Vector<>();
    8. // 空闲可用的 processor 集合
    9. private Stack processors = new Stack();
    10. // 当前已经创建的processors数量
    11. private int curProcessors = 0;
    12. // 最小processors数量
    13. protected int minProcessors = 5;
    14. // 最大processors数量,如果为负数,代表无限制
    15. private int maxProcessors = 20;
    16. // 监听TCP连接的 ServerSocket
    17. private ServerSocket serverSocket = null;
    18. // 连接器监听的端口
    19. private int port = 8080;
    20. // 运行连接器的线程
    21. private Thread thread = null;
    22. // 线程同步对象,加锁用
    23. private Object threadSync = new Object();
    24. // --------------------------------------------------------- Lifecycle Methods
    25. /**
    26. * 初始化连接器 (这个方法就创建了一个 ServerSocket)
    27. */
    28. public void initialize() throws LifecycleException {
    29. if (initialized) {
    30. throw new LifecycleException(sm.getString("httpConnector.alreadyInitialized"));
    31. }
    32. this.initialized = true;
    33. Exception eRethrow = null;
    34. // Establish a server socket on the specified port
    35. try {
    36. serverSocket = open();
    37. } catch (Exception ex) {
    38. // 为了代码好看,这里将所有的异常合并了,源码中针对不同的异常打了不同的日志
    39. log("httpConnector, have problem: ", ex);
    40. eRethrow = ex;
    41. }
    42. if (eRethrow != null) {
    43. throw new LifecycleException(threadName + ".open", eRethrow);
    44. }
    45. }
    46. /**
    47. * 开启连接器线程,创建最小数量的 processor 线程,这些线程创建完后就可以接收http请求了
    48. */
    49. public void start() throws LifecycleException {
    50. // 检查是否启动过了
    51. if (started) {
    52. throw new LifecycleException(sm.getString("httpConnector.alreadyStarted"));
    53. }
    54. threadName = "HttpConnector[" + port + "]";
    55. lifecycle.fireLifecycleEvent(START_EVENT, null);
    56. started = true;
    57. // 开启连接器线程
    58. threadStart();
    59. // 创建最小数量的 processor
    60. while (curProcessors < minProcessors) {
    61. if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) {
    62. break;
    63. }
    64. HttpProcessor processor = newProcessor();
    65. // 将 processor 入栈
    66. recycle(processor);
    67. }
    68. }
    69. /**
    70. * 开启连接器线程,并且此线程为守护线程
    71. */
    72. private void threadStart() {
    73. thread = new Thread(this, threadName);
    74. thread.setDaemon(true);
    75. thread.start();
    76. }
    77. /**
    78. * 通过连接器终止所有processor线程 --本章暂时没用到,简单看下就行
    79. */
    80. public void stop() throws LifecycleException {
    81. // Validate and update our current state
    82. if (!started) {
    83. throw new LifecycleException(sm.getString("httpConnector.notStarted"));
    84. }
    85. lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    86. started = false;
    87. // Gracefully shut down all processors we have created
    88. for (int i = created.size() - 1; i >= 0; i--) {
    89. HttpProcessor processor = (HttpProcessor) created.elementAt(i);
    90. if (processor != null) {
    91. try {
    92. ((Lifecycle) processor).stop();
    93. } catch (LifecycleException e) {
    94. log("HttpConnector.stop", e);
    95. }
    96. }
    97. }
    98. synchronized (threadSync) {
    99. // Close the server socket we were using
    100. if (serverSocket != null) {
    101. try {
    102. serverSocket.close();
    103. } catch (IOException e) {
    104. ;
    105. }
    106. }
    107. // Stop our background thread
    108. threadStop();
    109. }
    110. serverSocket = null;
    111. }
    112. /**
    113. * 关闭连接器线程
    114. */
    115. private void threadStop() {
    116. log(sm.getString("httpConnector.stopping"));
    117. stopped = true;
    118. try {
    119. threadSync.wait(5000);
    120. } catch (InterruptedException e) {
    121. ;
    122. }
    123. thread = null;
    124. }
    125. // ---------------------------------------------- Background Thread Methods
    126. /**
    127. * 连接器的后台线程run方法,负责监听 TCP/IP 连接,并将连接交给合适的processor去处理
    128. */
    129. public void run() {
    130. // 循环执行,直到 收到停止命令(stopped变为true)
    131. while (!stopped) {
    132. Socket socket;
    133. try {
    134. socket = serverSocket.accept();
    135. // 设置 Socket 的超时时间。这个方法的作用是当 Socket 进行 I/O 操作时,
    136. // 如果在指定的时间内没有完成读写操作,就会抛出 java.net.SocketTimeoutException 异常。
    137. if (connectionTimeout > 0) {
    138. socket.setSoTimeout(connectionTimeout);
    139. }
    140. // TCP_NODELAY为true时,代表关闭Nagle算法,大部分情况下能提高tcp通信效率
    141. socket.setTcpNoDelay(tcpNoDelay);
    142. } catch (AccessControlException ace) {
    143. log("socket accept security exception", ace);
    144. continue;
    145. } catch (IOException e) {
    146. try {
    147. // 如果socket创建失败了,则重新创建一下ServerSocket,如果仍然失败的话,就退出连接器线程
    148. synchronized (threadSync) {
    149. if (started && !stopped) {
    150. log("accept error: ", e);
    151. }
    152. if (!stopped) {
    153. serverSocket.close();
    154. serverSocket = open();
    155. }
    156. }
    157. } catch (Exception ex) {
    158. // 其实这里的日志打印 本来根据不同异常分的很细,这里为了代码好看,我且将它们省略了
    159. log("socket reopen, have problem: ", ex);
    160. // 又发生了异常,退出while循环
    161. break;
    162. }
    163. continue;
    164. }
    165. // 获取一个 processor 来接管本次 socket 通信
    166. HttpProcessor processor = createProcessor();
    167. // 如果没有 processor 可用的话,则放弃本次 socket 通信
    168. if (processor == null) {
    169. try {
    170. log(sm.getString("httpConnector.noProcessor"));
    171. socket.close();
    172. } catch (IOException e) {
    173. }
    174. continue;
    175. }
    176. // processor开始接管socket,processor处理完后会自动回收它自己(调用连接器的recycle方法),并准备处理下次http请求
    177. processor.assign(socket);
    178. }
    179. // while 循环到此结束
    180. // Notify the threadStop() method that we have shut ourselves down
    181. synchronized (threadSync) {
    182. threadSync.notifyAll();
    183. }
    184. }
    185. // -------------------------------------------------------- Private Methods
    186. /**
    187. * 创建或者分配一个可用的 processor,如果 processor数量已经达到最大值则返回null
    188. */
    189. private HttpProcessor createProcessor() {
    190. synchronized (processors) {
    191. if (processors.size() > 0) {
    192. return ((HttpProcessor) processors.pop());
    193. }
    194. if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
    195. return (newProcessor());
    196. } else {
    197. if (maxProcessors < 0) {
    198. return (newProcessor());
    199. } else {
    200. return (null);
    201. }
    202. }
    203. }
    204. }
    205. /**
    206. * 创建一个新的 processor 用以处理 http 请求,
    207. * 启动 processor 线程后,return 此 processor。
    208. */
    209. private HttpProcessor newProcessor() {
    210. HttpProcessor processor = new HttpProcessor(this, curProcessors++);
    211. try {
    212. // 启动 processor 线程
    213. processor.start();
    214. } catch (LifecycleException e) {
    215. log("newProcessor", e);
    216. return (null);
    217. }
    218. // 创建过的 processor 记录到一个集合中,连接器销毁时会用到(stop方法)
    219. created.addElement(processor);
    220. return processor;
    221. }
    222. // -------------------------------------------------------- Package Methods
    223. /**
    224. * 回收指定的 Processor, 以便于重新使用它
    225. * 回收方式:将processor入栈,下次http请求到来时就可以直接分配给它了
    226. */
    227. void recycle(HttpProcessor processor) {
    228. processors.push(processor);
    229. }
    230. // --------------------------------------------------------- Public Methods
    231. // 获取连接器关联的servlet容器
    232. public Container getContainer() {
    233. return (container);
    234. }
    235. // 将一个servlet容器与该连接器做关联
    236. public void setContainer(Container container) {
    237. this.container = container;
    238. }
    239. // 创建一个可以给Servlet容器使用的Request对象 --此方法给processor线程使用
    240. public Request createRequest() {
    241. HttpRequestImpl request = new HttpRequestImpl();
    242. request.setConnector(this);
    243. return (request);
    244. }
    245. // 创建一个可以给Servlet容器使用的Response对象 --此方法给processor线程使用
    246. public Response createResponse() {
    247. HttpResponseImpl response = new HttpResponseImpl();
    248. response.setConnector(this);
    249. return (response);
    250. }
    251. }

    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容器去执行后续操作。

    下面是我精简后的代码,仅保留了主要的几个方法

    1. public final class HttpProcessor implements Lifecycle, Runnable {
    2. // ----------------------------------------------------------- Constructors
    3. public HttpProcessor(HttpConnector connector, int id) {
    4. super();
    5. this.connector = connector;
    6. this.debug = connector.getDebug();
    7. this.id = id;
    8. this.proxyName = connector.getProxyName();
    9. this.proxyPort = connector.getProxyPort();
    10. this.request = (HttpRequestImpl) connector.createRequest();
    11. this.response = (HttpResponseImpl) connector.createResponse();
    12. this.serverPort = connector.getPort();
    13. this.threadName = "HttpProcessor[" + connector.getPort() + "][" + id + "]";
    14. }
    15. // ----------------------------------------------------- Instance Variables
    16. // 是否有一个新的socket可用了
    17. private boolean available = false;
    18. // 关联的连接器对象
    19. private HttpConnector connector = null;
    20. // 需要给到Servlet容器的request对象
    21. private HttpRequestImpl request = null;
    22. // 需要给到Servlet容器的response对象
    23. private HttpResponseImpl response = null;
    24. // 连接器监听的端口
    25. private int serverPort = 0;
    26. // 当前处理器线程正在处理的socket,这个socket对象用于 连接器线程与处理器线程之间的通信
    27. private Socket socket = null;
    28. // 处理器线程
    29. private Thread thread = null;
    30. // 线程的同步对象,加锁用
    31. private Object threadSync = new Object();
    32. // Keep alive 标志
    33. private boolean keepAlive = false;
    34. // 是否是HTTP/1.1 客户端.
    35. private boolean http11 = true;
    36. // 如果客户端请求接收请求确认,则为True。如果是这样,服务器将在成功解析请求报头之后,在开始读取请求实体体之前发送一个初步的100 Continue响应。
    37. private boolean sendAck = false;
    38. // 回应客户端的 “Expect:100-continue” 请求
    39. private static final byte[] ack = (new String("HTTP/1.1 100 Continue\r\n\r\n")).getBytes();
    40. private HttpRequestLine requestLine = new HttpRequestLine();
    41. // Processor 当前状态
    42. private int status = Constants.PROCESSOR_IDLE;
    43. // ------------------------------------------------------ Lifecycle Methods
    44. /**
    45. * 开启processor线程,用以处理http请求
    46. */
    47. public void start() throws LifecycleException {
    48. if (started) {
    49. throw new LifecycleException(sm.getString("httpProcessor.alreadyStarted"));
    50. }
    51. lifecycle.fireLifecycleEvent(START_EVENT, null);
    52. started = true;
    53. threadStart();
    54. }
    55. /**
    56. * 结束processor线程 --本章暂时没用到这个方法
    57. */
    58. public void stop() throws LifecycleException {
    59. if (!started) throw new LifecycleException(sm.getString("httpProcessor.notStarted"));
    60. lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    61. started = false;
    62. threadStop();
    63. }
    64. // ---------------------------------------------- Background Thread Methods
    65. /**
    66. * processor线程的run方法
    67. */
    68. public void run() {
    69. // processor线程会一直循环等待 处理请求,直到接收到一个停止信号
    70. while (!stopped) {
    71. // 阻塞等待下一个socket分配给当前processor
    72. Socket socket = await();
    73. if (socket == null) {
    74. continue;
    75. }
    76. // 处理这个 socket 请求
    77. try {
    78. process(socket);
    79. } catch (Throwable t) {
    80. log("process.invoke", t);
    81. }
    82. // 处理完这次请求了,让连接器回收自己,准备接收下次socket请求
    83. connector.recycle(this);
    84. }
    85. // Tell threadStop() we have shut ourselves down successfully
    86. synchronized (threadSync) {
    87. threadSync.notifyAll();
    88. }
    89. }
    90. /**
    91. * 启动processing线程
    92. */
    93. private void threadStart() {
    94. log(sm.getString("httpProcessor.starting"));
    95. thread = new Thread(this, threadName);
    96. thread.setDaemon(true);
    97. thread.start();
    98. if (debug >= 1) log(" Background thread has been started");
    99. }
    100. /**
    101. * 停止processing线程
    102. */
    103. private void threadStop() {
    104. log(sm.getString("httpProcessor.stopping"));
    105. stopped = true;
    106. assign(null);
    107. if (status != Constants.PROCESSOR_IDLE) {
    108. // Only wait if the processor is actually processing a command
    109. synchronized (threadSync) {
    110. try {
    111. threadSync.wait(5000);
    112. } catch (InterruptedException e) {
    113. ;
    114. }
    115. }
    116. }
    117. thread = null;
    118. }
    119. // -------------------------------------------------------- Package Methods
    120. /**
    121. * 这个方法是给连接器线程调用的。用于将socket交给HttpProcessor线程进行异步处理,以便连接器可以同时处理多个请求。
    122. *
    123. */
    124. synchronized void assign(Socket socket) {
    125. // 等待这个 Processor 获取上一个 Socket。
    126. // ps:如果这个方法执行完释放锁后,处理线程processor不给力,又被连接器线程给截胡了(理论上不存在),
    127. // 率先获取了processor对象锁,则连接器线程需要等待processor将上一个socket拿走后,才能给socket属性set新值
    128. while (available) {
    129. try {
    130. wait();
    131. } catch (InterruptedException e) {
    132. }
    133. }
    134. // 存储最新可用的 Socket,并 notify 当前这个processor对象对应的处理器线程
    135. this.socket = socket;
    136. available = true;
    137. notifyAll();
    138. if ((debug >= 1) && (socket != null)) {
    139. log(" An incoming request is being assigned");
    140. }
    141. }
    142. // -------------------------------------------------------- Private Methods
    143. /**
    144. * 等待连接器分配一个新的Socket过来。如何连接器正在关闭的话,连接器会分配一个为null的Socket过来
    145. */
    146. private synchronized Socket await() {
    147. // 等待连接器给分配一个新的 Socket
    148. while (!available) {
    149. try {
    150. wait();
    151. } catch (InterruptedException e) {
    152. }
    153. }
    154. // 告诉连接器,你分配来的Socket我收到了
    155. Socket socket = this.socket;
    156. available = false;
    157. notifyAll();
    158. if ((debug >= 1) && (socket != null)) {
    159. log(" The incoming request has been awaited");
    160. }
    161. return socket;
    162. }
    163. /**
    164. * 处理socket连接中携带的http请求
    165. */
    166. private void process(Socket socket) {
    167. boolean ok = true;
    168. boolean finishResponse = true;
    169. SocketInputStream input = null;
    170. OutputStream output = null;
    171. // 结构化并初始化我们需要的对象
    172. try {
    173. input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize());
    174. } catch (Exception e) {
    175. log("process.create", e);
    176. ok = false;
    177. }
    178. keepAlive = true;
    179. // 循环接收处理此长链接上的请求,直到客户端发出关闭连接的请求
    180. while (!stopped && ok && keepAlive) {
    181. finishResponse = true;
    182. try {
    183. request.setStream(input);
    184. request.setResponse(response);
    185. output = socket.getOutputStream();
    186. response.setStream(output);
    187. response.setRequest(request);
    188. ((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
    189. } catch (Exception e) {
    190. log("process.create", e);
    191. ok = false;
    192. }
    193. // 解析请求
    194. try {
    195. if (ok) {
    196. // 解析并记录本次请求对应连接的参数(客户端的地址与连接器监听的端口)
    197. parseConnection(socket);
    198. // 解析请求行,得到queryString、method、protocol、uri信息
    199. parseRequest(input, output);
    200. if (!request.getRequest().getProtocol().startsWith("HTTP/0")) {
    201. // 解析请求头,并将请求头中特殊的信息放到request对象的对应字段中
    202. parseHeaders(input);
    203. }
    204. if (http11) {
    205. // Sending a request acknowledge back to the client if requested.
    206. ackRequest(output);
    207. // If the protocol is HTTP/1.1, chunking is allowed.
    208. if (connector.isChunkingAllowed()) {
    209. response.setAllowChunking(true);
    210. }
    211. }
    212. }
    213. } catch (EOFException e) {
    214. // 这很可能是客户端或服务器上的套接字断开连接了
    215. ok = false;
    216. finishResponse = false;
    217. } catch (ServletException e) {
    218. ok = false;
    219. try {
    220. ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
    221. } catch (Exception f) {
    222. ;
    223. }
    224. } catch (InterruptedIOException e) {
    225. if (debug > 1) {
    226. try {
    227. log("process.parse", e);
    228. ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
    229. } catch (Exception f) {
    230. ;
    231. }
    232. }
    233. ok = false;
    234. } catch (Exception e) {
    235. try {
    236. log("process.parse", e);
    237. ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST);
    238. } catch (Exception f) {
    239. ;
    240. }
    241. ok = false;
    242. }
    243. // 将组装好的请求对象交给Servlet容器去进一步处理
    244. try {
    245. ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
    246. if (ok) {
    247. connector.getContainer().invoke(request, response);
    248. }
    249. } catch (ServletException e) {
    250. log("process.invoke", e);
    251. try {
    252. ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    253. } catch (Exception f) {
    254. ;
    255. }
    256. ok = false;
    257. } catch (InterruptedIOException e) {
    258. ok = false;
    259. } catch (Throwable e) {
    260. log("process.invoke", e);
    261. try {
    262. ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    263. } catch (Exception f) {
    264. ;
    265. }
    266. ok = false;
    267. }
    268. // Finish up the handling of the request
    269. if (finishResponse) {
    270. try {
    271. response.finishResponse();
    272. } catch (IOException e) {
    273. ok = false;
    274. } catch (Throwable e) {
    275. log("process.invoke", e);
    276. ok = false;
    277. }
    278. try {
    279. request.finishRequest();
    280. } catch (IOException e) {
    281. ok = false;
    282. } catch (Throwable e) {
    283. log("process.invoke", e);
    284. ok = false;
    285. }
    286. try {
    287. if (output != null) output.flush();
    288. } catch (IOException e) {
    289. ok = false;
    290. }
    291. }
    292. // We have to check if the connection closure has been requested
    293. // by the application or the response stream (in case of HTTP/1.0
    294. // and keep-alive).
    295. if ("close".equals(response.getHeader("Connection"))) {
    296. keepAlive = false;
    297. }
    298. // End of request processing
    299. status = Constants.PROCESSOR_IDLE;
    300. // Recycling the request and the response objects
    301. request.recycle();
    302. response.recycle();
    303. }
    304. // where循环结束
    305. try {
    306. shutdownInput(input);
    307. socket.close();
    308. } catch (IOException e) {
    309. ;
    310. } catch (Throwable e) {
    311. log("process.invoke", e);
    312. }
    313. socket = null;
    314. }
    315. /**
    316. * 解析并记录本次请求对应连接的参数(客户端的地址与连接器监听的端口)
    317. */
    318. private void parseConnection(Socket socket) throws IOException, ServletException {
    319. if (debug >= 2) {
    320. log(" parseConnection: address=" + socket.getInetAddress() + ", port=" + connector.getPort());
    321. }
    322. ((HttpRequestImpl) request).setInet(socket.getInetAddress());
    323. if (proxyPort != 0) {
    324. request.setServerPort(proxyPort);
    325. } else {
    326. request.setServerPort(serverPort);
    327. }
    328. request.setSocket(socket);
    329. }
    330. /**
    331. * 解析请求头,将解析出来的信息填充到request对象中
    332. */
    333. private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
    334. while (true) {
    335. HttpHeader header = request.allocateHeader();
    336. // Read the next header
    337. input.readHeader(header);
    338. if (header.nameEnd == 0) {
    339. if (header.valueEnd == 0) {
    340. return;
    341. } else {
    342. throw new ServletException(sm.getString("httpProcessor.parseHeaders.colon"));
    343. }
    344. }
    345. String value = new String(header.value, 0, header.valueEnd);
    346. if (debug >= 1) {
    347. log(" Header " + new String(header.name, 0, header.nameEnd) + " = " + value);
    348. }
    349. // Set the corresponding request headers
    350. if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
    351. request.setAuthorization(value);
    352. } else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
    353. parseAcceptLanguage(value);
    354. } else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
    355. Cookie cookies[] = RequestUtil.parseCookieHeader(value);
    356. for (int i = 0; i < cookies.length; i++) {
    357. if (cookies[i].getName().equals(Globals.SESSION_COOKIE_NAME)) {
    358. // Override anything requested in the URL
    359. if (!request.isRequestedSessionIdFromCookie()) {
    360. // Accept only the first session id cookie
    361. request.setRequestedSessionId(cookies[i].getValue());
    362. request.setRequestedSessionCookie(true);
    363. request.setRequestedSessionURL(false);
    364. if (debug >= 1)
    365. log(" Requested cookie session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
    366. }
    367. }
    368. if (debug >= 1) log(" Adding cookie " + cookies[i].getName() + "=" + cookies[i].getValue());
    369. request.addCookie(cookies[i]);
    370. }
    371. } else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
    372. int n = -1;
    373. try {
    374. n = Integer.parseInt(value);
    375. } catch (Exception e) {
    376. throw new ServletException(sm.getString("httpProcessor.parseHeaders.contentLength"));
    377. }
    378. request.setContentLength(n);
    379. } else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
    380. request.setContentType(value);
    381. } else if (header.equals(DefaultHeaders.HOST_NAME)) {
    382. int n = value.indexOf(':');
    383. if (n < 0) {
    384. if (connector.getScheme().equals("http")) {
    385. request.setServerPort(80);
    386. } else if (connector.getScheme().equals("https")) {
    387. request.setServerPort(443);
    388. }
    389. if (proxyName != null) request.setServerName(proxyName);
    390. else request.setServerName(value);
    391. } else {
    392. if (proxyName != null) request.setServerName(proxyName);
    393. else request.setServerName(value.substring(0, n).trim());
    394. if (proxyPort != 0) request.setServerPort(proxyPort);
    395. else {
    396. int port = 80;
    397. try {
    398. port = Integer.parseInt(value.substring(n + 1).trim());
    399. } catch (Exception e) {
    400. throw new ServletException(sm.getString("httpProcessor.parseHeaders.portNumber"));
    401. }
    402. request.setServerPort(port);
    403. }
    404. }
    405. } else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
    406. if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
    407. keepAlive = false;
    408. response.setHeader("Connection", "close");
    409. }
    410. //request.setConnection(header);
    411. /*
    412. if ("keep-alive".equalsIgnoreCase(value)) {
    413. keepAlive = true;
    414. }
    415. */
    416. } else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
    417. if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE)) sendAck = true;
    418. else throw new ServletException(sm.getString("httpProcessor.parseHeaders.unknownExpectation"));
    419. } else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
    420. //request.setTransferEncoding(header);
    421. }
    422. request.nextHeader();
    423. }
    424. }
    425. /**
    426. * 解析请求行,得到 queryString、method、protocol、uri信息。如果uri中包含jsessionid的话,同时也把jsessionid解析出来
    427. * 如果是HTTP/1.1 协议的话,默认采用持久化连接,更早版本的话默认采用非持久化连接。
    428. * 无论什么版本HTTP协议,如果请求头中声明了Connection的值的话,则以这个值为准
    429. */
    430. private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException {
    431. // 解析请求行
    432. input.readRequestLine(requestLine);
    433. // When the previous method returns, we're actually processing a request
    434. status = Constants.PROCESSOR_ACTIVE;
    435. String method = new String(requestLine.method, 0, requestLine.methodEnd);
    436. String uri;
    437. String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
    438. if (protocol.length() == 0) {
    439. protocol = "HTTP/0.9";
    440. }
    441. // Now check if the connection should be kept alive after parsing the request.
    442. if (protocol.equals("HTTP/1.1")) {
    443. http11 = true;
    444. sendAck = false;
    445. } else {
    446. http11 = false;
    447. sendAck = false;
    448. // For HTTP/1.0, connection are not persistent by default,
    449. // unless specified with a Connection: Keep-Alive header.
    450. keepAlive = false;
    451. }
    452. // Validate the incoming request line
    453. if (method.length() < 1) {
    454. throw new ServletException(sm.getString("httpProcessor.parseRequest.method"));
    455. } else if (requestLine.uriEnd < 1) {
    456. throw new ServletException(sm.getString("httpProcessor.parseRequest.uri"));
    457. }
    458. // Parse any query parameters out of the request URI
    459. int question = requestLine.indexOf("?");
    460. if (question >= 0) {
    461. request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
    462. if (debug >= 1) log(" Query string is " + ((HttpServletRequest) request.getRequest()).getQueryString());
    463. uri = new String(requestLine.uri, 0, question);
    464. } else {
    465. request.setQueryString(null);
    466. uri = new String(requestLine.uri, 0, requestLine.uriEnd);
    467. }
    468. // Checking for an absolute URI (with the HTTP protocol)
    469. if (!uri.startsWith("/")) {
    470. int pos = uri.indexOf("://");
    471. // Parsing out protocol and host name
    472. if (pos != -1) {
    473. pos = uri.indexOf('/', pos + 3);
    474. if (pos == -1) {
    475. uri = "";
    476. } else {
    477. uri = uri.substring(pos);
    478. }
    479. }
    480. }
    481. // Parse any requested session ID out of the request URI
    482. int semicolon = uri.indexOf(match);
    483. if (semicolon >= 0) {
    484. String rest = uri.substring(semicolon + match.length());
    485. int semicolon2 = rest.indexOf(';');
    486. if (semicolon2 >= 0) {
    487. request.setRequestedSessionId(rest.substring(0, semicolon2));
    488. rest = rest.substring(semicolon2);
    489. } else {
    490. request.setRequestedSessionId(rest);
    491. rest = "";
    492. }
    493. request.setRequestedSessionURL(true);
    494. uri = uri.substring(0, semicolon) + rest;
    495. if (debug >= 1)
    496. log(" Requested URL session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
    497. } else {
    498. request.setRequestedSessionId(null);
    499. request.setRequestedSessionURL(false);
    500. }
    501. // 标准化 URI,对非正常的URI进行修正
    502. String normalizedUri = normalize(uri);
    503. if (debug >= 1) {
    504. log("Normalized: '" + uri + "' to '" + normalizedUri + "'");
    505. }
    506. // Set the corresponding request properties
    507. ((HttpRequest) request).setMethod(method);
    508. request.setProtocol(protocol);
    509. if (normalizedUri != null) {
    510. ((HttpRequest) request).setRequestURI(normalizedUri);
    511. } else {
    512. ((HttpRequest) request).setRequestURI(uri);
    513. }
    514. request.setSecure(connector.getSecure());
    515. request.setScheme(connector.getScheme());
    516. if (normalizedUri == null) {
    517. log(" Invalid request URI: '" + uri + "'");
    518. throw new ServletException("Invalid URI: " + uri + "'");
    519. }
    520. if (debug >= 1) {
    521. log(" Request is '" + method + "' for '" + uri + "' with protocol '" + protocol + "'");
    522. }
    523. }
    524. /**
    525. * Send a confirmation that a request has been processed when pipelining.
    526. * HTTP/1.1 100 Continue is sent back to the client.
    527. *
    528. * @param output Socket output stream
    529. */
    530. private void ackRequest(OutputStream output) throws IOException {
    531. if (sendAck) output.write(ack);
    532. }
    533. protected void shutdownInput(InputStream input) {
    534. try {
    535. int available = input.available();
    536. // skip any unread (bogus) bytes
    537. if (available > 0) {
    538. input.skip(available);
    539. }
    540. } catch (Throwable e) {
    541. ;
    542. }
    543. }
    544. }

    SimpleContainer类

    这个类就不是Tomcat源码中的类了,这是我们自定义的一个Servlet容器类,它实现了org.apache.catalina.Container方法但是这里我们仅对invoke方法做了具体实现,因为默认连接器会调用该方法。该方法与第三章中ServletProcessor#process方法类似,创建类加载器,加载相关servlet类,创建servlet对象并调用其service方法。

    下面是精简后的代码,仅保留了invoke方法

    1. public class SimpleContainer implements Container {
    2. public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
    3. public void invoke(Request request, Response response) throws IOException, ServletException {
    4. String servletName = ((HttpServletRequest) request).getRequestURI();
    5. servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
    6. URLClassLoader loader = null;
    7. try {
    8. URL[] urls = new URL[1];
    9. URLStreamHandler streamHandler = null;
    10. File classPath = new File(WEB_ROOT);
    11. String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
    12. urls[0] = new URL(null, repository, streamHandler);
    13. loader = new URLClassLoader(urls);
    14. } catch (IOException e) {
    15. System.out.println(e.toString());
    16. }
    17. Class myClass = null;
    18. try {
    19. myClass = loader.loadClass(servletName);
    20. } catch (ClassNotFoundException e) {
    21. System.out.println(e.toString());
    22. }
    23. Servlet servlet = null;
    24. try {
    25. servlet = (Servlet) myClass.newInstance();
    26. servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
    27. } catch (Exception e) {
    28. System.out.println(e.toString());
    29. } catch (Throwable e) {
    30. System.out.println(e.toString());
    31. }
    32. }
    33. }

    你可能发现了,这里怎么没用门面类呢?额,我只能说书中源码中也没有,我也懒得加了😂,下一章详细介绍Servlet容器时再说。

    结果展示

    启动Bootstrap类,浏览器访问结果如下

    PrimitiveServlet

    ModernServlet

    注意,本章的简易Servlet容器仍然不能返回静态资源。

    好,Tomcat的默认连接器到此就介绍完了,后面章节中会一直使用这个连接器。下一章将具体研究下Servlet容器,敬请期待!

    源码分享

    https://gitee.com/huo-ming-lu/HowTomcatWorks

    本章的代码在这两个包下

      

  • 相关阅读:
    爬虫知识--02
    QtService实现Qt后台服务程序其一_基本使用步骤
    Java 求两个向量余弦相似度计算代码
    【C++】函数重载 & 引用 & 内联函数
    科技资讯|苹果Vision Pro获得被动冷却系统及数字表冠控制界面专利
    JMeter之脚本录制
    SpringBoot使用Nacos进行服务注册发现与配置管理
    STM32F4系列单片机GPIO概述和寄存器分析
    Win10找不到hosts文件的解决方案
    【MySQL数据库重点】第二节:MySQL基础知识(基本操作)
  • 原文地址:https://blog.csdn.net/weixin_37557894/article/details/138086689