上篇《白话tornado源码之一个脚本引发的血案》用上帝视角多整个框架做了一个概述,同时也看清了web框架的的本质,下面我们从tornado程序的起始来分析其源码。

概述

上图是tornado程序启动以及接收到客户端请求后的整个过程,对于整个过程可以分为两大部分:

  • 启动程序阶段,又称为待请求阶段(上图1、2所有系列和3.0)
  • 接收并处理客户端请求阶段(上图3系列)

简而言之:

1、在启动程序阶段,第一步,获取配置文件然后生成url映射(即:一个url对应一个XXRequestHandler,从而让XXRequestHandler来处理指定url发送的请求);第二步,创建服务器socket对象并添加到epoll中;第三步,创建无线循环去监听epoll。

2、在接收并处理请求阶段,第一步,接收客户端socket发送的请求(socket.accept);第二步,从请求中获取请求头信息,再然后根据请求头中的请求url去匹配某个XXRequestHandler;第三步,匹配成功的XXRequestHandler处理请求;第四步,将处理后的请求发送给客户端;第五步,关闭客户端socket。

本篇的内容主要剖析【启动程序阶段】,下面我们就来一步一步的剖析整个过程,在此阶段主要是有下面重点标注的三个方法来实现。

  1. import tornado.ioloop
  2. import tornado.web
  3.  
  4. class MainHandler(tornado.web.RequestHandler):
  5. def get(self):
  6. self.write("Hello, world")
  7.  
  8. application = tornado.web.Application([
  9. (r"/index", MainHandler),
  10. ])
  11. if __name__ == "__main__":
  12. application.listen()
  13. tornado.ioloop.IOLoop.instance().start()

