低调大师

您现在的位置是: 首页 > Tornado1.0源码分析-HTTP Servers

文章详情

Tornado1.0源码分析-HTTP Servers

2015-3-1 16:44 11已围观 收藏 加入我们

#HTTP Servers #

作者:MetalBug
时间:2015-03-01
出处:http://my.oschina.net/u/247728/blog
声明:版权所有,侵犯必究
  • tornado.httpserver `— Non-blocking HTTP server
  • tornado.httputil — Manipulate HTTP headers and URLs

##1.httpserver##

###1.1HTTPServer### HTTPServer是一个非阻塞的HTTP服务器。 在内部使用IOLoop对socket事件进行读写,因为IOLoop基于epoll,所以保证了Tornado的高效。

HTTPServer使用:

  1. 定义对client socket的回调函数,初始化HTTPServer

  2. 使用HTTPServer.bind(port)监听对应端口。

  3. 使用HTTPServer.start()开始运行服务器。

    http_server = httpserver.HTTPServer(handle_request)
    http_server.bind(8888)
    http_server.start()
    ioloop.IOLoop.instance().start()
    

以下是HTTPServer的大体处理过程: Tornado-HTTPServer-流程图

####内部实现-数据结构#### self.request_callback为对client socket的回调函数 self.socket为listen scoket self.io_loop为绑定的IOLoop

####内部实现-主要函数####

HTTPServer.start()中,会根据CPU的核数创建对应的进程,在每个进程中有自己的IOLoop,因为是进程,所以并没有数据竞争的问题。

for i in range(num_processes):
    if os.fork() == 0:
        self.io_loop = ioloop.IOLoop.instance()
        self.io_loop.add_handler(self._socket.fileno(), 
                    self._handle_events,ioloop.IOLoop.READ)
        return

可以看到,IOLoop监视了HTTPServer的listen socket的READ事件,使用_handle_events回调函数。在这里,监视的是socket的accept(),对每个连接上来的client socket进行处理。

def _handle_events(self, fd, events):
    while True:
        try:
            connection, address = self._socket.accept()
        except socket.error, e:
            if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
                return
            raise
        #####
        try:
            stream = iostream.IOStream(connection, io_loop=self.io_loop)
            HTTPConnection(stream, address, self.request_callback,
                           self.no_keep_alive, self.xheaders)
        except:
            logging.error("Error in connection callback", exc_info=True)

从代码可以得到,对于新连接的client socket,HTTPServer使用IOStream进行包装,然后传递给了HTTPConnectionHTTPConnection对该连接进行处理。

###1.2HTTPConnection### HTTPConnection用于处理HTTP连接的client,它会解析HTTP head和body,并在获得请求时,生成一个HTTPRequest,执行我们的_request_callback,直到连接关闭。如果HTTP连接为keep-alive,则继续以上流程。

以下是HTTPConnection的大体执行流程: Tornado-HTTPConnection-流程图

####内部实现-数据结构#### self.request_callback为对client socket的回调函数 self.stream为包装client的IOStream

####内部实现-主要函数#### 对于_on_headers,_on_request_body_parse_mime_body,都是根据HTTP协议进行解析,这里针对的是接受到的数据,最终将一个request中的数据用一个HTTPRequest表示。 而对于发送数据,因为发送数据是主动的,而接受数据是被动的,所以发送数据相对更难。

对于HTTPConnection, 其发送数据内部调用的是IOStream.write函数

def write(self, chunk):
    assert self._request, "Request closed"
    if not self.stream.closed():
        self.stream.write(chunk, self._on_write_complete)

可以看到,这里HTTPConnection直接将发送数据放到IOStream的write_buffer,并开始关注write事件,在IOStream_handle_write中将数据发送完成。完成发送数据后,会调用_on_write_complete用于处理request的关闭。

def _on_write_complete(self):
    if self._request_finished:
        self._finish_request()

对于self._request_finished初始化为False,在HTTPConnection.finish()中被置为True,用于标识request的结束。

当一次request结束之后,会根据请求的类型,是否为keep-alive从而决定是否关闭连接还是继续下一个request的解析和处理。

def _finish_request(self):
    if self.no_keep_alive:
        disconnect = True
    else:
        connection_header = self._request.headers.get("Connection")
        if self._request.supports_http_1_1():
            disconnect = connection_header == "close"
        elif ("Content-Length" in self._request.headers
                or self._request.method in ("HEAD", "GET")):
            disconnect = connection_header != "Keep-Alive"
        else:
            disconnect = True
    self._request = None
    self._request_finished = False
    if disconnect:
        self.stream.close()
        return
    self.stream.read_until("\r\n\r\n", self._on_headers)

####内部实现-实现细节#### IOStream中的_handle_write的实现,是反复调用write函数发送数据。但是在实际中,如果第一次没有能够发送完全部数据时,第二次调用write函数大部分会返回EAGAIN。所以在这里的IOstream._handle_write实现可以优化。

###1.3HTTPRequest### HTTPRequest是对一次HTTP请求的包装,更具请求接受到的数据进行解析,提供write和finish的接口。 HTTPRequest只是一个简单的用于暴露给用户使用的类,其内部的函数都是HTTPConnection函数的代理。

####内部实现-数据结构####

self.connection即为该请求对应的连接,类型为HTTPConnection

##2.httputil## httputils包含了httpclienthttpserver共享的工具类。

###2.1.1HTTPHeader### HTTPHeaders继承了dict,用于表示HTTP头部中各个key及其对应内容。

>> h.add("Set-Cookie", "A=B")
>> h.add("Set-Cookie", "C=D")
>> h["set-cookie"]
'A=B,C=D'
>> h.get_list("set-cookie")
['A=B', 'C=D']

####内部实现-数据结构 #### HTTPHeaders内部使用拼接字符串的方式实现了multiple values per key.

def __init__(self, *args, **kwargs):
    dict.__init__(self)
    self._as_list = {}
    self.update(*args, **kwargs)

_as_list是dict,一个key对应一个list

def add(self, name, value):
   norm_name = HTTPHeaders._normalize_name(name)
    if norm_name in self:
        dict.__setitem__(self, norm_name, self[norm_name] + ',' + value)
        self._as_list[norm_name].append(value) 
    else:
        self[norm_name] = value

HTTPHeaders在内部是维护了一个key对应一个list的结构,从而使用get_list能够返回一个list,这样造成了数据冗余,当然仅仅对于处理HTTP的头部这种小数据量而言,差别并不大。 如果要避免冗余的话,直接使用split函数对拼接而成的字符串进行处理即可。

#总结# 基于IOLoopTornado1.0实现了HTTPServer,一个非阻塞的HTTP服务器,同时利用IOStream实现了异步读写,对于读取数据然后针对HTTP的解析,这里在读取的过程中逐步解析了。 而对于发送数据,这里有两个可以改进的,

  1. HTTPConnectionwrite函数直接调用IOStream完成,而 IOStream中的_handle_write的实现,是反复调用write函数发送数据。但是在实际中,如果第一次没有能够发送完全部数据时,第二次调用write函数大部分会返回EAGAIN。所以在这里的IOstream._handle_write实现可以优化。

同时可以选择在HTTPConnection尝试往client socket中写一次数据,如果能够完成全部数据的发送,而并不使用IOStream进行发送,如果没有写完再使用IOStream进行发送数据。当然,如果此时IOStream的write_buffer不为空,则不能尝试先尝试发送,否则会造成时序错乱。

  1. 关于发送速率并没有进行考虑,如果发送数据的速率高于对方接受数据的速率,这会造成数据在本地内存中的堆积,对效率造成影响。在这里可以使用高水位回调和低水位回调进行控制。

文章转载至:https://my.oschina.net/u/247728/blog/381031
收藏 (0)

文章评论

共有0条评论来说两句吧...