Tornado6.0.3-源码分析之HTTPServer

一、前述

在开始正篇之前,先简单了解下http协议。
http协议是基于客户/服务器模型,面向tcp连接的协议。典型的http事务处理过程如下:
(1)客户端与服务器建立连接;
(2)客户端向服务器提出请求;
(3)服务器接受请求,并根据请求返回对应的响应;
(4)客户端与服务器关闭连接。

Tornado在实现http协议时,有一种代理模式的味道。所谓代理,就是在中间起一个沟通协调作用的主体;以二手车交易来说,在买家和卖主之间的中间商,就是一个代理的作用,负责不断把信息在买家和卖主之间传递,起一个桥梁作用。

二、相关类之间的关系

在http server的相关实现中,涉及到多个不同的类。先简单过一个基本的类间关系,有利于查看代码实现。

  • HTTPServer
    该类就是实现了一个非阻塞、单线程模式的http server端功能。该类继承自TCPServer,因为http协议是在tcp传输协议之上的,所以继承TCPServer是自然的,这也隐式说明,TCPServer的三种实例化方法,也是适用于HTTPServer;而http协议之上的应用实现,一般是web服务功能,在Tornado的实现,从HTTPServer的角度来看,所谓的一个服务功能,就是指继承自HTTPServerConnectionDelegate类的子类实现(或者,为了向后兼容,也可以是一个以HTTPServerRequest类实例为参数的一个回调函数),这个服务功能就是在HTTPServer实例化时,必需要传递的一个参数。到时在收到http请求时,HTTPServer会把相应的http请求,传递给这个服务功能去进一步处理。

  • HTTP1ServerConnection
    代表着一个http连接的服务端。由HTTPServer调用生成相应的实例。可以这么理解,一个HTTPServer,指的是整体概念上的http服务器实现,而对于每个具体的连接,在其中冲当服务端实现的就是由HTTP1ServerConnection实现;因此一个 HTTPServer可以根据实际的客户端连接,不断生成HTTP1ServerConnection实例。

  • HTTPServerConnectionDelegate
    接口定义,约定了start_request接口和on_close接口去进一步处理相应的http请求。

  • HTTPMessageDelegate
    接口定义类,用于实现对http请求或者响应消息的处理。

  • HTTPConnection
    用于响应http请求的类接口定义。约定有write_headers接口,用于写http头域信息;write接口,用于写http的body内容;finish接口,提示全部数据已经写完成。(这里注意不要被名字给骗了,这只个定义响应相关的接口,而不是定义http协议连接的实现,http1.x协议相关的实现定义在HTTP1Connection类中)

  • HTTP1Connection
    继承自HTTPConnection,这里定义实现了http1.x 协议的逻辑实现。实现了读取http请求,并提交给相应的消息处理器(HTTPMessageDelegate类的实例)进行消息处理;以及由上层调用相关的写方法回复响应。

除此之外,还有相关的其它类,这里就不作一一介绍了。个人觉得比较容易混淆的,是这些类的功能实现。

简单总结一下:
1、HTTPServer是http服务端整体概念上的实现,实例化时,需要提供一个HTTPServerConnectionDelegate子类实例,比如后续应用层上的 Application 对象;
2、HTTPServer对于每一个客户端连接,会生成一个HTTP1ServerConnection实例,该实例代表着一个连接交互中的具体的http服务端的角色;
3、对于一次http连接,具体的交互实现由HTTP1Connection类来完成。该类会读取相应的http请求消息,传递给HTTPMessageDelegate类的对象去处理,并发送相应的响应消息。

示意图如下(绿色表示类继承、深蓝色表示类接口继承实现、黄色表示类相关关系)
在这里插入图片描述

三、HTTPServer类分析

3.1 继承关系
class HTTPServer(TCPServer, Configurable, httputil.HTTPServerConnectionDelegate)

