上篇文章已经实现了一个简易版的连接器,理解了那个连接器,再看Tomcat的默认连接器就会轻松很多,本章中的默认连接器指的是Tomcat4的默认连接器,尽管该连接器已经被弃用了,被另一个运行速度更快的连接器 Coyote 取代,但它仍然是一个很不错的学习工具。
Tomcat的连接器是一个独立的模块,可以被插入到servlet容器中。之所以说它独立,是因为市面上的连接器有很多,只要它满足了catalina中的连接器要求,那就可以放入Tomcat中使用而不用修改其他代码。类似于我们电脑中的内存条,只要主板支持,就有很多厂家的内存条供我们选择,如果空间不够用了还可以选择更大存储的内存条,是一个可插拔的设备。
那么catalina能使用的连接器,有哪些要求呢?
与我们的简易连接器相同,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之前,浏览器与服务器建立的连接都是短连接,一次请求结束后,连接即刻关闭。但是随着网页内容越来越丰富,一个网页的展现往往需要多次请求后台,如果每次请求建立的连接都是用完即关的话,会很浪费资源,效率也不高。所以HTTP 1.1引入了长连接的概念,反映在HTTP请求头中就是 Connection: keep-alive ,当请求头或响应头中带上了这个长链接标识的话,就代表客户端或服务器开启了长链接,接下来该网页的请求都会使用者一个Socket连接来进行通信,效率大大提高。
那什么时候长连接会被关闭呢?
这块内容在第二章中已经介绍过,不再重复介绍。
使用HTTP1.1的客户端可以在向服务端发送请求体之前,先发送一个这个消息
Expect:100-continue
这个通常用在客户端准备了一个特大消息体的时候,先问候一下服务端看它在不在,如果服务端不在,那你客户端拖着一个特大消息体扔给互联网,那属实是浪费所有资源。
当客户端接收到 “ HTTP/1.1 100 Continue” 响应消息后,代表服务器是在岗的,就可以继续沟通了。
好,HTTP1.1的新特性已讲完,现在回到连接器的设计上。
来看下默认连接器的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,我就不再重新编码了
这仍然是启动整个Web容器的启动类。该类创建了连接器与Servlet容器,并将两者做了个关联。在执行完HttpConnector的 initialize() 与 start() 两个方法后,Web容器就启动了。
- 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实现了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源码代码量很大,我精简了一下,仅列出几个主要的属性及方法供你串通逻辑。
- public final class HttpConnector implements Connector, Lifecycle, Runnable {
-
- // ServerSocket的backlog参数,最大等待连接数量
- private int acceptCount = 10;
-
- // 关联的容器
- protected Container container = null;
-
- // 已经被创建的 processor 集合
- private final Vector
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一样,都是处理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容器去执行后续操作。
下面是我精简后的代码,仅保留了主要的几个方法
- 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) {
- ;
- }
- }
-
- }
这个类就不是Tomcat源码中的类了,这是我们自定义的一个Servlet容器类,它实现了org.apache.catalina.Container方法但是这里我们仅对invoke方法做了具体实现,因为默认连接器会调用该方法。该方法与第三章中ServletProcessor#process方法类似,创建类加载器,加载相关servlet类,创建servlet对象并调用其service方法。
下面是精简后的代码,仅保留了invoke方法
- 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
本章的代码在这两个包下