Archive for tornado

昨天下午写了一个postgresql访问接口库psycopg2的异步封装,这个库是支持异步请求postgresql的,所以只是简单的封装了一下。简介了torndb和一个网上写好的异步库momoko,又找到一个网上分享的ppt。写的时候直接用gen.coroutine装饰接口函数,这样我再models里写的时候直接yield query()就行了。 写完对tornado异步封装的代码也看了一些,有了一些理解,不过没有循序看。之前看到时候感觉太乱实在不想从新看了。先记录一下,可能很多错误。 future是保存的异步的结果,这个类是在concurrent.py文件里。 result()方法是返回结果,通过done()方法判断执行结束,就可以用result方法获得结果。 set_result()是设置结果,像gen.Task()就是调用一个带有callback参数的异步方法,返回future,变成为一个可以使用yield协程 的函数,对他就是个函数。2333 这个函数里边就使用了set_result()方法,Task这个函数其实不复杂写了那么多的目的就是把Task装饰的那个函数里边callback的参数用set_result()设置成返回值,保存在future里。哈哈 我觉得以后我看这里也不会看懂,还不如直接看代码容易。这里用到了带名字的元组,这个看到过没用过。set_result()用的地方很多 其他的基本就是异常处理, 先看看其他周边,想gen.Return是继承自Exception所以这个用raise,这个异常会在后边用的时候捕获到,然后当做结果。 其他的没看,代码没看全,没看到那些类和函数。 coroutine这个装饰器,内容很多。 先是执行函数,有返回值,保存,然后如果抛出Return exception 获取里边的值,或者其他程序异常。 没有异常,就判断结果是否是生成器,不是生成器直接将结果set_result保存到future并返回结果,如果是next执行之,如果执行完或者Return设置执行完的值,这里异常处理和上边一样,执行完后如果没有异常产生,执行Runner(),handler_yield没看,主要看了run(),处理方式基本相同,主要就是将刚刚执行的结果通过send方法返回给yield前边的变量,就是类似a = yield query()这种,将结果返回给a。 然后总结一下思路,可能不太对。requesthandler执行的时候,执行相对应的http method,如果返回的不是future,那ok,流程上边写过了,执行完没有异常并判断不是个生成器类型就会直接在future保存结果并返回。为什么还要保存到future中呢。因为requesthandler里的execute也是经过coroutine修饰过了。 发现返回一个future,因为比如一个get方法被我们用coroutine装饰了。然后他就yield,这个的返回结果就是你get方法的结果了。 然后执行对应的我的model里的search_result(),然后执行query语句,我都是用gen.Return返回的结果,怎么感觉这么复杂。。。感觉这样写不太对,不太符合设计 query方法先获取cursor,这些都是coroutine修饰的,都是返回的生成器类型。(之所以这样写,这个库只我自己用,我不喜欢callback的模式,感觉yield写起来也方便看起来也方便,封装的时候参数也不写callback了)使用gen.Task获取conn,为啥用这个因为这个必须设置callback,然后里边的callback参数就是conn,这样task会将装饰的函数参数callback的参数最终作为结果返回。这里说装饰也不太对,Task不是个装饰器。感觉跟functools里的partial很像,但不一样,他返回一个future。这样就会最终返回给我链接数据库的conn,在cursor我是用gen.Return返回corsors,这个会捕获到Return异常直接设置到future里当做结果,最后通过runner里的run返回给yield关键字前的cursor变量。这样就获取到了cousor。。。。。下边执行语句的时候和这个流程一样

Continue

前边ioloop看完了,application以前也看过一点,现在看看application和IOLoop是怎么结合起来的。 Application里的listen(port),实例化一个HTTPServer(),然后listen port address。 HTTPServer()继承自TCPServer, httputil.HTTPServerConnectionDelegate。HTTPServer参数传入application实例,还有一些参数。app保存在属性request_callback中,调用TCPServer的初始化方法,ioloop默认为None。然后都是参数的 保存。 listen方法是TCPServer里的,第一个方法返回socket,后调用add_sockets。self.ioploop为None,调用类静态方法IOLoop.current(),实例化IOLoop。然后将所有socket保存到_sockets中,调用在netutil.py里的add_accept_handler。 add_accept_handler第二个参数是一个函数,_handle_connection,ssl的不看。总的说就是实例化一个IOStream,然后调用handle_stream,handle_stream应该是看HTTPServer里的定义,这里没有。我去 看到这里感觉又要晕了。httpserver.py里的handle_stream应该是启动了httpserver。看到这里总结一个下。 一条是add_accept_handler的第二个参数应该是启动httpserver的功能的一个函数,看参数名是个callback。实例化了一个IOStream还没看,然后调用handle_stream启动httpserver。 额,先看看IOStream。继承BaseIOStream,额,没啥看的,付了一堆的变量。然后看httpserver.py里的handle_stream,一个context,看HTTP1ServerConnection,主要看start_serving,先进行类型判断,这是返回true,因为HTTPServer继承自httputil.HTTPServerConnectionDelegate,类似接口,只定义了两个空方法。然后后边运行_server_request_loop,异步。主要调了连接的read_response方法。大体看了一下,就是对包内容进行解析了。不看了 没仔细看逻辑,感觉画个图会好一点,看的都快吐了。看个流程都这么麻烦,以后再静下心来看一遍。放松一下