从代码可以很直观看出,HTTPServer的类继承关系。
继承自TCPServer类,从而由TCPServer类来完成传输层相应的工作,不过从之前的TCPServer类的分析中,我们知道,对于继承自TCPServer类的子类,必须实现TCPServer约定的handle_stream处理方法;
继承自Configurable类,因此真正的初始化实现,在initialize方法中实现;
继承自HTTPServerConnectionDelegate接口类,因此需要实现该接口类约定的接口,start_request方法和on_close方法。其中start_request方法在一个请求消息需要开始处理时被调用,其第一个参数server_conn表示传输层意义的TCP的长连接;而第二个参数request_conn表示http协议概念上的一次请求与响应的交互连接;而on_close方法在tcp连接关闭时,被调用。

3.2 handle_stream方法实现

实现TCPServer类的缺省接口,实现对于特定网络连接的异步处理实现

def handle_stream(self, stream: iostream.IOStream, address: Tuple) -> None:
    # 保存本次http连接的相关上下文信息,说白了,就是保留一些信息内容,然后放在一个类里面。
    context = _HTTPRequestContext(
        stream, address, self.protocol, self.trusted_downstream
    )
    # 生成一个服务端处理实现实例
    conn = HTTP1ServerConnection(stream, self.conn_params, context)
    self._connections.add(conn)
    # 该服务端实例启动服务,需要注意的是,这里是异步启动的,在start_serving内部,是生成了一个协程对象,
    # 提交到stream.io_loop中去异步执行了,从而这里在调用完了之后就立即返回了。
    # 也就是说,从这里可以看到HTTPServer的异步网络实现,每个连接对应生成一个协程对象去执行具体的网络消息
    # 交互,从而对于HTTPServer来说,对于客户端连接,实现了非阻塞。
    conn.start_serving(self)

HTTP1ServerConnection.start_serving方法主要是将HTTP1ServerConnection._server_request_loop协程转换成future对象从而实现协程的异步并发处理。接下来看下_server_request_loop协程的具体工作内容。

async def _server_request_loop(
    self, delegate: httputil.HTTPServerConnectionDelegate
) -> None:
    try:
    	# 循环处理当前连接的HTTP消息
        while True:
            # 实例化一个HTTP1Connection类,前面说了,这个类是http 1.x的具体实现,包含了读写交互过程
            conn = HTTP1Connection(self.stream, False, self.params, self.context)
            # 这里的delegate是之前的HTTPServer类,其实现的start_request方法,就是把当前的连接传递给
            # 最开始、初始化HTTPServer时的回调对象上,也就是说传递给应用层对象,由应用层对象返回一个
            # HTTPMessageDelegate实例,这个HTTPMessageDelegate代理实例用于后续继续把相应的请求的具体
            # 内容传递给应用层去进一步处理。
            request_delegate = delegate.start_request(self, conn)
            try:
            	# 这里的conn.read_response最终是调用到HTTP1Connection._read_message方法,这个方法
            	# 也是返回一个协程,而这里直接使用await方法等待这个协程对象,其作用就是阻塞等待这个协程
            	# 处理完成。在这里面会有具体的数据读取处理过程。这里要理解清楚的是,对于HTTPServer来说
            	# _server_request_loop方法是以立即返回的,协程的运行是提交到io loop循环中去的;
            	# 而对于每个具体的_server_request_loop的协程对象内部,是阻塞执行每一个消息交互的。
                ret = await conn.read_response(request_delegate)
            except:
            	# 异常的处理代码就不贴出来了
            	pass
            if not ret:
                return
            await asyncio.sleep(0)
    finally:
        delegate.on_close(self)

ok,顺着这个逻辑,我们继续查看HTTP1Connection.read_response内部实现。在HTTP1Connection.read_response内部,重点是看其返回的HTTP1Connection._read_message的协程对象,在这个协程里面完成数据的读取和传递给HTTPMessageDelegate对象,即前面的request_delegate参数,从而实现把读取提交给上层应用层去进行详细的处理。因为这个协程对象代码比较长,我就截取重点说明。
首先要知道的是HTTP请求信息由3部分组成:
请求方法URI协议/版本
请求头(Request Header)
请求正文

