tornado源码分析系列一

简介:

先来看一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env  python
#coding:utf8
 
import  socket
 
 
def  run():
     sock  =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     sock.bind(( '127.0.0.1' , 8008 ))
     sock.listen( 5 )
     
     while  True :
         connection,address  =  sock.accept()
         handle_request(connection)
         connection.close()
     
def  handle_request(client):
     but  =  client.recv( 1024 )
     client.send( "HTTP/1.1 200 OK \r\n\r\n" )
     client.send( "Hello everyone!" )
     
if  __name__  = =  "__main__" :
     run()

上述分析:

  1、浏览器其实就是一个socket客户端,而web应用其实就是一个socket服务端,并且web应用在服务器上一直在监听某个端口。

  2、当浏览器请求某个web应用时,需要指定服务器的IP(DNS解析)和端口建立一个socket连接。

  3、建立链接后,web应用根据请求的不同,给用户返回相应的数据。

  4、断开socket连接。(之所以说http是短链接,其实就是因为每次请求完成后,服务器就会断开socket链接)

  对于Web框架来说,一般分为两类,其中一类则是包含上述 4部分 内容的框架,另外一类就是只包含 第3部分 功能的框架。tornado就是一中属于前者的框架。tornado 是一个基于 Python 开发的web框架,较其他 Web 框架的区别是:采用了非阻塞的方式和对epoll的应用。这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。


tornado经典官网DEMO

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

运行该脚本,依次执行:

  • 创建一个Application对象,并把一个正则表达式'/'和类名MainHandler传入构造函数:tornado.web.Application(...)  

  • 执行Application对象的listen(...)方法,即:application.listen(8888)

  • 执行IOLoop类的类的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()

整个过程其实就是在创建一个socket服务端并监听8888端口,当请求到来时,根据请求中的url和请求方式(post、get或put等)来指定相应的类中的方法来处理本次请求,在上述demo中只为url为http://127.0.0.1:8888/index的请求指定了处理类MainHandler。所以,在浏览器上访问:http://127.0.0.1:8888/index,则服务器给浏览器就会返回 Hello,world ,否则返回 404: Not Found(tornado内部定义的值), 即完成一次http请求和响应。


tornado处理请求的步骤大体可以分为两个部分:启动程序阶段和处理请求阶段

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

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

启动程序阶段:

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


Application.__init__:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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()

    2、Application.add_Handlers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class  Application( object ):    
     def  add_handlers( self , host_pattern, host_handlers):        
         #如果主机模型最后没有结尾符,那么就为他添加一个结尾符。
         if  not  host_pattern.endswith( "$" ):
             host_pattern  + =  "$"
         handlers  =  []        
         #对主机名先做一层路由映射,例如:http://www.5ihouse.com 和 http://safe.5ihouse.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

    3、URLspec:

1
2
3
4
5
6
7
8
9
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()

上述三个步骤主要完成了:

    1、静态文件路径设置

    2、ui_modules和ui_methods(模板语言中使用)

    3、是否debug模式运行

    4、生成URL映射 

    5、封装数据,将配置信息和url映射关系封装到Application对象中

     6、保存编码和返回方式信息(self.transforms)

    7、self.settings 保存配置信息

      8、self.Handler 保存着所有的主机名对应的Handlers,每个handlers则是url正则对应的Handler


以上为application = tornado.web.Application([(r"/index",MainHandler),])完成的工作


   4、application.listen(8888)

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

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

    5、HTTPServer类代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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和Https
         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  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 )

    6、IOloop类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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()
             =  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)

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

  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处理该请求。


本文转自 AltBoy 51CTO博客,原文链接:http://blog.51cto.com/altboy/1958775


相关文章
|
5月前
|
JSON 前端开发 数据库
17 Tornado - Tornado异步
17 Tornado - Tornado异步
52 1
|
5月前
|
Unix Linux Python
03 Tornado - 入门程序
03 Tornado - 入门程序
33 0
|
5月前
|
调度 Python
16 Tornado - 认识异步
16 Tornado - 认识异步
32 1
|
5月前
|
Ubuntu 应用服务中间件 开发工具
19 Tornado - 部署Tornado
19 Tornado - 部署Tornado
30 0
|
5月前
|
应用服务中间件 数据库 nginx
01 Tornado - 介绍
01 Tornado - 介绍
25 0
|
5月前
|
XML 存储 JSON
14 Tornado - XSRF
14 Tornado - XSRF
15 1
|
9月前
|
JSON 网络协议 中间件
Sanic源码剖析
Sanic源码剖析
|
9月前
|
应用服务中间件 Linux 程序员
Python下篇 2. Tornado异步编程
Python下篇 2. Tornado异步编程
|
安全 中间件 API
werkzeug源码阅读-上
Werkzeug是一个全面的WSGI Web应用程序库。它最初是WSGI实用程序各种工具的简单集合,现已成为最高级的WSGI实用程序库之一,是Flask背后的项目。
225 0
werkzeug源码阅读-上
|
存储 算法 前端开发
werkzeug源码阅读-下
Werkzeug是一个全面的WSGI Web应用程序库。它最初是WSGI实用程序各种工具的简单集合,现已成为最高级的WSGI实用程序库之一,是Flask背后的项目。
303 0
werkzeug源码阅读-下