Continue

tornado.ioloop.IOLoop.instance().start() ioloop继承自Configurable。 configurable_base返回IOLoop,然后调用configured_class,然后再看IOLoop的configurable_default。这里面根据不同系统选择了不同的IOLoop类实现返回。下一步是initialize初始化了。 看一下epoll吧。 先找了一下IOLoop的start(),意料之中。因为configurable_default返回的类实现都是别的类,要看具体别的类中的定义了。 epoll.py里的EPollIOLoop只定义了initialize,EPollIOLoop继承自PollIOLoop,在ioloop.py文件里。 PollIOLoop继承自IOLoop,现在还不知道为什么这么继承,先往下看。super,IOLoop里边是空的。下边用到initialize第二个参数是impl。在PollIOLoop里是select.epoll()。还要去看看platform/select.py,额 我去还是import select。标准库中的select.epoll()返回一个epoll obj。判断是否有fileno属性,这个方法是返回一个文件描述符的。然后不知道是怎么操作这个句柄的不懂。先过。。 设置了一堆值。看platform/common.py里的Waker.继承自interface.Waker.父类只是定义了几个方法名。 先看初始化,self.write是个socket.self.writer.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)注释说禁止buffer,有数据就立即发送。 下边是做了一个测试,好像有些系统不支持检查出来。 下边又没懂,a都关闭了,还要那个文件描述符干嘛.这块后边做个测试研究一下。 初始化完了回到PollIOLoop,add_handler,第一个参数a的文件描述符,第二个参数consume是从a读数据读完为止。第三个参数是IOLoop里定义的,这里体现出继承的意义来了。 看add_handler,spit_fd取文件描述符,第二个handlers空,添加fd为key,文件描述符,handler为值。 没看懂,注释说就为了在io loop空闲时,想让他醒来发送假数据,这里也先过,网络方面的还是懂的太少了 然后初始化完成了。。下边是调用instance()了。这个只有在IOLoop里有定义,这里才是真正的继承意义。开始是没有_instance的,然后加锁,定义个实例IOLoop()返回这个实例。 饶了这么多圈终于实例化了。。。卧槽来 然后start(),PollIOLoop里,这里开始ioloop的循环执行了。详细的先不看了,看了一下等后边仔细分析一下。上边的waker也是结合这里一起再看一遍。

Continue

想看一下IOLoop类的实现,发现继承自Configurable。就先看一下这个类。 Configurable类在util.py这个文件里。Configurable类的子类有AsyncHTTPClient,Resolver,IOLoop。 看__new__构造方法,configurable_base返回一个子类的实现,如果返回类本身。类的实现需要调用configured_class,主要初始化了__impl_class在configurable_default里。然后通过子类中configurable_default的定义选择不同的__impl_class(类实现)返回。initialize初始化类。 然后没了 ,没想到这么短。等后边看看IOLoop。

Continue

tornado里面有关几个cookie的处理,在web.py文件里。 get_cookie,set_cookie普通的设置cookie,clear_cookie,clear_all_cookies是删除cookie。 还有两个是get_secure_cookie,set_secure_cookie,这两个的功能是能够防止用户的cookie被伪造。 先看看函数处理方法。set_secure_cookie,里边与set_cookie的区别就是value经过create_signed_value的处理。 create_signed_value,得到当前时间,将要存的value base64编码,通过_cookie_signature将 加上name,这三个值加密生成签名。然后将签名,value的base64编码,时间戳用|连接,作为cookie的值。 _cookie_signature,就是根据settings里边的 保密的密钥生成签名返回。 get_secure_cookie,用|分割cookie的value,通过name,原value的base64的编码,时间戳得到签名,验证签名是否正确,正确返回,还多了一个过期时间的判断 如果别人想伪造用户的cookie,必须要知道密钥,才能生成正确的签名,不然通过get_secure_cookie获取value的时候,不会通过验证,然后就不会返回伪造的cookie值。

Continue