一、application = tornado.web.Application([(xxx,xxx)])

  执行Application类的构造函数,并传入一个列表类型的参数,这个列表里保存的是url规则和对应的处理类,即:当客户端的请求url可以配置这个规则时,那么该请求就交由对应的Handler去执行。

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

  1. class Application(object):
  2. def __init__(self, handlers=None, default_host="", transforms=None,wsgi=False, **settings):
  3. #设置响应的编码和返回方式,对应的http相应头:Content-Encoding和Transfer-Encoding
  4. #Content-Encoding:gzip 表示对数据进行压缩,然后再返回给用户,从而减少流量的传输。
  5. #Transfer-Encoding:chunck 表示数据的传送方式通过一块一块的传输。
  6. if transforms is None:
  7. self.transforms = []
  8. if settings.get("gzip"):
  9. self.transforms.append(GZipContentEncoding)
  10. self.transforms.append(ChunkedTransferEncoding)
  11. else:
  12. self.transforms = transforms
  13. #将参数赋值为类的变量
  14. self.handlers = []
  15. self.named_handlers = {}
  16. self.default_host = default_host
  17. self.settings = settings
  18. #ui_modules和ui_methods用于在模版语言中扩展自定义输出
  19. #这里将tornado内置的ui_modules和ui_methods添加到类的成员变量self.ui_modules和self.ui_methods中
  20. self.ui_modules = {'linkify': _linkify,
  21. 'xsrf_form_html': _xsrf_form_html,
  22. 'Template': TemplateModule,
  23. }
  24. self.ui_methods = {}
  25. self._wsgi = wsgi
  26. #获取获取用户自定义的ui_modules和ui_methods,并将他们添加到之前创建的成员变量self.ui_modules和self.ui_methods中
  27. self._load_ui_modules(settings.get("ui_modules", {}))
  28. self._load_ui_methods(settings.get("ui_methods", {}))
  29.  
  30. #设置静态文件路径,设置方式则是通过正则表达式匹配url,让StaticFileHandler来处理匹配的url
  31. if self.settings.get("static_path"):
  32. #从settings中读取key为static_path的值,用于设置静态文件路径
  33. path = self.settings["static_path"]
  34. #获取参数中传入的handlers,如果空则设置为空列表
  35. handlers = list(handlers or [])
  36. #静态文件前缀,默认是/static/
  37. static_url_prefix = settings.get("static_url_prefix","/static/")
  38. #在参数中传入的handlers前再添加三个映射:
  39. #【/static/.*】 --> StaticFileHandler
  40. #【/(favicon\.ico)】 --> StaticFileHandler
  41. #【/(robots\.txt)】 --> StaticFileHandler
  42. handlers = [
  43. (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,dict(path=path)),
  44. (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
  45. (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
  46. ] + handlers
  47. #执行本类的Application的add_handlers方法
  48. #此时,handlers是一个列表,其中的每个元素都是一个对应关系,即:url正则表达式和处理匹配该正则的url的Handler
  49. if handlers: self.add_handlers(".*$", handlers)
  50.  
  51. # Automatically reload modified modules
  52. #如果settings中设置了 debug 模式,那么就使用自动加载重启
  53. if self.settings.get("debug") and not wsgi:
  54. import autoreload
  55. autoreload.start()

Application.__init__

  1. class Application(object):
  2. def add_handlers(self, host_pattern, host_handlers):
  3. #如果主机模型最后没有结尾符,那么就为他添加一个结尾符。
  4. if not host_pattern.endswith("$"):
  5. host_pattern += "$"
  6. handlers = []
  7. #对主机名先做一层路由映射,例如:http://www.wupeiqi.com 和 http://safe.wupeiqi.com
  8. #即:safe对应一组url映射,www对应一组url映射,那么当请求到来时,先根据它做第一层匹配,之后再继续进入内部匹配。
  9.  
  10. #对于第一层url映射来说,由于.*会匹配所有的url,所将 .* 的永远放在handlers列表的最后,不然 .* 就会截和了...
  11. #re.complie是编译正则表达式,以后请求来的时候只需要执行编译结果的match方法就可以去匹配了
  12. if self.handlers and self.handlers[-1][0].pattern == '.*$':
  13. self.handlers.insert(-1, (re.compile(host_pattern), handlers))
  14. else:
  15. self.handlers.append((re.compile(host_pattern), handlers))
  16.  
  17. #遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译)
  18. #并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。
  19. for spec in host_handlers:
  20. if type(spec) is type(()):
  21. assert len(spec) in (2, 3)
  22. pattern = spec[0]
  23. handler = spec[1]
  24. if len(spec) == 3:
  25. kwargs = spec[2]
  26. else:
  27. kwargs = {}
  28. spec = URLSpec(pattern, handler, kwargs)
  29. handlers.append(spec)
  30.  
  31. if spec.name:
  32. #未使用该功能,默认spec.name = None
  33. if spec.name in self.named_handlers:
  34. logging.warning("Multiple handlers named %s; replacing previous value",spec.name)
  35. self.named_handlers[spec.name] = spec

Application.add_handlers

  1. class URLSpec(object):
  2. def __init__(self, pattern, handler_class, kwargs={}, name=None):
  3. if not pattern.endswith('$'):
  4. pattern += '$'
  5. self.regex = re.compile(pattern)
  6. self.handler_class = handler_class
  7. self.kwargs = kwargs
  8. self.name = name
  9. self._path, self._group_count = self._find_groups()

URLSpec

上述代码主要完成了以下功能:加载配置信息和生成url映射,并且把所有的信息封装在一个application对象中。

加载的配置信息包括:

  • 编码和返回方式信息
  • 静态文件路径
  • ui_modules(模版语言中使用,暂时忽略)
  • ui_methods(模版语言中使用,暂时忽略)
  • 是否debug模式运行

  以上的所有配置信息,都可以在settings中配置,然后在创建Application对象时候,传入参数即可。如:application = tornado.web.Application([(r"/index", MainHandler),],**settings)

生成url映射:

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

 封装数据:

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

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

二、application.listen(xxx)

  第一步操作将配置和url映射等信息封装到了application对象中,而这第二步执行application对象的listen方法,该方法内部又把之前包含各种信息的application对象封装到了一个HttpServer对象中,然后继续调用HttpServer对象的liseten方法。

  1. class Application(object):
  2. #创建服务端socket,并绑定IP和端口并添加相应设置,注:未开始通过while监听accept,等待客户端连接
  3. def listen(self, port, address="", **kwargs):
  4. from tornado.httpserver import HTTPServer
  5. server = HTTPServer(self, **kwargs)
  6. server.listen(port, address)

详细代码:

  1. class HTTPServer(object):
  2. def __init__(self, request_callback, no_keep_alive=False, io_loop=None,xheaders=False, ssl_options=None):
  3. #Application对象
  4. self.request_callback = request_callback
  5. #是否长连接
  6. self.no_keep_alive = no_keep_alive
  7. #IO循环
  8. self.io_loop = io_loop
  9. self.xheaders = xheaders
  10. #Http和Http
  11. self.ssl_options = ssl_options
  12. self._socket = None
  13. self._started = False
  14.  
  15. def listen(self, port, address=""):
  16. self.bind(port, address)
  17. self.start(1)
  18.  
  19. def bind(self, port, address=None, family=socket.AF_UNSPEC):
  20. assert not self._socket
  21. #创建服务端socket对象,IPV4和TCP连接
  22. self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
  23. flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)
  24. flags |= fcntl.FD_CLOEXEC
  25. fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)
  26. #配置socket对象
  27. self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  28. self._socket.setblocking(0)
  29. #绑定IP和端口
  30. self._socket.bind((address, port))
  31. #最大阻塞数量
  32. self._socket.listen(128)
  33.  
  34. def start(self, num_processes=1):
  35. assert not self._started
  36. self._started = True
  37. if num_processes is None or num_processes <= 0:
  38. num_processes = _cpu_count()
  39. if num_processes > 1 and ioloop.IOLoop.initialized():
  40. logging.error("Cannot run in multiple processes: IOLoop instance "
  41. "has already been initialized. You cannot call "
  42. "IOLoop.instance() before calling start()")
  43. num_processes = 1
  44. #如果进程数大于1
  45. if num_processes > 1:
  46. logging.info("Pre-forking %d server processes", num_processes)
  47. for i in range(num_processes):
  48. if os.fork() == 0:
  49. import random
  50. from binascii import hexlify
  51. try:
  52. # If available, use the same method as
  53. # random.py
  54. seed = long(hexlify(os.urandom(16)), 16)
  55. except NotImplementedError:
  56. # Include the pid to avoid initializing two
  57. # processes to the same value
  58. seed(int(time.time() * 1000) ^ os.getpid())
  59. random.seed(seed)
  60. self.io_loop = ioloop.IOLoop.instance()
  61. self.io_loop.add_handler(
  62. self._socket.fileno(), self._handle_events,
  63. ioloop.IOLoop.READ)
  64. return
  65. os.waitpid(-1, 0)
  66. #进程数等于1,默认
  67. else:
  68. if not self.io_loop:
  69. #设置成员变量self.io_loop为IOLoop的实例,注:IOLoop使用methodclass完成了一个单例模式
  70. self.io_loop = ioloop.IOLoop.instance()
  71. #执行IOLoop的add_handler方法,将socket句柄、self._handle_events方法和IOLoop.READ当参数传入
  72. self.io_loop.add_handler(self._socket.fileno(),
  73. self._handle_events,
  74. ioloop.IOLoop.READ)
  75. def _handle_events(self, fd, events):
  76. while True:
  77. try:
  78. #====important=====#
  79. connection, address = self._socket.accept()
  80. except socket.error, e:
  81. if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
  82. return
  83. raise
  84. if self.ssl_options is not None:
  85. assert ssl, "Python 2.6+ and OpenSSL required for SSL"
  86. try:
  87. #====important=====#
  88. connection = ssl.wrap_socket(connection,server_side=True,do_handshake_on_connect=False,**self.ssl_options)
  89. except ssl.SSLError, err:
  90. if err.args[0] == ssl.SSL_ERROR_EOF:
  91. return connection.close()
  92. else:
  93. raise
  94. except socket.error, err:
  95. if err.args[0] == errno.ECONNABORTED:
  96. return connection.close()
  97. else:
  98. raise
  99. try:
  100. if self.ssl_options is not None:
  101. stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
  102. else:
  103. stream = iostream.IOStream(connection, io_loop=self.io_loop)
  104. #====important=====#
  105. HTTPConnection(stream, address, self.request_callback,self.no_keep_alive, self.xheaders)
  106. except:
  107. logging.error("Error in connection callback", exc_info=True)

