博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
04: 打开tornado源码剖析处理过程
阅读量:4957 次
发布时间:2019-06-12

本文共 21000 字,大约阅读时间需要 70 分钟。

目录:Tornado其他篇

目录:

1.1 tornado处理的两个阶段:启动程序阶段 & 处理请求阶段

  1、第一阶段:启动程序阶段(也叫待处理请求阶段)

      1. 第一步,获取配置文件然后生成url映射(即:一个url对应一个XXRequestHandler,从而让XXRequestHandler来处理指定url发送的请求);

      2. 第二步,创建服务器socket对象并添加到epoll中;

      3. 第三步,创建无线循环去监听epoll,当请求到达时交由HttpServer类的_handle_events方法处理请求

  2、第二阶段:接收并处理请求阶段

      1. 第一步,接收客户端socket发送的请求(socket.accept);

      2. 第二步,从请求中获取请求头信息,再然后根据请求头中的请求url去匹配某个XXRequestHandler;

      3. 第三步,匹配成功的XXRequestHandler处理请求;

      4. 第四步,将处理后的请求发送给客户端;

      5. 第五步,关闭客户端socket。

  3、tornado使用最基本代码

import tornado.ioloopimport tornado.web#1、 处理访问/index/的get请求: http://127.0.0.1:8888/index/class MainHandler(tornado.web.RequestHandler):   def get(self):      self.write("I am index!!")#2、 配置settingssettings = {    'template_path': 'template',         # 配置html文件模板位置    'static_path': 'static',             # 配置静态文件路径(图片等)    'static_url_prefix': '/static/',     # 前端引入静态文件路径}#3、路由系统application = tornado.web.Application([   (r"/index/", MainHandler),],**settings)#4、启动这个tornado这个程序if __name__ == "__main__":   application.listen(8888)   print('http://127.0.0.1:8888/index/')   tornado.ioloop.IOLoop.instance().start()
tornado使用最基本代码

1.2 application = tornado.web.Application([(xxx,xxx)]) : 启动程序阶段(第一步)

   1、此句代码处理过程

      1. 执行Application类的构造函数,并传入一个列表类型的参数,这个列表里保存的是url规则和对应的处理类

      2. 即:当客户端的请求url可以配置这个规则时,那么该请求就交由对应的Handler去执行

      3. 注意:Handler泛指继承自RequestHandler的所有类,Handlers泛指继承自RequestHandler的所有类的集合

  2、处理此句代码对应源码

      1. Application.__init__:加载配置信息和生成url映射,并且把所有的信息封装在一个application对象中。

      2. Application.add_handlers: 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中

      3. URLSpec : 接收传入的url映射,生成URLSpec类

