
5.2 创建非阻塞的HTTP服务器
HTTP服务器的主要任务就是接收HTTP请求,然后发送HTTP响应。图5-4是本节所介绍的非阻塞的HTTP服务器范例的模型。

图5-4 HTTP服务器的对象模型
在这个对象模型中,HttpServer类是服务器主程序,由它启动服务器。AcceptHandler负责接收客户连接,RequestHandler负责接收客户的HTTP请求,对其解析,然后生成相应的HTTP响应,再把它发送给客户。Request类表示HTTP请求,Response类表示HTTP响应,Content类表示HTTP响应的正文。
5.2.1 服务器主程序:HttpServer类
HttpServer类是服务器的主程序,它的实现方式与本书4.3.2节的例程4-3(EchoServer.java)相似,仅启用了单个主线程,采用非阻塞模式来接收客户连接,以及收发数据。例程5-2是HttpServer类的源程序。
例程5-2 HttpServer.java


在HttpServer类的service()方法中,当ServerSocketChannel向Selector注册接收连接就绪事件时,设置了一个AcceptHandler附件。

AcceptHandler类的handle()方法负责处理接收连接就绪事件。当某种事件发生时,HttpServer类的service()方法从SelectionKey中获得Handler附件,然后调用它的handle()方法。

5.2.2 具有自动增长的缓冲区的ChannelIO类
自定义的ChannelIO类对SocketChannel进行了包装,增加了自动增长缓冲区容量的功能。当调用socketChannel.read(ByteBuffer buffer)方法时,如果buffer已满(即position=limit),那么即使通道中还有未接收的数据,read方法也不会读取任何数据,而是直接返回0,表示读到了零字节。
为了能读取通道中的所有数据,必须保证缓冲区的容量足够大。在ChannelIO类中,有一个requestBuffer变量,它用来存放客户的HTTP请求数据,当requestBuffer剩余容量已经不足5%,并且还有HTTP请求数据未接收时,ChannelIO会自动扩充requestBuffer的容量,该功能由resizeRequestBuffer()方法完成。
例程5-3是ChannelIO类的源程序,它的read()方法和write()方法利用SocketChannel来接收和发送数据,并且它还提供了实用方法transferTo(),该方法能把文件中的数据发送到SocketChannel中。
例程5-3 ChannelIO.java


5.2.3 负责处理各种事件的Handler接口
Handler接口负责处理各种事件,它的定义如下。

Handler接口有AcceptHandler和RequestHandler两个实现类。AcceptHandler负责处理接收连接就绪事件,RequestHandler负责处理读就绪和写就绪事件。更确切地说,RequestHandler负责接收客户的HTTP请求,以及发送HTTP响应。
5.2.4 负责处理接收连接就绪事件的AcceptHandler类
AcceptHandler负责处理接收连接就绪事件。它获得与客户连接的SocketChannel,然后向Selector注册读就绪事件,并且创建了一个RequestHandler,把它作为SelectionKey的附件。当读就绪事件发生时,将由这个RequestHandler来处理该事件。例程5-4是AcceptHandler类的源程序。
例程5-4 AcceptHandler.java


在以上AcceptHandler的handle()方法中,还创建了一个ChannelIO,RequestHandler与它关联。RequestHandler会利用ChannelIO来接收和发送数据。
5.2.5 负责接收HTTP请求和发送HTTP响应的RequestHandler类
RequestHandler先通过ChannelIO来接收HTTP请求,当接收到了HTTP请求的所有数据后,就对HTTP请求数据进行解析,创建相应的Request对象,然后依据客户的请求内容,创建相应的Response对象,最后发送Response对象中包含的HTTP响应数据。为了简化程序,RequestHandler仅仅支持GET和HEAD这两种请求方式。例程5-5是RequestHandler的源程序。
例程5-5 RequestHandler.java



5.2.6 代表HTTP请求的Request类
RequestHandler通过ChannelIO读取HTTP请求数据时,这些数据被放在requestByteBuffer中。当HTTP请求的所有数据接收完毕,就要对requestByteBuffer中的数据进行解析,然后创建相应的Request对象。Request对象就表示特定的HTTP请求。
本范例仅支持GET和HEAD请求方式,在这两种方式下,HTTP请求没有正文部分,并且以“\r\n\r\n”结尾。Request类有3个成员变量:action、uri和version,它们分别表示HTTP请求中的请求方式、URI和HTTP的版本。例程5-6是Request类的源程序。
例程5-6 Request.java



5.2.7 代表HTTP响应的Response类
Response类表示HTTP响应。它有3个成员变量:code、headerBuffer和content,它们分别表示HTTP响应中的状态代码、响应头和正文。Response类的prepare()方法负责准备HTTP响应的响应头和正文内容,send()方法负责发送HTTP响应的所有数据。例程5-7是Response类的源程序。
例程5-7 Response.java



5.2.8 代表响应正文的Content接口及其实现类
Response类有一个成员变量content,表示响应正文,它被定义为Content类型。

Content接口表示响应正文,它的定义如下。

Content接口继承了Sendable接口,Sendable接口表示服务器端可发送给客户的内容,它的定义如下:

Content接口有StringContent和FileContent两个实现类。StringContent表示字符串形式的正文,FileContent表示文件形式的正文。例如在RequestHandler类的build()方法中,如果HTTP请求方式不是GET和HEAD,就创建一个包含StringContent的Response对象,否则就创建一个包含FileContent的Response对象。

下面主要介绍FileContent类的实现。FileContent类有一个成员变量fileChannel,它表示读文件的通道。FileContent类的send()方法把fileChannel中的数据发送到ChannelIO的SocketChannel中,如果文件中的所有数据发送完毕,send()方法就返回false。例程5-8是FileContent类的源程序。
例程5-8 FileContent.java



5.2.9 运行HTTP服务器
在chapter05目录下运行命令“java HttpServer”,就启动了HTTP服务器。在本范例的root目录下存放了各种供浏览器访问的文档,比如login.htm、hello.htm和data.rar文件等。打开IE浏览器,输入URL:http://localhost/login.htm或者http://localhost/data.rar,就能接收到服务器发送过来的相应文档。如果浏览器按照POST方式访问hello.htm,服务器就会返回HTTP405错误,因为本服务器不支持POST方式。