HTTPServer

  1. class IOLoop(object):
  2. # Constants from the epoll module
  3. _EPOLLIN = 0x001
  4. _EPOLLPRI = 0x002
  5. _EPOLLOUT = 0x004
  6. _EPOLLERR = 0x008
  7. _EPOLLHUP = 0x010
  8. _EPOLLRDHUP = 0x2000
  9. _EPOLLONESHOT = (1 << 30)
  10. _EPOLLET = (1 << 31)
  11.  
  12. # Our events map exactly to the epoll events
  13. NONE = 0
  14. READ = _EPOLLIN
  15. WRITE = _EPOLLOUT
  16. ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP
  17.  
  18. def __init__(self, impl=None):
  19. self._impl = impl or _poll()
  20. if hasattr(self._impl, 'fileno'):
  21. self._set_close_exec(self._impl.fileno())
  22. self._handlers = {}
  23. self._events = {}
  24. self._callbacks = []
  25. self._timeouts = []
  26. self._running = False
  27. self._stopped = False
  28. self._blocking_signal_threshold = None
  29.  
  30. # Create a pipe that we send bogus data to when we want to wake
  31. # the I/O loop when it is idle
  32. if os.name != 'nt':
  33. r, w = os.pipe()
  34. self._set_nonblocking(r)
  35. self._set_nonblocking(w)
  36. self._set_close_exec(r)
  37. self._set_close_exec(w)
  38. self._waker_reader = os.fdopen(r, "rb", 0)
  39. self._waker_writer = os.fdopen(w, "wb", 0)
  40. else:
  41. self._waker_reader = self._waker_writer = win32_support.Pipe()
  42. r = self._waker_writer.reader_fd
  43. self.add_handler(r, self._read_waker, self.READ)
  44.  
  45. @classmethod
  46. def instance(cls):
  47. if not hasattr(cls, "_instance"):
  48. cls._instance = cls()
  49. return cls._instance
  50.  
  51. def add_handler(self, fd, handler, events):
  52. """Registers the given handler to receive the given events for fd."""
  53. self._handlers[fd] = stack_context.wrap(handler)
  54. self._impl.register(fd, events | self.ERROR)

