首页 > 代码库 > 将tornado改成rails的风格形式,并可以设置隐藏参数

将tornado改成rails的风格形式,并可以设置隐藏参数

什么是rails的风格形式,就是所谓的约定优于配置。比如请求是user/login,则会去执行user类的login方法。

而隐藏参数就是比如请求是main/index/1/TheViper,配置是param_keys=(‘id‘, ‘name‘),那执行的时候会自动映射成{‘id‘:1,‘name‘:‘TheViper‘}

先看下tornado的风格

import tornado.ioloopimport tornado.webclass MainHandler(tornado.web.RequestHandler):    def get(self):        self.write(<html><body><form action="/" method="post">                   <input type="text" name="message">                   <input type="submit" value="http://www.mamicode.com/Submit">                   </form></body></html>)    def post(self):        self.set_header("Content-Type", "text/plain")        self.write("You wrote " + self.get_argument("message"))application = tornado.web.Application([    (r"/main", MainHandler),])if __name__ == "__main__":    application.listen(8888)    tornado.ioloop.IOLoop.instance().start()  

请求时/main时,如果请求方法是post,则执行MainHandler里面的post方法。。。。。

这样感觉用着很不习惯。就只有

"GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
"OPTIONS"

这几个方法,而且还不是由url体现出来的。

说下我的做法。由于是hack tornado的源码,所以有必要简单看下tornado的运行流程。

主要是在web.py里面。这里不讨论tornado是怎么实现一个高性能,非阻塞的 http 服务器,只简单说下他是怎么匹配映射然后执行的。

