Tornado源码分析之HTTPServer
上篇介绍了IOStream异步读写原理”Tornado源码分析之IOStream“,本文将介绍基于IOStream上层的HTTPServer类
HTTPServer类源码: https://github.com/tornadoweb/tornado/blob/master/tornado/httpserver.py
前两篇分析完了IOLoop和IOStream后就知道,第一次在监听套接口的时候需要用到IOLoop,然后创建一个IOStream对象,然后以后的IO操作都由IOStream对象完成,所以在上层的HTTP协议处理中,tornado定义了一个HTTPConnection类,这个类主要完成的工作就是完成对下层HTTP数据包的传输,对上层HTTPserver提供解析后的request对象。
那么他们之间的工作模式是怎样的呢
tornado/httpserver.py 源码 Demo
import httpserver import ioloop def handle_request(request): message = "You requested %s\n" % request.uri request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % (len(message), message)) request.finish() http_server = httpserver.HTTPServer(handle_request) http_server.listen(8888) ioloop.IOLoop.instance().start()
这次没有在Demo里面加注释,因为这样更清晰
上面的代码是现在HTTP框架很传统的方式
定义一个处理request请求的回调函数,并完成自己HTTP数据包的构造,并发送出去,过程很简单,
首先是创建一个HTTPServer,然后加入自己的回调函数,当然这个回调函数可以不这么简单,这里只是一个Demo,你甚至可以在 request对象中提取表单数据等
启动监听端口8888并启动IOLoop
然后在取得了一个HTTP请求之后,就会回调handle_request,其中在回调函数中的 request 就是由 HTTPConnection处理后的完整的HTTP层需要的数据,包括一些表单数据等
好了,现在很清晰的知道了HTTP层总共有几个主要的类
1)HTTPServer
2)HTTPConnection
3)HTTPRequest
这三个类的功能总体上是这样
总结:这个是很传统的处理方式,但是其中涉及的细节很多,怎样放置回调函数等。
在此HTTPServer之上,可以通过解析Request并加上一些其他更好的功能,组成一个真正的web框架。
上面讲了关于HTTPServer的原理,下面将通过分析源码来了解更多的细节
HTTPServer类的结构
HTTPServer的主要工作
1)提供了一些基础的共有操作,例如 listen,bind等。
2)完成了一个 _handle_events()的公有回调函数,此函数是 IOLoop的基础,此函数为每一个连接创建一个单独的 IOStream 对象
3)start函数,启动HTTPServer,并设置相应的参数(默认根据CPU个数来设置进程数等)
从HTTPServer类的构造函数可以看出,最重要的参数是设置回调函数,此回调函数用于处理request对象
每次有HTTP的请求,都会通过HTTPConnection 封装一个HTTPRequest对象,这个对象包含了HTTP请求的所有信息
所以在HTTPServer层,需要对这个对象进行一番处理后调用 request.write将结果返回给客户端
此回调函数会先注册到HTTPServer,然后注册到HTTPConnection 里面,因为request这个对象是由HTTPConnection对象产生
def _handle_events(self, fd, events): while True: try: connection, address = self._socket.accept() except socket.error, e: if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise #SSL 选项 if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl.wrap_socket(connection, server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = iostream.SSLIOStream(connection, io_loop=self.io_loop) else: #为每一个 connection 创建一个 iostream 实例,以后的IO操作由此实例负责 #IOLoop只负责 accept这个连接 stream = iostream.IOStream(connection, io_loop=self.io_loop) #将 stream对象和对应的 address , callback加入到HTTPConnection 中 #HTTPConnection稍后会有介绍 #这里的 request_callback 是由Demo里 httpserver.HTTPServer(handle_request) 传递进来 #现代的 HTTP 框架都采用这种模式 #创建一个 handle_request 这个 回调函数嵌套的注册到下层,直到真正处理request #一般情况是回调继续传递下去直到遇到一个类方法能够传递 request 对象给这个函数 HTTPConnection(stream, address, self.request_callback, self.no_keep_alive, self.xheaders) except: logging.error("Error in connection callback", exc_info=True)
通过调用HTTPConnection,然后传递stream,address,request_callback到HTTPConnection可以看到,处理request的回调函数注册到了HTTPConnection.
还需要注意的地方就是,每一次有一个连接的到来,IOLoop都只负责处理accept此连接,然后后面的IO操作就交给IOStream来处理
在start()函数中,会为每个进程创建一个单独的IOLoop,然后此IOLoop的回调函数统一采用_handle_events()
_handle_events()函数的处理流程总体来说是这样:
1)注册到本进程的IOLoop中
2)当有事件发生,只注册了READ事件,也就是只接受新连接,每次有连接到来,都回调_handle_events()
3)accept此新连接,然后为此新连接创建一个IOStream对象,以后此IOStream负责本连接的所有IO操作,这里是一层抽象,实际在IOStream的读写事件也是注册到了本进程的IOLoop中,只不过回调函数不一样,因为注册时候的描述符不同。
调用方式是通过handle[fd]()这种方式调用,所以对于监听套接口每次都只会调用_handle_events(),而对于其他的IOStream的连接fd会调用在read_bytes(),read_utils()中注册的回调函数
HTTPServer中的 start()函数
def start(self, num_processes=1): assert not self._started self._started = True if num_processes is None or num_processes <= 0: num_processes = _cpu_count() if num_processes > 1 and ioloop.IOLoop.initialized(): logging.error("Cannot run in multiple processes: IOLoop instance " "has already been initialized. You cannot call " "IOLoop.instance() before calling start()") num_processes = 1 if num_processes > 1: logging.info("Pre-forking %d server processes", num_processes) #根据 处理器个数来决定 fork多少个进程 for i in range(num_processes): if os.fork() == 0:# fork() == 0 表示子进程 import random from binascii import hexlify try: # If available, use the same method as # random.py seed = long(hexlify(os.urandom(16)), 16) except NotImplementedError: # Include the pid to avoid initializing two # processes to the same value seed(int(time.time() * 1000) ^ os.getpid()) random.seed(seed) #为每个进程创建一个IOLoop实例 self.io_loop = ioloop.IOLoop.instance() #为每个IOLoop 添加回调函数,这里采用统一回调方式,和IOStream 一样 self.io_loop.add_handler(self._socket.fileno(), self._handle_events, ioloop.IOLoop.READ) return os.waitpid(-1, 0)#预防僵尸进程,Unix 环境编程介绍很多 else: if not self.io_loop: self.io_loop = ioloop.IOLoop.instance() self.io_loop.add_handler(self._socket.fileno(), self._handle_events, ioloop.IOLoop.READ)
可以在代码注释中看到会根据每一个CPU一个IOLoop实例的方式处理,至于中间的产生随机数是为什么,如果有人知道请告知我
在start()函数的最后可以看到add_handle将监听套接口和_handle_events()函数注册到了IOLoop中,这就是上面所讲的HTTPServer处理连接的过程
原文: 破修电脑的
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2014-12-06 17:52:59
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!