IOLoop

  1. def wrap(fn):
  2. '''Returns a callable object that will resore the current StackContext
  3. when executed.
  4.  
  5. Use this whenever saving a callback to be executed later in a
  6. different execution context (either in a different thread or
  7. asynchronously in the same thread).
  8. '''
  9. if fn is None:
  10. return None
  11. # functools.wraps doesn't appear to work on functools.partial objects
  12. #@functools.wraps(fn)
  13. def wrapped(callback, contexts, *args, **kwargs):
  14. # If we're moving down the stack, _state.contexts is a prefix
  15. # of contexts. For each element of contexts not in that prefix,
  16. # create a new StackContext object.
  17. # If we're moving up the stack (or to an entirely different stack),
  18. # _state.contexts will have elements not in contexts. Use
  19. # NullContext to clear the state and then recreate from contexts.
  20. if (len(_state.contexts) > len(contexts) or
  21. any(a[1] is not b[1]
  22. for a, b in itertools.izip(_state.contexts, contexts))):
  23. # contexts have been removed or changed, so start over
  24. new_contexts = ([NullContext()] +
  25. [cls(arg) for (cls,arg) in contexts])
  26. else:
  27. new_contexts = [cls(arg)
  28. for (cls, arg) in contexts[len(_state.contexts):]]
  29. if len(new_contexts) > 1:
  30. with contextlib.nested(*new_contexts):
  31. callback(*args, **kwargs)
  32. elif new_contexts:
  33. with new_contexts[0]:
  34. callback(*args, **kwargs)
  35. else:
  36. callback(*args, **kwargs)
  37. if getattr(fn, 'stack_context_wrapped', False):
  38. return fn
  39. contexts = _state.contexts
  40. result = functools.partial(wrapped, fn, contexts)
  41. result.stack_context_wrapped = True
  42. return result