class Application(object):    def __init__(self, handlers=None, default_host="", transforms=None, wsgi=False, **settings):        # 设置响应的编码和返回方式,对应的http响应头:Content-Encoding和Transfer-Encoding        # Content-Encoding:gzip 表示对数据进行压缩,然后再返回给用户,从而减少流量的传输。        # Transfer-Encoding:chunck 表示数据的传送方式通过一块一块的传输。        if transforms is None:            self.transforms = []            if settings.get("gzip"):                self.transforms.append(GZipContentEncoding)            self.transforms.append(ChunkedTransferEncoding)        else:            self.transforms = transforms        # 将参数赋值为类的变量        self.handlers = []        self.named_handlers = {}        self.default_host = default_host        self.settings = settings        # ui_modules和ui_methods用于在模版语言中扩展自定义输出        # 这里将tornado内置的ui_modules和ui_methods添加到类的成员变量self.ui_modules和self.ui_methods中        self.ui_modules = {
'linkify': _linkify, 'xsrf_form_html': _xsrf_form_html, 'Template': TemplateModule, } self.ui_methods = {} self._wsgi = wsgi # 获取获取用户自定义的ui_modules和ui_methods,并将他们添加到之前创建的成员变量self.ui_modules和self.ui_methods中 self._load_ui_modules(settings.get("ui_modules", {})) self._load_ui_methods(settings.get("ui_methods", {})) # 设置静态文件路径,设置方式则是通过正则表达式匹配url,让StaticFileHandler来处理匹配的url if self.settings.get("static_path"): # 从settings中读取key为static_path的值,用于设置静态文件路径 path = self.settings["static_path"] # 获取参数中传入的handlers,如果空则设置为空列表 handlers = list(handlers or []) # 静态文件前缀,默认是/static/ static_url_prefix = settings.get("static_url_prefix", "/static/") # 在参数中传入的handlers前再添加三个映射: # 【/static/.*】 --> StaticFileHandler # 【/(favicon\.ico)】 --> StaticFileHandler # 【/(robots\.txt)】 --> StaticFileHandler handlers = [ (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler, dict(path=path)), (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)), (r"/(robots\.txt)", StaticFileHandler, dict(path=path)), ] + handlers # 执行本类的Application的add_handlers方法 # 此时,handlers是一个列表,其中的每个元素都是一个对应关系,即:url正则表达式和处理匹配该正则的url的Handler if handlers: self.add_handlers(".*$", handlers) # Automatically reload modified modules # 如果settings中设置了 debug 模式,那么就使用自动加载重启 if self.settings.get("debug") and not wsgi: import autoreload autoreload.start()
Application.__init__
class Application(object):    def add_handlers(self, host_pattern, host_handlers):        # 如果主机模型最后没有结尾符,那么就为他添加一个结尾符。        if not host_pattern.endswith("$"):            host_pattern += "$"        handlers = []        # 对主机名先做一层路由映射,例如:http://www.wupeiqi.com 和 http://safe.wupeiqi.com        # 即:safe对应一组url映射,www对应一组url映射,那么当请求到来时,先根据它做第一层匹配,之后再继续进入内部匹配。        # 对于第一层url映射来说,由于.*会匹配所有的url,所将 .* 的永远放在handlers列表的最后,不然 .* 就会截和了...        # re.complie是编译正则表达式,以后请求来的时候只需要执行编译结果的match方法就可以去匹配了        if self.handlers and self.handlers[-1][0].pattern == '.*$':            self.handlers.insert(-1, (re.compile(host_pattern), handlers))        else:            self.handlers.append((re.compile(host_pattern), handlers))        # 遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译)        # 并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。        for spec in host_handlers:            if type(spec) is type(()):                assert len(spec) in (2, 3)                pattern = spec[0]                handler = spec[1]                if len(spec) == 3:                    kwargs = spec[2]                else:                    kwargs = {}                spec = URLSpec(pattern, handler, kwargs)            handlers.append(spec)            if spec.name:                # 未使用该功能,默认spec.name = None                if spec.name in self.named_handlers:                    logging.warning("Multiple handlers named %s; replacing previous value", spec.name)                self.named_handlers[spec.name] = spec
Application.add_handlers
class URLSpec(object):    def __init__(self, pattern, handler_class, kwargs={}, name=None):        if not pattern.endswith('$'):            pattern += '$'        self.regex = re.compile(pattern)        self.handler_class = handler_class        self.kwargs = kwargs        self.name = name        self._path, self._group_count = self._find_groups()
URLSpec

   3、加载的配置信息包括:

      1. 编码和返回方式信息

      2. 静态文件路径
      3. ui_modules(模版语言中使用,暂时忽略)
      4. ui_methods(模版语言中使用,暂时忽略)
      5. 是否debug模式运行

      注: 以上的所有配置信息,都可以在settings中配置,然后在创建Application对象时候,传入参数即可。

            如:application = tornado.web.Application([(r"/index", MainHandler),],**settings)

  4、生成url映射:

      将url和对应的Handler添加到对应的主机前缀中,如:safe.index.com、www.auto.com

                        

   5、 封装数据:

      将配置信息和url映射关系封装到Application对象中,信息分别保存在Application对象的以下字段中:

      1. self.transforms,     保存着编码和返回方式信息
      2. self.settings,          保存着配置信息
      3. self.ui_modules,    保存着ui_modules信息
      4. self.ui_methods,    保存这ui_methods信息
      5. self.handlers,         保存着所有的主机名对应的Handlers,每个handlers则是url正则对应的Handler

