#HTTP Servers #ios
做者:MetalBug 时间:2015-03-01 出处:http://my.oschina.net/u/247728/blog 声明:版权全部,侵犯必究
tornado.httpserver
`— Non-blocking HTTP servertornado.httputil
— Manipulate HTTP headers and URLs##1.httpserver##服务器
###1.1HTTPServer### HTTPServer
是一个非阻塞的HTTP服务器。 在内部使用IOLoop
对socket事件进行读写,由于IOLoop
基于epoll
,因此保证了Tornado
的高效。cookie
HTTPServer使用:数据结构
定义对client socket的回调函数,初始化HTTPServer
。app
使用HTTPServer.bind(port)
监听对应端口。异步
使用HTTPServer.start()
开始运行服务器。socket
http_server = httpserver.HTTPServer(handle_request) http_server.bind(8888) http_server.start() ioloop.IOLoop.instance().start()
如下是HTTPServer的大致处理过程: 函数
####内部实现-数据结构#### self.request_callback
为对client socket的回调函数 self.socket
为listen scoket self.io_loop
为绑定的IOLoop
tornado
####内部实现-主要函数####工具
在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
进行包装,而后传递给了HTTPConnection
,HTTPConnection
对该链接进行处理。
###1.2HTTPConnection### HTTPConnection
用于处理HTTP链接的client,它会解析HTTP head和body,并在得到请求时,生成一个HTTPRequest
,执行咱们的_request_callback
,直到链接关闭。若是HTTP链接为keep-alive,则继续以上流程。
如下是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
包含了httpclient
和httpserver
共享的工具类。
###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函数对拼接而成的字符串进行处理便可。
#总结# 基于IOLoop
,Tornado1.0
实现了HTTPServer
,一个非阻塞的HTTP服务器,同时利用IOStream
实现了异步读写,对于读取数据而后针对HTTP的解析,这里在读取的过程当中逐步解析了。 而对于发送数据,这里有两个能够改进的,
HTTPConnection
的write
函数直接调用IOStream完成,而IOStream
中的_handle_write
的实现,是反复调用write函数发送数据。可是在实际中,若是第一次没有可以发送彻底部数据时,第二次调用write函数大部分会返回EAGAIN
。因此在这里的IOstream._handle_write
实现能够优化。
同时能够选择在
HTTPConnection
尝试往client socket中写一次数据,若是可以完成所有数据的发送,而并不使用IOStream
进行发送,若是没有写完再使用IOStream进行发送数据。固然,若是此时IOStream
的write_buffer不为空,则不能尝试先尝试发送,不然会形成时序错乱。
关于发送速率并无进行考虑,若是发送数据的速率高于对方接受数据的速率,这会形成数据在本地内存中的堆积,对效率形成影响。在这里可使用高水位回调和低水位回调进行控制。