# 这里就可以看到了,从IOStream的read_until_regex方法进行读取http直到请求头结束,在请求头和请求正文
# 有一个空行,这个是http协议规定的。从之前的IOStream类分析,可以知道,这里返回的是一个future对象,
# 这个future对象的结果就是读取到的数据。
header_future = self.stream.read_until_regex(
    b"\r?\n\r?\n", max_bytes=self.params.max_header_size
)
# 所以这里只是区分要不要设置超时等待读取数据,并且把数据保存在header_data中
if self.params.header_timeout is None:
    header_data = await header_future
else:
    try:
        header_data = await gen.with_timeout(
            self.stream.io_loop.time() + self.params.header_timeout,
            header_future,
            quiet_exceptions=iostream.StreamClosedError,
        )
    except gen.TimeoutError:
        self.close()
        return False

获取到请求头相关信息后,解析成首行和头部对象HTTPHeaders

start_line_str, headers = self._parse_headers(header_data)
...
with _ExceptionLoggingContext(app_log):
     # 这里把首行信息和头部信息提交给上层去处理,这个delegate就是前面的HTTPMessageDelegate对象,
     # 即request_delegate参数
    header_recv_future = delegate.headers_received(start_line, headers)
    if header_recv_future is not None:
        await header_recv_future

然后接着读取body正文,流程也跟读取头部差不多,只是HTTP1Connection定义了一个_read_body方法而已,最终依据头部参数信息,可能进一步区分调用哪个读取方法;而不管是self._read_fixed_body还是self._read_chunked_body,还是self._read_body_until_close,都是self.stream.read_bytes进行数据读取,然后通过delegate.data_received(body)传递给上层处理。

if content_length is not None:
    return self._read_fixed_body(content_length, delegate)
if headers.get("Transfer-Encoding", "").lower() == "chunked":
    return self._read_chunked_body(delegate)
if self.is_client:
    return self._read_body_until_close(delegate)

到这里为止,对于http请求的消息内容是都已经提交给上层应用层了,那么上层可以依据逻辑实现,生成相应的响应发送给客户端。还记得HTTP1Connection是继承自HTTPConnection,而HTTPConnection是定义写操作的接口吗?是的。HTTP1Connection需要实现HTTPConnection的写操作接口:write_headers,write,finish,具体写实现细节,自行看下代码就可以了,需要注意的是在前面的读取消息内容后,如果本连接没有被脱离(脱离就是指失去对IOStream对象的直接控制,即被调用了detach方法,这个IOStream对象变成由上层来直接管理)或者异步写还没有完成,则原来的_read_message对象,会阻塞等待写操作完成,即await self._finish_future。

那么上层为什么能调用HTTP1Connection对象的这些写接口呢?原因是在前面的_server_request_loop实现中,通过request_delegate = delegate.start_request(self, conn),即HTTPServer.start_request方法,这个conn对象(即HTTP1Connection对象,是直接传递到上层的),从而上层能保存这个对象,进而能调用到HTTP1Connection的写操作接口。

四、总结HTTPServer承上启下的实现

HTTPServer直接继承自TCPServer,并且实现TCPServer.handle_stream方法,从而能处理跟客户端的连接,这就是启下;而HTTPServer在初始化时,接受了一个回调对象,这个对象一般是上层应用层对象,在通过HTTPServer.start_request被调用时,会把相应的http连接对象(一个表示长连接的HTTP1ServerConnection对象,一个表示单次http协议交互的HTTP1Connection对象)传给上层回调对象,而上层回调对象则是返回一个HTTPMessageDelegate对象,用于后续的http请求消息的传递,进而上层能相应的响应该消息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章