1.3 application.listen(xxx) : 启动程序阶段(第二步)

   1、此句代码处理过程

      1. 第一步操作将配置和url映射等信息封装到了application对象中, 而这第二步执行application对象的listen方法,

      2. 该方法内部又把之前包含各种信息的application对象封装到了一个HttpServer对象中,然后继续调用HttpServer对象的liseten方法。

  2、处理此句代码对应源码(简要代码)

                

  3、处理此句代码对应源码(详细代码)

class HTTPServer(object):    def __init__(self, request_callback, no_keep_alive=False, io_loop=None,xheaders=False, ssl_options=None):        #Application对象        self.request_callback = request_callback        #是否长连接        self.no_keep_alive = no_keep_alive        #IO循环        self.io_loop = io_loop        self.xheaders = xheaders        #Http和Http        self.ssl_options = ssl_options        self._socket = None        self._started = False    def listen(self, port, address=""):        self.bind(port, address)        self.start(1)    def bind(self, port, address=None, family=socket.AF_UNSPEC):        assert not self._socket        #创建服务端socket对象,IPV4和TCP连接        self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)        flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)        flags |= fcntl.FD_CLOEXEC        fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)        #配置socket对象        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)        self._socket.setblocking(0)        #绑定IP和端口        self._socket.bind((address, port))        #最大阻塞数量        self._socket.listen(128)    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        #如果进程数大于1        if num_processes > 1:            logging.info("Pre-forking %d server processes", num_processes)            for i in range(num_processes):                if os.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)                    self.io_loop = ioloop.IOLoop.instance()                    self.io_loop.add_handler(                        self._socket.fileno(), self._handle_events,                        ioloop.IOLoop.READ)                    return            os.waitpid(-1, 0)        #进程数等于1,默认        else:            if not self.io_loop:                #设置成员变量self.io_loop为IOLoop的实例,注:IOLoop使用methodclass完成了一个单例模式                self.io_loop = ioloop.IOLoop.instance()            #执行IOLoop的add_handler方法,将socket句柄、self._handle_events方法和IOLoop.READ当参数传入            self.io_loop.add_handler(self._socket.fileno(),                                     self._handle_events,                                     ioloop.IOLoop.READ)    def _handle_events(self, fd, events):        while True:            try:                #====important=====#                connection, address = self._socket.accept()            except socket.error, e:                if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):                    return                raise            if self.ssl_options is not None:                assert ssl, "Python 2.6+ and OpenSSL required for SSL"                try:                    #====important=====#                    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:                    stream = iostream.IOStream(connection, io_loop=self.io_loop)                #====important=====#                HTTPConnection(stream, address, self.request_callback,self.no_keep_alive, self.xheaders)            except:                logging.error("Error in connection callback", exc_info=True)
HTTPServer
class IOLoop(object):    # Constants from the epoll module    _EPOLLIN = 0x001    _EPOLLPRI = 0x002    _EPOLLOUT = 0x004    _EPOLLERR = 0x008    _EPOLLHUP = 0x010    _EPOLLRDHUP = 0x2000    _EPOLLONESHOT = (1 << 30)    _EPOLLET = (1 << 31)    # Our events map exactly to the epoll events    NONE = 0    READ = _EPOLLIN    WRITE = _EPOLLOUT    ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP    def __init__(self, impl=None):        self._impl = impl or _poll()        if hasattr(self._impl, 'fileno'):            self._set_close_exec(self._impl.fileno())        self._handlers = {}        self._events = {}        self._callbacks = []        self._timeouts = []        self._running = False        self._stopped = False        self._blocking_signal_threshold = None        # Create a pipe that we send bogus data to when we want to wake        # the I/O loop when it is idle        if os.name != 'nt':            r, w = os.pipe()            self._set_nonblocking(r)            self._set_nonblocking(w)            self._set_close_exec(r)            self._set_close_exec(w)            self._waker_reader = os.fdopen(r, "rb", 0)            self._waker_writer = os.fdopen(w, "wb", 0)        else:            self._waker_reader = self._waker_writer = win32_support.Pipe()            r = self._waker_writer.reader_fd        self.add_handler(r, self._read_waker, self.READ)    @classmethod    def instance(cls):        if not hasattr(cls, "_instance"):            cls._instance = cls()        return cls._instance            def add_handler(self, fd, handler, events):        """Registers the given handler to receive the given events for fd."""        self._handlers[fd] = stack_context.wrap(handler)        self._impl.register(fd, events | self.ERROR)
IOLoop
def wrap(fn):    '''Returns a callable object that will resore the current StackContext    when executed.    Use this whenever saving a callback to be executed later in a    different execution context (either in a different thread or    asynchronously in the same thread).    '''    if fn is None:      return None    # functools.wraps doesn't appear to work on functools.partial objects    #@functools.wraps(fn)    def wrapped(callback, contexts, *args, **kwargs):        # If we're moving down the stack, _state.contexts is a prefix        # of contexts.  For each element of contexts not in that prefix,        # create a new StackContext object.        # If we're moving up the stack (or to an entirely different stack),        # _state.contexts will have elements not in contexts.  Use        # NullContext to clear the state and then recreate from contexts.        if (len(_state.contexts) > len(contexts) or            any(a[1] is not b[1]                for a, b in itertools.izip(_state.contexts, contexts))):            # contexts have been removed or changed, so start over            new_contexts = ([NullContext()] +                            [cls(arg) for (cls,arg) in contexts])        else:            new_contexts = [cls(arg)                            for (cls, arg) in contexts[len(_state.contexts):]]        if len(new_contexts) > 1:            with contextlib.nested(*new_contexts):                callback(*args, **kwargs)        elif new_contexts:            with new_contexts[0]:                callback(*args, **kwargs)        else:            callback(*args, **kwargs)    if getattr(fn, 'stack_context_wrapped', False):        return fn    contexts = _state.contexts    result = functools.partial(wrapped, fn, contexts)    result.stack_context_wrapped = True    return result
stack_context.wrap

    1. HTTPServer实质做了以下四件事

        1. 调用自己的构造方法,把包含了各种配置信息的application对象封装到了HttpServer对象的request_callback字段

        2. 调用socketsocket() , 创建服务端socket对象

        3. 单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中

        4. 向epoll中注册监听服务端socket对象的读可用事件

    2. IOLoop作用

        1. 在HttpServer中生成IOLoop对象,并调用 IOLoop的add_handler方法

        2. add_handler 方法内部,将_handle_events函数封装到stack_context.wrap中,然后保存到IOLoop对象的self._handlers[fd]字段中

        3. 然后调用epoll对象,向epoll中注册监听socket事件

    3. stack_context.wrap作用

        1. stack_context.wrap其实就是对函数进行一下封装,即:函数在不同情况下上下文信息可能不同。

  4、使用epoll创建服务端socket(补充)