Application类

  1 class Application(object):  2       3     def __init__(self, handlers=None, default_host="", transforms=None,  4                  wsgi=False, **settings):  5         if transforms is None:  6             self.transforms = []  7             if settings.get("gzip"):  8                 self.transforms.append(GZipContentEncoding)  9             self.transforms.append(ChunkedTransferEncoding) 10         else: 11             self.transforms = transforms 12         #保存配置handlers中处理的类,此时列表中的类还没实例化 13         self.handlers = [] 14         self.named_handlers = {} 15         self.default_host = default_host 16         self.settings = settings 17         self.ui_modules = {linkify: _linkify, 18                            xsrf_form_html: _xsrf_form_html, 19                            Template: TemplateModule, 20                            } 21         self.ui_methods = {} 22         self._wsgi = wsgi 23         self._load_ui_modules(settings.get("ui_modules", {})) 24         self._load_ui_methods(settings.get("ui_methods", {})) 25         if self.settings.get("static_path"): 26             path = self.settings["static_path"] 27             handlers = list(handlers or []) 28             static_url_prefix = settings.get("static_url_prefix", 29                                              "/static/") 30             static_handler_class = settings.get("static_handler_class", 31                                                 StaticFileHandler) 32             static_handler_args = settings.get("static_handler_args", {}) 33             static_handler_args[path] = path 34             for pattern in [re.escape(static_url_prefix) + r"(.*)", 35                             r"/(favicon\.ico)", r"/(robots\.txt)"]: 36                 handlers.insert(0, (pattern, static_handler_class, 37                                     static_handler_args)) 38         if handlers: 39             self.add_handlers(".*$", handlers) 40  41         if self.settings.get(debug): 42             self.settings.setdefault(autoreload, True) 43             self.settings.setdefault(compiled_template_cache, False) 44             self.settings.setdefault(static_hash_cache, False) 45             self.settings.setdefault(serve_traceback, True) 46  47         # Automatically reload modified modules 48         if self.settings.get(autoreload) and not wsgi: 49             from tornado import autoreload 50             autoreload.start() 51  52     def listen(self, port, address="", **kwargs): 53         # import is here rather than top level because HTTPServer 54         # is not importable on appengine 55         #开启服务器监听 56         from tornado.httpserver import HTTPServer 57         server = HTTPServer(self, **kwargs) 58         server.listen(port, address) 59  60     def add_handlers(self, host_pattern, host_handlers): 61         if not host_pattern.endswith("$"): 62             host_pattern += "$" 63         handlers = [] 64         # The handlers with the wildcard host_pattern are a special 65         # case - they‘re added in the constructor but should have lower 66         # precedence than the more-precise handlers added later. 67         # If a wildcard handler group exists, it should always be last 68         # in the list, so insert new groups just before it. 69         if self.handlers and self.handlers[-1][0].pattern == .*$: 70             self.handlers.insert(-1, (re.compile(host_pattern), handlers)) 71         else: 72             self.handlers.append((re.compile(host_pattern), handlers)) 73         for spec in host_handlers: 74             if isinstance(spec, (tuple, list)): 75                 assert len(spec) in (2, 3, 4) 76                 #创建映射url与handler的类,URLSpec类中有实例过的handler 77                 spec = URLSpec(*spec) 78             #添加 79             handlers.append(spec) 80             if spec.name: 81                 if spec.name in self.named_handlers: 82                     app_log.warning( 83                         "Multiple handlers named %s; replacing previous value", 84                         spec.name) 85                 self.named_handlers[spec.name] = spec 86  87     def add_transform(self, transform_class): 88         self.transforms.append(transform_class) 89  90     def __call__(self, request): 91         """Called by HTTPServer to execute the request.""" 92         #请求从这里进入 93         transforms = [t(request) for t in self.transforms] 94         handler = None 95         args = [] 96         kwargs = {} 97         handlers = self._get_host_handlers(request) 98         if not handlers: 99             handler = RedirectHandler(100                 self, request, url="http://" + self.default_host + "/")101         else:102             #例子走这里103             for spec in handlers:104                 #遍历,依次匹配105                 match = spec.regex.match(request.path)106                 #匹配成功107                 if match:108                     #实例过的handler109                     handler = spec.handler_class(self, request,*args,**kwargs)110                     if spec.regex.groups:111                         # None-safe wrapper around url_unescape to handle112                         # unmatched optional groups correctly113                         def unquote(s):114                             if s is None:115                                 return s116                             return escape.url_unescape(s, encoding=None,117                                                        plus=False)118                         # Pass matched groups to the handler.  Since119                         # match.groups() includes both named and unnamed groups,120                         # we want to use either groups or groupdict but not both.121                         # Note that args are passed as bytes so the handler can122                         # decide what encoding to use.123 124                         if spec.regex.groupindex:125                             kwargs = dict(126                                 (str(k), unquote(v))127                                 for (k, v) in match.groupdict().items())128                         else:129                             args = [unquote(s) for s in match.groups()]130                     break131             if not handler:132                 if self.settings.get(default_handler_class):133                     handler_class = self.settings[default_handler_class]134                     handler_args = self.settings.get(135                         default_handler_args, {})136                 else:137                     handler_class = ErrorHandler138                     handler_args = dict(status_code=404)139                 #不会走这里140                 handler = handler_class(self, request, **handler_args)141 142         # If template cache is disabled (usually in the debug mode),143         # re-compile templates and reload static files on every144         # request so you don‘t need to restart to see changes145         if not self.settings.get("compiled_template_cache", True):146             with RequestHandler._template_loader_lock:147                 for loader in RequestHandler._template_loaders.values():148                     loader.reset()149         if not self.settings.get(static_hash_cache, True):150             StaticFileHandler.reset()151         #准备开始执行类中的方法152         handler._execute(transforms,spec, *args, **kwargs)153         return handler

__init__()里面就是保存设置,设置默认。注意里面有个add_handlers()。

进入add_handlers(),里面主要是对每个映射规则创建一个URLSpec类。这个类是专门保存映射规则和实例化handler类的,是hack的重点对象,这个后面会讲到。

然后就是__call__(self, request),这个可以理解为请求的入口,里面注释写的很详细了。

关于__call__和__init__ ,可以看下http://stackoverflow.com/questions/9663562/what-is-difference-between-init-and-call-in-python。