stack_context.wrap

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

上述代码本质上就干了以下这么四件事:

  1. 把包含了各种配置信息的application对象封装到了HttpServer对象的request_callback字段中
  2. 创建了服务端socket对象
  3. 单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中
  4. 向epoll中注册监听服务端socket对象的读可用事件

目前,我们只是看到上述代码大致干了这四件事,而其目的有什么?他们之间的联系又是什么呢?

答:现在不妨先来做一个猜想,待之后再在源码中确认验证是否正确!猜想:通过epoll监听服务端socket事件,一旦请求到达时,则执行3中被封装了的_handle_events函数,该函数又利用application中封装了的各种配置信息对客户端url来指定判定,然后指定对应的Handler处理该请求。

注意:使用epoll创建服务端socket

  1. import socket, select
  2.  
  3. EOL1 = b'/n/n'
  4. EOL2 = b'/n/r/n'
  5. response = b'HTTP/1.0 200 OK/r/nDate: Mon, 1 Jan 1996 01:01:01 GMT/r/n'
  6. response += b'Content-Type: text/plain/r/nContent-Length: 13/r/n/r/n'
  7. response += b'Hello, world!'
  8.  
  9. serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  10. serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  11. serversocket.bind(('0.0.0.0', 8080))
  12. serversocket.listen(1)
  13. serversocket.setblocking(0)
  14.  
  15. epoll = select.epoll()
  16. epoll.register(serversocket.fileno(), select.EPOLLIN)
  17.  
  18. try:
  19. connections = {}; requests = {}; responses = {}
  20. while True:
  21. events = epoll.poll(1)
  22. for fileno, event in events:
  23. if fileno == serversocket.fileno():
  24. connection, address = serversocket.accept()
  25. connection.setblocking(0)
  26. epoll.register(connection.fileno(), select.EPOLLIN)
  27. connections[connection.fileno()] = connection
  28. requests[connection.fileno()] = b''
  29. responses[connection.fileno()] = response
  30. elif event & select.EPOLLIN:
  31. requests[fileno] += connections[fileno].recv(1024)
  32. if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
  33. epoll.modify(fileno, select.EPOLLOUT)
  34. print('-'*40 + '/n' + requests[fileno].decode()[:-2])
  35. elif event & select.EPOLLOUT:
  36. byteswritten = connections[fileno].send(responses[fileno])
  37. responses[fileno] = responses[fileno][byteswritten:]
  38. if len(responses[fileno]) == 0:
  39. epoll.modify(fileno, 0)
  40. connections[fileno].shutdown(socket.SHUT_RDWR)
  41. elif event & select.EPOLLHUP:
  42. epoll.unregister(fileno)
  43. connections[fileno].close()
  44. del connections[fileno]
  45. finally:
  46. epoll.unregister(serversocket.fileno())
  47. epoll.close()
  48. serversocket.close()

Code

上述,其实就是利用epoll对象的poll(timeout)方法去轮询已经注册在epoll中的socket句柄,当有读可用的信息时候,则返回包含当前句柄和Event Code的序列,然后在通过句柄对客户端的请求进行处理