import socket, select EOL1 = b'/n/n' EOL2 = b'/n/r/n' response  = b'HTTP/1.0 200 OK/r/nDate: Mon, 1 Jan 1996 01:01:01 GMT/r/n' response += b'Content-Type: text/plain/r/nContent-Length: 13/r/n/r/n' response += b'Hello, world!' serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) serversocket.bind(('0.0.0.0', 8080)) serversocket.listen(1) serversocket.setblocking(0) epoll = select.epoll() epoll.register(serversocket.fileno(), select.EPOLLIN) try:    connections = {}; requests = {}; responses = {}    while True:       events = epoll.poll(1)       for fileno, event in events:          if fileno == serversocket.fileno():             connection, address = serversocket.accept()             connection.setblocking(0)             epoll.register(connection.fileno(), select.EPOLLIN)             connections[connection.fileno()] = connection             requests[connection.fileno()] = b''             responses[connection.fileno()] = response          elif event & select.EPOLLIN:             requests[fileno] += connections[fileno].recv(1024)             if EOL1 in requests[fileno] or EOL2 in requests[fileno]:                epoll.modify(fileno, select.EPOLLOUT)                print('-'*40 + '/n' + requests[fileno].decode()[:-2])          elif event & select.EPOLLOUT:             byteswritten = connections[fileno].send(responses[fileno])             responses[fileno] = responses[fileno][byteswritten:]             if len(responses[fileno]) == 0:                epoll.modify(fileno, 0)                connections[fileno].shutdown(socket.SHUT_RDWR)          elif event & select.EPOLLHUP:             epoll.unregister(fileno)             connections[fileno].close()             del connections[fileno] finally:    epoll.unregister(serversocket.fileno())    epoll.close()    serversocket.close()
使用epoll创建服务端socket

    1.  上述,其实就是利用epoll对象的poll(timeout)方法去轮询已经注册在epoll中的socket句柄,

    2.  当有读可用的信息时候,则返回包含当前句柄和Event Code的序列,然后在通过句柄对客户端的请求进行处理

