#HTTP Servers #
作者: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
的高效。
HTTPServer使用:
-
定义对client socket的回调函数,初始化
HTTPServer
。 -
使用
HTTPServer.bind(port)
监听对应端口。 -
使用
HTTPServer.start()
开始运行服务器。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
####内部实现-主要函数####
在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不为空,则不能尝试先尝试发送,否则会造成时序错乱。
-
关于发送速率并没有进行考虑,如果发送数据的速率高于对方接受数据的速率,这会造成数据在本地内存中的堆积,对效率造成影响。在这里可以使用高水位回调和低水位回调进行控制。