三、tornado.ioloop.IOLoop.instance().start()

上一步中创建了socket对象并使得socket对象和epoll建立了关系,该步骤则就来执行epoll的epoll方法去轮询已经注册在epoll对象中的socket句柄,当有读可用信息时,则触发一些操作什么的....

  1. class IOLoop(object):
  2. def add_handler(self, fd, handler, events):
  3. #HttpServer的Start方法中会调用该方法
  4. self._handlers[fd] = stack_context.wrap(handler)
  5. self._impl.register(fd, events | self.ERROR)
  6.  
  7. def start(self):
  8. while True:
  9. poll_timeout = 0.2
  10. try:
  11. #epoll中轮询
  12. event_pairs = self._impl.poll(poll_timeout)
  13. except Exception, e:
  14. #省略其他
  15. #如果有读可用信息,则把该socket对象句柄和Event Code序列添加到self._events中
  16. self._events.update(event_pairs)
  17. #遍历self._events,处理每个请求
  18. while self._events:
  19. fd, events = self._events.popitem()
  20. try:
  21. #以socket为句柄为key,取出self._handlers中的stack_context.wrap(handler),并执行
  22. #stack_context.wrap(handler)包装了HTTPServer类的_handle_events函数的一个函数
  23. #是在上一步中执行add_handler方法时候,添加到self._handlers中的数据。
  24. self._handlers[fd](fd, events)
  25. except:
  26. #省略其他
  1. class IOLoop(object):
  2. def start(self):
  3. """Starts the I/O loop.
  4.  
  5. The loop will run until one of the I/O handlers calls stop(), which
  6. will make the loop stop after the current event iteration completes.
  7. """
  8. if self._stopped:
  9. self._stopped = False
  10. return
  11. self._running = True
  12. while True:
  13. # Never use an infinite timeout here - it can stall epoll
  14. poll_timeout = 0.2
  15.  
  16. # Prevent IO event starvation by delaying new callbacks
  17. # to the next iteration of the event loop.
  18. callbacks = self._callbacks
  19. self._callbacks = []
  20. for callback in callbacks:
  21. self._run_callback(callback)
  22.  
  23. if self._callbacks:
  24. poll_timeout = 0.0
  25.  
  26. if self._timeouts:
  27. now = time.time()
  28. while self._timeouts and self._timeouts[0].deadline <= now:
  29. timeout = self._timeouts.pop(0)
  30. self._run_callback(timeout.callback)
  31. if self._timeouts:
  32. milliseconds = self._timeouts[0].deadline - now
  33. poll_timeout = min(milliseconds, poll_timeout)
  34.  
  35. if not self._running:
  36. break
  37.  
  38. if self._blocking_signal_threshold is not None:
  39. # clear alarm so it doesn't fire while poll is waiting for
  40. # events.
  41. signal.setitimer(signal.ITIMER_REAL, 0, 0)
  42.  
  43. try:
  44. event_pairs = self._impl.poll(poll_timeout)
  45. except Exception, e:
  46. # Depending on python version and IOLoop implementation,
  47. # different exception types may be thrown and there are
  48. # two ways EINTR might be signaled:
  49. # * e.errno == errno.EINTR
  50. # * e.args is like (errno.EINTR, 'Interrupted system call')
  51. if (getattr(e, 'errno', None) == errno.EINTR or
  52. (isinstance(getattr(e, 'args', None), tuple) and
  53. len(e.args) == 2 and e.args[0] == errno.EINTR)):
  54. continue
  55. else:
  56. raise
  57.  
  58. if self._blocking_signal_threshold is not None:
  59. signal.setitimer(signal.ITIMER_REAL,
  60. self._blocking_signal_threshold, 0)
  61.  
  62. # Pop one fd at a time from the set of pending fds and run
  63. # its handler. Since that handler may perform actions on
  64. # other file descriptors, there may be reentrant calls to
  65. # this IOLoop that update self._events
  66. self._events.update(event_pairs)
  67. while self._events:
  68. fd, events = self._events.popitem()
  69. try:
  70. self._handlers[fd](fd, events)
  71. except (KeyboardInterrupt, SystemExit):
  72. raise
  73. except (OSError, IOError), e:
  74. if e.args[0] == errno.EPIPE:
  75. # Happens when the client closes the connection
  76. pass
  77. else:
  78. logging.error("Exception in I/O handler for fd %d",
  79. fd, exc_info=True)
  80. except:
  81. logging.error("Exception in I/O handler for fd %d",
  82. fd, exc_info=True)
  83. # reset the stopped flag so another start/stop pair can be issued
  84. self._stopped = False
  85. if self._blocking_signal_threshold is not None:
  86. signal.setitimer(signal.ITIMER_REAL, 0, 0)

