Tornado1.0源码分析-HTTP Servers

#HTTP Servers #ios

做者: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的高效。cookie

HTTPServer使用:数据结构

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

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

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

    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为绑定的IOLooptornado

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

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. 关于发送速率并无进行考虑,若是发送数据的速率高于对方接受数据的速率,这会形成数据在本地内存中的堆积,对效率形成影响。在这里可使用高水位回调和低水位回调进行控制。

相关文章
相关标签/搜索