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

什么是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()的写法才可以,否则报错。。不知道是什么原因。

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

时间: 2024-10-22 03:02:52

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

windows 7 Alt+Tab 的风格改成 XP 风格

1.开始菜单-运行-输入"regedit". 2.找到这个位置"[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]". 3.在右边的窗口单击右键,新建一个"DWORD(32-位)值". 4.把新建的这个值的名字改成"AltTabSettings",然后双击它,将其"数值数据"改成 1 . 原文地址:https://ww

织梦栏目列表目录形式改成链接形式默认页

改变后的最终效果: 第一步 修改栏目保存目录和默认页 第二步 打开 /include/helpers/channelunit.helper.php 找到 大概在251行 1 if($cfg_typedir_df=='N' || $isdefault==0) $reurl = $typedir.'/'.$defaultname; 改成 1 if($defaultname != 'index.html' || $cfg_typedir_df=='N' || $isdefault==0) $reurl

快速又改动少的将窗口改成融合窗口

假设您要将模式窗口为主的桌面程序改成融合窗口,原子窗口为TForm类型窗口.用到的组件:Raize 5.x (不要问我为什么不用TFrame)目前流行用Raize的RzPageControl来做融合窗口,因为它自带TabSheet的关闭按钮和事件,所以大家都用这个了.我的实现方法是放一个RzTabSheet当首页,上面显示一些通知通告.待办事项提醒的项目,然后所有子模块的窗口直接动态创建到RzPageControl里去,创建代码如下: procedure TfrmMain.N13Click(Se

【Android端】代码打包成jar包/aar形式

Android端代码打包成jar包和aar形式: 首先,jar包的形式和aar形式有什么区别? 1.打包之后生成的文件地址: *.jar:库/build/intermediates/bundles/debug(release)/classes.jar *.aar:库/build/outputs/aar/libraryname.aar 区别:jar包只包含了classes文件,不包含资源文件:aar不仅包含了classes文件,还包含资源文件 并且,aar的这个可以发布到maven库,然后使用者直

kafka-connect-hdfs连接hadoop hdfs时候,竟然是单点的,太可怕了。。。果断改成HA

2017-08-16 11:57:28,237 WARN [org.apache.hadoop.hdfs.LeaseRenewer][458] - <Failed to renew lease for [DFSClient_NONMAPREDUCE_-1756242047_26] for 30 seconds. Will retry shortly ...> org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.ipc.StandbyE

Oracle中表列由VARCHAR2类型改成CLOB

情景 原来表中的列定义成VARCHAR2类型,众所周知,VARCHAR2类型最大支持长度为4000.假设因为业务须要.想把此列转换为CLOB类型,在Oracle中直接通过ALTER语句转换是行不通的.以下依据详细事例解说在Oracle数据库中怎样把表列由VARCHAR2类型转换为CLOB类型. 演示样例准备 1. 新建两张张表TB_WITHOUT_DATA(此VARCHAR2列不包括数据)和TB_WITH_DATA(此Varchar2列包括数据) create table TB_WITHOUT_

centos7/redhat7 将网卡名字改成eth样式的方法

centos7/redhat7 将网卡名字改成eth样式的方法 方法/步骤 1 编辑 /etc/sysconfig/grub 找到“GRUB_CMDLINE_LINUX”这一行 2 在rhgb前面,添加net.ifnames=0 biosdevname=0 3 在打开的终端上然后执行 grub2-mkconfig -o /boot/grub2/grub.cfg 4 用命令切换到/etc/sysconfig/network-scripts/网卡目录下,如图所示 5 我这里是有两个网卡信息,将ifc

把Ubuntu用户目录下的目录名改成英文

直接改名字是不行的,一重启就回去了 方法一: 把中文文件夹改成相应的英文文件夹,再修改配置文件 ~/.config/user-dirs.dirs XDG_DESKTOP_DIR="$HOME/Desktop" XDG_DOWNLOAD_DIR="$HOME/Downloads" XDG_TEMPLATES_DIR="$HOME/Templates" XDG_PUBLICSHARE_DIR="$HOME/Public" XDG_

完美解决Android使用Zxing扫描二维码改成竖屏后,后面的预览画面出现了拉伸,扭曲的情况

完美解决解决Android使用Zxing扫描二维码改成竖屏后,后面的预览画面出现了拉伸,扭曲的情况 第一步:找到com.zxing.camera包下的CameraConfigurationManager.java文件中的void initFromCameraParameters(Camera camera)方法 第二步:在 Log.d(TAG, "Screen resolution: " + screenResolution);后加上如下的代码 Point screenResoluti