URLSpec类

class URLSpec(object):    """Specifies mappings between URLs and handlers."""    def __init__(self, pattern, handler, kwargs=None, name=None):        """Parameters:        * ``pattern``: Regular expression to be matched.  Any groups          in the regex will be passed in to the handler‘s get/post/etc          methods as arguments.        * ``handler_class``: `RequestHandler` subclass to be invoked.        * ``kwargs`` (optional): A dictionary of additional arguments          to be passed to the handler‘s constructor.        * ``name`` (optional): A name for this handler.  Used by          `Application.reverse_url`.        """        if not pattern.endswith($):            pattern += $        self.regex = re.compile(pattern)        assert len(self.regex.groupindex) in (0, self.regex.groups),             ("groups in url regexes must either be all named or all "             "positional: %r" % self.regex.pattern)        if isinstance(handler, str):            # import the Module and instantiate the class            # Must be a fully qualified name (module.ClassName)            #实例化handler类            handler = import_object(handler)        #保存action        self.action=None        #如果配置中设置了action        if type(kwargs) is dict and action in kwargs:            self.action=kwargs[action]        self.handler_class = handler        self.kwargs = kwargs or {}        self.name = name        self._path, self._group_count = self._find_groups()

回到__call__里面的最后handler._execute(transforms,spec, *args, **kwargs)。

这里我在_execute加了spec,为了在后面执行handler类中方法时用到保存在spec中的action.

_execute在RequestHandler类中

   def _execute(self, transforms,spec, *args, **kwargs):        """Executes this request with the given output transforms."""        self._transforms = transforms        try:            if self.request.method not in self.SUPPORTED_METHODS:                raise HTTPError(405)            self.path_args = [self.decode_argument(arg) for arg in args]            self.path_kwargs = dict((k, self.decode_argument(v, name=k))                                    for (k, v) in kwargs.items())            # If XSRF cookies are turned on, reject form submissions without            # the proper cookie            if self.request.method not in ("GET", "HEAD", "OPTIONS") and                     self.application.settings.get("xsrf_cookies"):                self.check_xsrf_cookie()            #设置当前的action,后面会用到            self.current_action=spec.action            #如果设置了隐藏参数            if param_keys in spec.kwargs:                #将隐藏参数和实际请求中的参数一一对应                self.params=dict(zip(spec.kwargs[param_keys],self.path_args));            self._when_complete(self.prepare(), self._execute_method)        except Exception as e:            self._handle_request_exception(e)    def _when_complete(self, result, callback):        try:            #不是长连接,走这里,执行下面的_execute_method(self)            if result is None:                callback()            elif isinstance(result, Future):                if result.done():                    if result.result() is not None:                        raise ValueError(Expected None, got %r % result.result())                    callback()                else:                    # Delayed import of IOLoop because it‘s not available                    # on app engine                    from tornado.ioloop import IOLoop                    IOLoop.current().add_future(                        result, functools.partial(self._when_complete,                                                  callback=callback))            else:                raise ValueError("Expected Future or None, got %r" % result)        except Exception as e:            self._handle_request_exception(e)    def _execute_method(self):        if not self._finished:            #默认的action是请求方法            method = getattr(self, self.request.method.lower())            if self.current_action:                #变成我的action                method = getattr(self, self.current_action)            #执行            self._when_complete(method(*self.path_args, **self.path_kwargs),                                self._execute_finish)

tornado的源码算是属于很少很少的那种了。把复杂问题变简单,这就是facebook工程师的境界。

最后附上例子 http://files.cnblogs.com/TheViper/python_rails_style.zip 基于tornado 3.2.2

有个问题需要注意下,我用的是sublime text3,它是基于python3.3的,电脑装的是python 2.7.运行的时候却必须是print()的写法才可以,否则报错。。不知道是什么原因。

有知道的朋友请告诉我一声。

将tornado改成rails的风格形式,并可以设置隐藏参数