首页 > 学院 > 开发设计 > 正文

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

2019-11-14 17:30:42
字体:
来源:转载
供稿:网友

转载请注明: TheViper http://www.VEVb.com/TheViper

什么是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="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类

class Application(object):        def __init__(self, handlers=None, default_host="", transforms=None,                 wsgi=False, **settings):        if transforms is None:            self.transforms = []            if settings.get("gzip"):                self.transforms.append(GZipContentEncoding)            self.transforms.append(ChunkedTransferEncoding)        else:            self.transforms = transforms        #保存配置handlers中处理的类,此时列表中的类还没实例化        self.handlers = []        self.named_handlers = {}        self.default_host = default_host        self.settings = settings        self.ui_modules = {'linkify': _linkify,                           'xsrf_form_html': _xsrf_form_html,                           'Template': TemplateModule,                           }        self.ui_methods = {}        self._wsgi = wsgi        self._load_ui_modules(settings.get("ui_modules", {}))        self._load_ui_methods(settings.get("ui_methods", {}))        if self.settings.get("static_path"):            path = self.settings["static_path"]            handlers = list(handlers or [])            static_url_PRefix = settings.get("static_url_prefix",                                             "/static/")            static_handler_class = settings.get("static_handler_class",                                                StaticFileHandler)            static_handler_args = settings.get("static_handler_args", {})            static_handler_args['path'] = path            for pattern in [re.escape(static_url_prefix) + r"(.*)",                            r"/(favicon/.ico)", r"/(robots/.txt)"]:                handlers.insert(0, (pattern, static_handler_class,                                    static_handler_args))        if handlers:            self.add_handlers(".*$", handlers)        if self.settings.get('debug'):            self.settings.setdefault('autoreload', True)            self.settings.setdefault('compiled_template_cache', False)            self.settings.setdefault('static_hash_cache', False)            self.settings.setdefault('serve_traceback', True)        # Automatically reload modified modules        if self.settings.get('autoreload') and not wsgi:            from tornado import autoreload            autoreload.start()    def listen(self, port, address="", **kwargs):        # import is here rather than top level because HTTPServer        # is not importable on appengine        #开启服务器监听        from tornado.httpserver import HTTPServer        server = HTTPServer(self, **kwargs)        server.listen(port, address)    def add_handlers(self, host_pattern, host_handlers):        if not host_pattern.endswith("$"):            host_pattern += "$"        handlers = []        # The handlers with the wildcard host_pattern are a special        # case - they're added in the constructor but should have lower        # precedence than the more-precise handlers added later.        # If a wildcard handler group exists, it should always be last        # in the list, so insert new groups just before it.        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))        for spec in host_handlers:            if isinstance(spec, (tuple, list)):                assert len(spec) in (2, 3, 4)                #创建映射url与handler的类,URLSpec类中有实例过的handler                spec = URLSpec(*spec)            #添加            handlers.append(spec)            if spec.name:                if spec.name in self.named_handlers:                    app_log.warning(                        "Multiple handlers named %s; replacing previous value",                        spec.name)                self.named_handlers[spec.name] = spec    def add_transform(self, transform_class):        self.transforms.append(transform_class)    def __call__(self, request):        """Called by HTTPServer to execute the request."""        #请求从这里进入        transforms = [t(request) for t in self.transforms]        handler = None        args = []        kwargs = {}        handlers = self._get_host_handlers(request)        if not handlers:            handler = RedirectHandler(                self, request, url="http://" + self.default_host + "/")        else:            #例子走这里            for spec in handlers:                #遍历,依次匹配                match = spec.regex.match(request.path)                #匹配成功                if match:                    #实例过的handler                    handler = spec.handler_class(self, request,*args,**kwargs)                    if spec.regex.groups:                        # None-safe wrapper around url_unescape to handle                        # unmatched optional groups correctly                        def unquote(s):                            if s is None:                                return s                            return escape.url_unescape(s, encoding=None,                                                       plus=False)                        # Pass matched groups to the handler.  Since                        # match.groups() includes both named and unnamed groups,                        # we want to use either groups or groupdict but not both.                        # Note that args are passed as bytes so the handler can                        # decide what encoding to use.                        if spec.regex.groupindex:                            kwargs = dict(                                (str(k), unquote(v))                                for (k, v) in match.groupdict().items())                        else:                            args = [unquote(s) for s in match.groups()]                    break            if not handler:                if self.settings.get('default_handler_class'):                    handler_class = self.settings['default_handler_class']                    handler_args = self.settings.get(                        'default_handler_args', {})                else:                    handler_class = ErrorHandler                    handler_args = dict(status_code=404)                #不会走这里                handler = handler_class(self, request, **handler_args)        # If template cache is disabled (usually in the debug mode),        # re-compile templates and reload static files on every        # request so you don't need to restart to see changes        if not self.settings.get("compiled_template_cache", True):            with RequestHandler._template_loader_lock:                for loader in RequestHandler._template_loaders.values():                    loader.reset()        if not self.settings.get('static_hash_cache', True):            StaticFileHandler.reset()        #准备开始执行类中的方法        if issubclass(spec.handler_class,tornado.websocket.WebSocketHandler):        #如果handler_class是websocket的子类,这里必须要这个,否则       #不能用websocket,因为websocket里面重写了_execute方法            handler._execute(transforms,*args, **kwargs)        else:            handler._execute(transforms,spec,*args, **kwargs)        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)

最后的效果类似于

可以看到,路由规则并不是完全的ruby on rail那种,还是要自己写需要指定映射的方法,框架只会帮你映射到类。

这样的好处就是保有一定的灵活性。比如,像一般的登陆页面,链接是'/login'就可以了,没必要非要弄成"user/login".

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

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

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

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

 


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表