对于上述代码,执行start方法后,程序就进入“死循环”,也就是会一直不停的轮询的去检查是否有请求到来,如果有请求到达,则执行封装了HttpServer类的_handle_events方法和相关上下文的stack_context.wrap(handler)(其实就是执行HttpServer类的_handle_events方法),详细见下篇博文,简要代码如下:

  1. class HTTPServer(object):
  2. def _handle_events(self, fd, events):
  3. while True:
  4. try:
  5. connection, address = self._socket.accept()
  6. except socket.error, e:
  7. if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN):
  8. return
  9. raise
  10. if self.ssl_options is not None:
  11. assert ssl, "Python 2.6+ and OpenSSL required for SSL"
  12. try:
  13. connection = ssl.wrap_socket(connection,
  14. server_side=True,
  15. do_handshake_on_connect=False,
  16. **self.ssl_options)
  17. except ssl.SSLError, err:
  18. if err.args[0] == ssl.SSL_ERROR_EOF:
  19. return connection.close()
  20. else:
  21. raise
  22. except socket.error, err:
  23. if err.args[0] == errno.ECONNABORTED:
  24. return connection.close()
  25. else:
  26. raise
  27. try:
  28. if self.ssl_options is not None:
  29. stream = iostream.SSLIOStream(connection, io_loop=self.io_loop)
  30. else:
  31. stream = iostream.IOStream(connection, io_loop=self.io_loop)
  32. HTTPConnection(stream, address, self.request_callback,
  33. self.no_keep_alive, self.xheaders)
  34. except:
  35. logging.error("Error in connection callback", exc_info=True) 

结束

本篇博文介绍了“待请求阶段”的所作所为,简要来说其实就是三件事:其一、把setting中的各种配置以及url和Handler之间的映射关系封装到来application对象中(application对象又被封装到了HttpServer对象的request_callback字段中);其二、结合epoll创建服务端socket;其三、当请求到达时交由HttpServer类的_handle_events方法处理请求,即:处理请求的入口。对于处理请求的详细,请参见下篇博客(客官莫急,加班编写中...)

