首页 > 代码库 > 将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的风格形式,并可以设置隐藏参数