1.4 tornado.ioloop.IOLoop.instance().start() : 启动程序阶段(第三步)

   1、此句代码处理过程

      1. 执行IOLoop实例的start()方法后程序就进入“死循环”,也就是会一直不停的轮询的去检查是否有请求到来

      2. 如果有请求到达,则执行封装了HttpServer类的_handle_events方法和相关上下文的stack_context.wrap(handler)

           (其实就是执行HttpServer类的_handle_events方法)

  2、处理此句代码对应源码(简要代码)

class IOLoop(object):    def add_handler(self, fd, handler, events):        # HttpServer的Start方法中会调用该方法        self._handlers[fd] = stack_context.wrap(handler)        self._impl.register(fd, events | self.ERROR)    def start(self):        while True:            poll_timeout = 0.2            try:                # epoll中轮询                event_pairs = self._impl.poll(poll_timeout)            except Exception, e:            # 省略其他            # 如果有读可用信息,则把该socket对象句柄和Event Code序列添加到self._events中            self._events.update(event_pairs)            # 遍历self._events,处理每个请求            while self._events:                fd, events = self._events.popitem()                try:                    # 以socket为句柄为key,取出self._handlers中的stack_context.wrap(handler),并执行                    # stack_context.wrap(handler)包装了HTTPServer类的_handle_events函数的一个函数                    # 是在上一步中执行add_handler方法时候,添加到self._handlers中的数据。                    self._handlers[fd](fd, events)                except:                # 省略其他
IOLoop.start
class HTTPServer(object):    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            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:                    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)
HTTPServer._handle_events

 

转载于:https://www.cnblogs.com/xiaonq/p/8042017.html

你可能感兴趣的文章
UVA 11044
查看>>
mysq找不到pid无法正常启动
查看>>
php实现抓取网站百度快照和百度收录数量的代码实例
查看>>
Qt那点事儿(三) 论父对象与子对象的关系
查看>>
jar 命令 打包装class文件的文件夹
查看>>
node.js express配置允许跨域
查看>>
set调用add报错:
查看>>
四轴飞行器1.2.1 RT-Thread 环境搭建
查看>>
choose&&char与char*区别
查看>>
51nod 1021 石子归并
查看>>
实验二:ICMP重定向攻击
查看>>
C/C++判断文件是否存在
查看>>
基础总结1
查看>>
随机算法
查看>>
洛谷 P1443 马的遍历
查看>>
AutoMapper中的Map和DynamicMap——高手注重细节,思考和总结
查看>>
html5离线应用application cache
查看>>
sqlserver 获取实例上用户数据库的数据字典
查看>>
【伯乐在线】每个程序员都该知道的编码准则
查看>>
Priority_queue详解
查看>>