第二篇:白话tornado源码之待请求阶段的更多相关文章

  1. 第三篇:白话tornado源码之请求来了

    上一篇<白话tornado源码之待请求阶段>中介绍了tornado框架在客户端请求之前所做的准备(下图1.2部分),本质上就是创建了一个socket服务端,并进行了IP和端口的绑定,但是未 ...

  2. 第五篇:白话tornado源码之褪去模板的外衣

    上一篇<白话tornado源码之请求来了>介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求.在上一篇的内容中,我们只是给客户端返回了 ...

  3. 第一篇:白话tornado源码之一个脚本引发的血案

    本系列博文计划: 1.剖析基于Python的Web框架Tornado的源码 2.为Python开发一个完善的MVC框架 首先将带着大家一起来剖析基于python编写的Web框架 tornado ,本着 ...

  4. ExcelReport第二篇:ExcelReport源码解析

    导航 目   录:基于NPOI的报表引擎——ExcelReport 上一篇:使用ExcelReport导出Excel 下一篇:扩展元素格式化器 概述 针对上一篇随笔收到的反馈,在展开对ExcelRep ...

  5. 第二篇:SOUI源码的获取及编译

    源代码的获取 SOUI的源码采用SVN管理. SVN:http://code.taobao.org/svn/soui2 这里主要包含两个目录:trunk 及 third-part. trunk目录保存 ...

  6. 工作之余第二篇(看源码自己实现ArrayList和LinkList)

    先看源码: 首先看构造器,构造器有三种,一种直接给定初始长度的,如下代码 public ArrayList(int initialCapacity) { if (initialCapacity > ...

  7. Tornado源码探寻(准备阶段)

    上一篇从一个简单的例子大致了解到Tornado框架的一个概述,同时也看清了web框架的本质. 接下来,我们从tornado程序的起始来分析其源码: 一.概述 上图是摘自朋友的博客里的内容,这张图很明确 ...

  8. 第四篇:白话tornado源码之褪去模板外衣的前戏

    加班程序员最辛苦,来张图醒醒脑吧! ... ... ... 好了,醒醒吧,回归现实看代码了!! 执行字符串表示的函数,并为该函数提供全局变量 本篇的内容从题目中就可以看出来,就是为之后剖析tornad ...

  9. Tornado源码分析 --- 静态文件处理模块

    每个web框架都会有对静态文件的处理支持,下面对于Tornado的静态文件的处理模块的源码进行分析,以加强自己对静态文件处理的理解. 先从Tornado的主要模块 web.py 入手,可以看到在App ...

随机推荐

  1. Oracle监听的静态注册和动态注册

    静态注册:通过解析listene.ora文件 动态注册:由PMON进程动态注册至监听中 在没有listener.ora配置文件的情况下,如果启动监听,则监听为动态注册.用图形化netca创建的监听,默 ...

  2. 代理模式/proxy模式/结构型模式

    代理模式proxy 定义 为其他对象提供一种代理,并以控制对这个对象的访问.最简单的理解,买东西都是要去商店的,不会去工厂. java实现三要素 proxy(代理)+subject(接口)+realS ...

  3. ios 模拟器内部网络连接问题

    今日,一运行程序,打印出来头疼的的日志 "Error Domain=kCFErrorDomainCFNetwork Code=-1001 "The request timed ou ...

  4. jmeter仅一次控制器

    仅针对 1个线程的 多线程的那个不生效  想实现多次执行某个请求只执行一次 需要设置为单线程 循环次数设置为多次就可以了 

  5. IIS8的证书设置

    首先,打开IIS的网站,找到“服务器证书” 然后根据需要创建证书 创建好后,如果某一个网站(注意是网站,不是应用程序集)需要使用https则, 在右侧“绑定”一项中添加新的https连接,并选择对应的 ...

  6. UIView--震动效果

    //震动效果- (void)shake:(UIView *)view{ CGRect frame = view.frame; CAKeyframeAnimation *shakeAnimation = ...

  7. Subsonic简单的语法整理

    .查询方面 (查询所有数据记录[dataset读取方法]) Myuser.Query().ExecuteDataSet().Tables[]; Myuser.Query().ExecuteDataSe ...

  8. language level in Intellij IDEA

    The Language level setting sets which features the code assistance in the editor should support. For ...

  9. linux 限制root SSH登陆和限制su

    linux 限制root用户SSH登录:   1.修改SSHD服务的配置文件/etc/ssh/sshd_config    将#PermitRootLogin yes 改为PermitRootLogi ...

  10. 【BZOJ4008】[HNOI2015]亚瑟王 期望

    [BZOJ4008][HNOI2015]亚瑟王 Description 小 K 不慎被 LL 邪教洗脑了,洗脑程度深到他甚至想要从亚瑟王邪教中脱坑. 他决定,在脱坑之前,最后再来打一盘亚瑟王.既然是最 ...