第一章 一个简单的web服务器
Web服务器
一个最简单的web服务器会包括3个东西。
- HttpServer:ServerSocket等待客户端的请求,一旦获得一个连接请求,就创建一个Socket实例来与客户端进行通信。从Socket中获取inputStream和outputStream。利用inputStream创建Request,解析URI,读取请求的参数等。利用OutputStream创建Response,将处理后的结果写入outputStream,可以是静态资源或者动态资源。
- Request:解析HTTP请求中的原始数据,把uri解析出来等。
- Response:将要发送的资源(静态,动态)写到socket的outputStream
基于Java的Web服务器会使用2个重要的类,并通过HTTP消息进行通信。
- java.net.Socket类:套接字是网络连接的一个端点。套接字使得一个应用可以从网络中读取和写入数据。放在两个不同计算机上的两个应用可以通过连接发送和接受字节流。为了从你的应用发送一条信息到另一个应用,你需要知道另一个应用的IP地址和套接字端口。
- java.net.ServerSocker类:等待客户端的请求,一旦获得一个连接请求,就创建一个Socket示例来与客户端进行通信。
Servlet
所有的servlet程序都必须实现javax.servlet.servlet接口。
public void init(ServletConfig config) throws ServletExceptionpublic void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOExceptionpublic void destroy()public ServletConfig getServletConfig()public java.lang.String getServletInfo()
servlet接口相当于一个标准,只有实现了接口中的这些方法的类,才能被称之为servlet。
为什么要按照这个servlet标准来写呢,因为servlet容器要调用特定的方法,实例化servlet,或者传递参数,或者销毁。
Example
HttpServer.java —— 创建socket, request, response。
Request.java —— 利用socket的inputstream创建request,解析url,得到请求的文件名。
Response.java —— 利用socket的outputStream创建response,sendStaticResource(),发送请求的文件。
第二章 一个简单的servlet容器
Servlet容器
一个功能齐全的servlet容器有以下几件事要做:
- 第一次调用某个servlet时,要载入该servlet类,并调用其init(),整个声明周期仅此一次。
- 每收到一个request请求,创建一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。
- 调用该servlet的service()方法,将servletRequest对象和servletResponse对象作为参数传入。
- 关闭该servlet类时,调用其destroy()方法,并卸载该servlet类。
Example
HttpServer1.java —— 创建socket, request, response, 根据request parse出的uri判断是静态资源还是servlet,如果是静态资源,servletProcessor为StaticResourceProcessor, 否则为ServletProcessor1,然后调用servletProcessor的process()方法。
Request.java —— 实现ServletRequest接口。
Response.java —— 实现ServletResponse接口,重写getWriter()方法。
StaticResourceProcessor.java —— 只有一个函数,process(), 直接调用Response的sendStaticResource()方法。
ServletProcessor1.java —— 只有一个函数,process()。 从request中得到要载入的servlet的name,通过类加载器将其加载进来,向下转换类型为servlet,然后调用servlet.service((ServletRequest) request, (ServletResponse) response)。
Example Enhance
servlet.service((ServletRequest) request, (ServletResponse) response)
需要将自定义的request和response向上转型为javax.servlet.ServletRequest,javax.servlet.ServletResponse。
在servlet内部就可以调用它们的公共方法,比如parse(), senderStaticResource(),而这些不应该被servlet内部调用。
但是也不能将这两个方法设置为私有,因为servletProcess类需要使用它。
可以使用外观类解决。——设计模式
HttpServer2.java —— 与HTTPServer1类似,只是在await()方法中使用servletProcessor2代替serverProcessor1。
Request.java —— 与之前相同。
Response.java —— 与之前相同。
RequestFacade.java—— 定义一个ServletRequest类型的private变量,public方法只定义可供别人使用的方法。
ResponseFacade.java—— 定义一个ServletResponse类型的private变量,public方法只定义可供别人使用的方法。
servletProcessor2.java —— 与servletProcessor类似,只是不直接将request和response作为servlet.service()的参数,而是,先利用request,response初始化新的requestFacade,requestFacade, 再调用servlet.service((ServletRequest) requestFacade, (ServletResponse) requestFacade)。
第三章 连接器 Connector
HttpConnector + HttpProcessor = 上章的HttpServer1
本章很大篇幅都是在讲怎么解析Request,设置Response的。
Example
Bootstrap.java —— 实例化HttpConnector。
HttpConnector.java —— 等待HTTP请求。
实现了Runnable, 被实例化时,会另起一个线程执行。
为每个请求创建一个HttpProcessor,调用HttpProcessor对象的process(socket)方法。
HttpProcessor.java ——process()方法完成的操作:
创建HttpRequest。从socket中得到inputStream,实例化为SocketInputStream,再用来创建HttpRequest。
创建HttpResponse对象。
解析请求, 填充HttpRequest对象(工作量很大)。parseRequest()
将HttpRequest,HttpResponse对象传递给servletProcessor或staticResourceProcessor的process()方法。
SocketInputStream.java —— 继承自InputStream。
使用这个类是为了调用它的readRequestLine()和readHeader()。
HttpRequest.java —— 很多功能
HttpRequestFacade.java —— RequestFacade.java的外观类。
HttpResponse.java —— 很多功能
HttpResponseFacade.java —— HttpResponse.java的外观类。
ServletProcessor.java —— 与上章功能类似。
但是它的process()方法接收HttpRequest,HttpResponse对象, 而不是Request,Response对象。
StaticResourceProcessor.java —— 与上章功能相同。
第四章 Tomcat的默认连接器
基础介绍
Tomcat中的连接器是一个独立的模块,可以被插入到servlet容器中,而且,有很多连接器可供使用。
Tomcat中使用的连接器必须满足以下条件:
- 实现org.apache.catalina.Connector接口;
- 创建实现了org.apache.catalina.Request接口的request对象;
- 创建实现了org.apache.catalina.Response接口的Response对象。
Tomcat中的默认连接器工作原理与上章类似。
等待Http请求,创建request对象和response对象, 然后调用org.apache.catalina.Connector接口的invoke()方法,将request对象和response对象传给servlet容器。
在invoke()方法内部,servlet容器会载入相应的servlet类,调用其service()方法,管理session()对象,记录错误信息等操作。
Http 1.1新特性
Connector接口
Tomcat的连接器必须实现org.apache.catalina.Connector接口。
在接口中最重要的方法:
- setContainer()
将连接器和某个servlet容器关联。
- getContainer()
返回与当前连接器关联的servlet容器。
- createResponse()
创建一个response对象。
连接器和servlet容器是一一对应的。
HttpConnector类
HttpProcessor实现了java.lang.Runnable接口——“连接器线程”
它的功能:
- 创建服务器套接字
- 维护HttpProcessor实例
对于每个HTTP请求,HttpConnector会调用createProcessor(),从HttpProcessor连接池中获取一个对象。
如果连接池中没有HttpProcessor使用,即达到了maxProcessors,关闭socket,不处理这个请求。
- 提供Http请求服务
HttpConnector的主要业务逻辑在其run()方法中,包含一个while循环,在该循环体内,服务器套接字等待HTTP请求,直到HttpConnector对象关闭。如下所示。
while (!stopped) {// Accept the next incoming connection from the server socketSocket socket = null;try {socket = serverSocket.accept();}catch (Exception e) {continue;}// Hand this socket off to an HttpProcessor//这是第三章中的做法,是同步的,即在processor解析完request之前,都不能接收新的http请求。HttpProcessor processor = new HttpProcessor(this);processor.process(socket);}
本章中会从连接池获取到HttpProcessor后,调用HttpProcessor实例的assign()读取socket输入流,解析HTTP请求。
这样HttpConnector不需要等待HttpProcessor解析完请求后,再接收下一个Http请求。
processor.assign(socket)
HttpProcessor类
上章的process()方法是同步的。需要等待processor解析完请求后,再接收下一个Http请求。
HttpProcessor processor = new HttpProcessor(this);processor.process(socket);
但是在tomcat的默认连接器中,HttpProcessor实现了java.lang.Runnable接口,每个HttpProcessor实例可以运行在自己的线程中——“处理器线程”。
每个HttpConnector创建HttpProcessor实例后,会调用其start()方法,启动HttpProcessor实例的处理器线程。
HttpProcessor类的run()方法:
public void run(){while(!stopped){Socket socket = await();if(socket == null)continue;try{//通过socket的输入流,解析requestprocess(socket);}catch(Throwable t){log("process.invoke", t)}//结束这个request的处理后,调用连接器的recycle()方法,将当前HttpProcessor实例压入栈中connector.recycle(this)}//stop的操作,通知其他线程synchronized(threadSync){threadSYnc.notifyAll();}}
以下是HttpConnector的recycle()方法:
void recycle(HttpProcessor processor){processors.push(processor);}
while循环执行到await()时会阻塞,知道它从HttpConnector中获取到了新的Socket对象。
即直到HttpConnector调用HttpProcessor的assign()方法前,都会一直阻塞。
但是,await()和assign()不是运行在同一个线程中的。
assign()方法是从HttpConnector对象的run()方法中调用的,assign()如何通知await(),自己已经被调用了呢?
通过一个名为available的布尔变量和java.lang.Object类的wait()方法和notifyAll()方法来进行沟通。
wait()方法会使当前线程进入等待状态,直到其他线程调用了这个对象的notify()和notifyAll()。
以下是HttpProcessor类的assign()方法和await()方法实现。
synchronized void assign(Socket socket) {while(available) {try{wait();}catch(InterruptedException e){}}this.socket = socket;available = true;notifyAll();...}private synchronized Socket await(){while(!available){try{wait();}catch(InterruptedException e){}}//使用局部变量socket,可以在当前socket对象没处理完时,不影响assign()接收下一个socket对象。Socket socket = this.socket;available = false;//把assign()从阻塞中释放notifyAll();if(debug>=1 && socket!=null){log("incoming request has been awaited");}return socket;}
处理请求
当Socket对象被赋值给HttpProcessor类后,HttpProcessor的run()方法会调用processor()方法。
process()方法会执行以下3个操作:
- 解析connection
- 解析request
- 解析request headers
完成解析后,process()方法将request和response对象作为参数传入servlet容器的invoke()方法。
try{((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());if(ok){connector.getContainer().invoke(request, response);}}
servlet容器invoke()
servlet容器的invoke()方法会创建一个类载入器,载入相关servlet类,并调用该servlet类的service()方法。
Servlet容器
Catalina中的servlet容器,共有4种类型:
- Engine:表示整个Catalina servlet引擎;
- Host:表示包含有一个或多个Context容器的虚拟主机;
- Context:表示一个web应用程序。一个Context可以有多个Wrapper;
- Wrapper:表示一个独立的servlet容器。
