《How Tomcat Works》读书笔记(二):Connector

来源:互联网 发布:电脑打碟软件 编辑:程序博客网 时间:2024/04/28 16:52

Chapter Three:Connector

tomcat的Connector名字叫做Coyote,我之前也写了几篇关于coyote的博客,不过在看了第三章后,才对tomcat的Connector有了更加深入的认识。需要说明的是,这一章的Connector只是一个简化版,而第四章介绍的也只是“默认”(旧版本)的Tomcat的Connector,正因为“默认”的Connector性能不佳,才产生了后来的coyote,这是后话。

StringManager

在讲述连接器前,首先介绍一个tomcat内部使用频率非常高的工具类——StringManager,简称sm(O(∩_∩)O有点歧义~)。我们知道tomcat是一个大项目,里面的package很多,而每个package内的类都需要输出很多信息,包括错误信息、调试信息,等等。为了降低耦合性,tomcat开发人员专门设计了这个sm类,用来存取相关的输出信息。每个package都有一个“LocalStrings.properties”文件,就像ini文件那样保存了这些信息。我们只需要像下面这样:

StringManager sm = StringManager.getManager("ex03.pyrmont.connector.http");

就可以获得一个特定于某个package输出信息的sm,然后直接getString即可。为了便于支持多语言,一般还包括了LocalStrings_es.properties和LocalStrings_ja.properties,sm会自动根据本地语言设置来选择相应的语种,这和Struts非常像(兴许Struts就是模仿tomcat的)。可惜,没有LocalStrings_cn.properties

Bootstrap

像我们看到的tomcat6一样,在这一章的小例子中,也专门将Bootstrap类提取出来,用于启动tomcat,当然这里还是非常简单的new了一个Connector,以后的章节会陆续添加功能。在这里,Connector实现了Runnable接口

public final class Bootstrap {
   public static void main(String[] args) {
     HttpConnector connector = new HttpConnector();
     connector.start();
   }
}

Connector

终于可以一睹Connector的“芳容”了!很遗憾,这一章的Connector还非常简陋,只是把前一章中监听Socket的部分代码copy了过来,略微有点不同的是:

// Hand this socket off to an HttpProcessor
       HttpProcessor processor = new HttpProcessor(this);
       processor.process(socket);

很明显,这里多了一个HttpProcessor ,其实就相当于“容器”的角色。tomcat发展到现在,其架构也还是:Connector+Container

HttpProcessor

可以说,HttpProcessor 是这一章的重头戏,大部分功能都是通过这个类,直接或间接地实现的。

照旧,在HttpProcessor 的process方法中,首先获取Socket的输入输出流、new 一个Request和Response,然后调用:

parseRequest(input, output);
     parseHeaders(input);

这两个方法是本类的核心,parseRequest是处理http请求行(就是类似“GET http://xxx.xxx/xxx?name=xxx”,位于http请求的第一行),parseHeaders则处理请求行之后的一堆header,比如content-length、cookie等。别看只有两个方法,深入进去其实调用了很多其他类的方法,看来解析一个http请求也不是那么容易的。

SocketInputStream

这个类转自tomcat源代码,它对Socket的原始inputstream进行了封装,负责将以下字段分离出来:

  • http schema:请求行中的GET,POST等等
  • URI:例如,http://xxx.xxx/xxx,这里还要区分是绝对路径还是相对路径
  • 查询字符串:就是“?”后面的那些键值对
  • header:http 请求的headers

当然,分离出来的字段都是以char数组的形式保存的,因为生成String的开销很大,通常都是“lazy load”,不到不得已不会随便new string

Populate Request

所谓populate,即是“赋值”的意思,就是调用Request的那些set方法,把之前解析出来的那些字段一个个放进Request对象中,供后续使用。考虑到解析、分割字符串的开销很大,tomcat的原则是把查询字符串(query string)和cookie的解析工作放到Servlet中,因为这些字段未必一定会用到,要的时候在生成也不迟,从而节省了系统资源。

具体的解析过程很啰嗦,基本上都是字符串处理,在一堆字节中摸爬滚打,这里就不赘述了。

Create Response

相比Request,Response的工作量就小了一些。但也有不少改进之处。首先,之前的Response.getWriter方法,单纯的返回一个包装了Socket的OutputStream的PrintWriter,但这是JAVA自带的PrintWriter,功能上不能完全满足tomcat的需要,例如它的print方法不能自动flush(具体可以参考JavaDoc)。所以,在这里通过两个类:ResponseStream和ResponseWriter,分别拓展了原始的ServletOutputStream和PrintWriter。下面摘取其中一部分代码:

public void write(String s, int off, int len) {
    super.write(s, off, len);
    super.flush();
  }

这是ResponseWriter的write方法,可见就是在PrintWriter的方法上多了一个flush而已

其他

搞定Request后,还是像上一章那样,把Request交给ServletProcessor或者StaticResourceProcessor,基本没有大的变动

总结

首先,这一章的服务器架构如下:(图片源自原书)

image

 

处理流程为:

  1. Bootstrap启动HttpConnector
  2. HttpConnector监听Socket端口,将得到的Socket对象交给HttpProcessor
  3. HttpProcessor通过调用parseRequest和parseHeaders方法,解析底层Socket流中的字节,生成Request对象和Response对象
  4. 把Request对象和Response对象交给“容器”处理,即ServletProcessor或者StaticResourceProcessor
  5. 载入Servlet对象,利用Facade模式,将Request对象和Response对象传入Servlet的service方法,处理,然后通过Response对象的writer,把响应内容返回给客户端
原创粉丝点击