码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2014-11-14 01:31:38      阅读:329      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   io   color   ar   os   sp   

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

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

先看下tornado的风格

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write(<html><body><form action="/" method="post">
                   <input type="text" name="message">
                   <input type="submit" value="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                     #实例过的handler
109                     handler = spec.handler_class(self, request,*args,**kwargs)
110                     if spec.regex.groups:
111                         # None-safe wrapper around url_unescape to handle
112                         # unmatched optional groups correctly
113                         def unquote(s):
114                             if s is None:
115                                 return s
116                             return escape.url_unescape(s, encoding=None,
117                                                        plus=False)
118                         # Pass matched groups to the handler.  Since
119                         # 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 can
122                         # 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                     break
131             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 = ErrorHandler
138                     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 every
144         # request so you don‘t need to restart to see changes
145         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的风格形式,并可以设置隐藏参数

标签:des   style   blog   http   io   color   ar   os   sp   

原文地址:http://www.cnblogs.com/TheViper/p/4093744.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!