第一章 一个简单的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接口。

  1. public void init(ServletConfig config) throws ServletException
  2. public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
  3. public void destroy()
  4. public ServletConfig getServletConfig()
  5. 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对象关闭。如下所示。

  1. while (!stopped) {
  2. // Accept the next incoming connection from the server socket
  3. Socket socket = null;
  4. try {
  5. socket = serverSocket.accept();
  6. }
  7. catch (Exception e) {
  8. continue;
  9. }
  10. // Hand this socket off to an HttpProcessor
  11. //这是第三章中的做法,是同步的,即在processor解析完request之前,都不能接收新的http请求。
  12. HttpProcessor processor = new HttpProcessor(this);
  13. processor.process(socket);
  14. }

本章中会从连接池获取到HttpProcessor后,调用HttpProcessor实例的assign()读取socket输入流,解析HTTP请求。
这样HttpConnector不需要等待HttpProcessor解析完请求后,再接收下一个Http请求。

  1. processor.assign(socket)

HttpProcessor类

上章的process()方法是同步的。需要等待processor解析完请求后,再接收下一个Http请求。

  1. HttpProcessor processor = new HttpProcessor(this);
  2. processor.process(socket);

但是在tomcat的默认连接器中,HttpProcessor实现了java.lang.Runnable接口,每个HttpProcessor实例可以运行在自己的线程中——“处理器线程”。
每个HttpConnector创建HttpProcessor实例后,会调用其start()方法,启动HttpProcessor实例的处理器线程。

HttpProcessor类的run()方法:

  1. public void run(){
  2. while(!stopped){
  3. Socket socket = await();
  4. if(socket == null)
  5. continue;
  6. try{
  7. //通过socket的输入流,解析request
  8. process(socket);
  9. }catch(Throwable t){
  10. log("process.invoke", t)
  11. }
  12. //结束这个request的处理后,调用连接器的recycle()方法,将当前HttpProcessor实例压入栈中
  13. connector.recycle(this)
  14. }
  15. //stop的操作,通知其他线程
  16. synchronized(threadSync){
  17. threadSYnc.notifyAll();
  18. }
  19. }

以下是HttpConnector的recycle()方法:

  1. void recycle(HttpProcessor processor){
  2. processors.push(processor);
  3. }

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()方法实现。

  1. synchronized void assign(Socket socket) {
  2. while(available) {
  3. try{
  4. wait();
  5. }catch(InterruptedException e){
  6. }
  7. }
  8. this.socket = socket;
  9. available = true;
  10. notifyAll();
  11. ...
  12. }
  13. private synchronized Socket await(){
  14. while(!available){
  15. try{
  16. wait();
  17. }catch(InterruptedException e){
  18. }
  19. }
  20. //使用局部变量socket,可以在当前socket对象没处理完时,不影响assign()接收下一个socket对象。
  21. Socket socket = this.socket;
  22. available = false;
  23. //把assign()从阻塞中释放
  24. notifyAll();
  25. if(debug>=1 && socket!=null){
  26. log("incoming request has been awaited");
  27. }
  28. return socket;
  29. }

处理请求

当Socket对象被赋值给HttpProcessor类后,HttpProcessor的run()方法会调用processor()方法。

process()方法会执行以下3个操作:

  • 解析connection
  • 解析request
  • 解析request headers

完成解析后,process()方法将request和response对象作为参数传入servlet容器的invoke()方法。

  1. try{
  2. ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
  3. if(ok){
  4. connector.getContainer().invoke(request, response);
  5. }
  6. }

servlet容器invoke()

servlet容器的invoke()方法会创建一个类载入器,载入相关servlet类,并调用该servlet类的service()方法。

Servlet容器

Catalina中的servlet容器,共有4种类型:

  • Engine:表示整个Catalina servlet引擎;
  • Host:表示包含有一个或多个Context容器的虚拟主机;
  • Context:表示一个web应用程序。一个Context可以有多个Wrapper;
  • Wrapper:表示一个独立的servlet容器。