还是从helloworld里,application = tornado.web.Application([(r"/", MainHandler),]),然后看类Application。 参数 transforms用于头信息的定义,列表存储处理类名。ChunkedTransferEncoding,GZipContentEncoding继承自OutputTransform,可以实例化类的时候传入参数transforms,想gzip的启用可以使用setting,例如:settings = {'gzip': True}传入settings参数。 handlers列表,元素为(regexp, request_class)元祖。 settings,还可以设置static_path来标明静态文件访问路径,static_url_prefix标明访问前缀,没有默认设置"/static/"。给添加静态文件访问路由。最后合并参数中 的handlers。这里有一个StaticFileHandler类,等会儿看。 再往下是否载入自动更新模块,通过settings中debug设置。autoreload最最后再看吧。。初始化结束 方法 add_handlers。添加路由协议。983行,self.handlers[-1][0].pattern这里开始没看懂,后来看了re的文档才知道,这里几个变量也容易搞混,不易看懂。self.handlers只能通过,add_handlers进行添加。所以在__init__里面执行add_handlers的时候,self.handlers为空列表。983行 if self.handlers and self.handlers[-1][0].pattern == '.*$':判断只会执行if self.handlers。而当后来进行add_handlers的时候,self.handlers不为空,后面self.handlers[-1][0]类型为正则匹配的类型(我也不知道具体啥名,用type为SRE_Pattern与文档的不一样),可以使用.pattern返回匹配用的字符串进行比较。 这里的功能是为了让‘.*$’匹配放到规则的最下边,这个匹配是域名匹配,路径匹配在handlers,是一个列表,可变对象。在往下就是想handers列表中添加匹配规则,类型URLSpec。具体格式可以打印,先看看类URLSpec。再往下是添加named_handlers,用来通过reverse_url方法,使用name返回匹配的url路径,这个name也是在URLSpec。 URLSpec中self._path, self._group_count的用处现在还不清楚,其他几个上边都有介绍。self._path是把url路径中所有要匹配的替换成%s。self._group_count需要匹配 的数量。reverse方法就是将args带入到self._path。 UIModule类 定义同一类页面的显示。现在还没看到在哪里初始化和使用,Application中_load_ui_methods,_load_ui_modules通过settings设置对应的属性。 _get_host_handlers,__call__在我看完RequestHandler类之后再看,还有web.py其他的类一起。 通过修改helloworl打印一些信息,帮助理解代码:

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import os.path

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def main():
    tornado.options.parse_command_line()
    settings = {'gzip': True, 'debug': True,
                'static_path': os.path.join(os.path.dirname(__file__), "static"),
                }
    application = tornado.web.Application([
        (r"/", MainHandler),
    ], **settings)
    print application.transforms
    print repr(application.handlers)
    h = application.handlers[0][0]
    print h.pattern
    for i in application.handlers[0][1]:
        print i.regex.pattern, i.handler_class

    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

Continue

下载了tornado 0.1的代码进行学习,之所以选择tornado,是看网上对非阻塞模式评价很高,他也不仅仅是web框架,而且相对django来说代码少多了(django的代码也有看)。选择0.1的原因还是代码相对简单,差别也不大。 查看demos里的helloworld,代码如下:

import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import logging

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()
从options倒入options和define。先看看官方的描述: 命令行解析模块,来定义使用参数。 例如:上边代码中define("port", default=8888, help="run on the given port", type=int)这句,当年在终端输入helloworld.py --help的时候,将会有“--port                           run on the given port (default 8888)”的提示。 tornado.options.parse_command_line()这一行是对命令行进行解析,格式:("--myoption=myvalue")。也可以是配置文件解析tornado.options.parse_config_file("/etc/server.conf"),配置文件必须是python文件,像: myoption = "myvalue" myotheroption = "myothervalue" 参数指定类型type可以是:datetimes, timedeltas, ints, and floats。 主要说_Options()和_Option() 344行实例化了类_Options,options = _Options.instance(),options现在为空字典,以后将 存储所有选项,通过上边提到的三种方式,添加是只通过define()。 _Option的参数来自define,metavar选择类型的参数,multiple=True会解析多个值,结果列表保存。 这个类里有个有parse()方法,学到很多知识。主要用于解析命令行下的参数,需要将选项对应的类型进行转换,作者使用_parse={type:parse_option_func}.get(type,type)的方法,然后通过_parse(value)进行解析转换参数。没有定义过的解析方法就直接使用type(value)转换了,只能说作者好强大。 代码的最后定义了几个默认设置。0.1的里面有help和logging。 对于options模块里的log代码等下一次总结,剩下的几个函数就很简单了,就不记录了。

Continue