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

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

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

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

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

先看下tornado的风格

importtornado.ioloopimporttornado.webclassMainHandler(tornado.web.RequestHandler):defget(self):

self.write('<html><body><form action="/" method="post">''<input type="text" name="message">''<input type="submit" value="Submit">''</form></body></html>')defpost(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类

classApplication(object):def__init__(self, handlers=None, default_host="", transforms=None,

wsgi=False, **settings):iftransformsisNone:

self.transforms=[]ifsettings.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", {}))ifself.settings.get("static_path"):

path= self.settings["static_path"]

handlers= list(handlersor[])

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'] =pathforpatternin[re.escape(static_url_prefix) + r"(.*)",

r"/(favicon\.ico)", r"/(robots\.txt)"]:

handlers.insert(0, (pattern, static_handler_class,

static_handler_args))ifhandlers:

self.add_handlers(".*$", handlers)ifself.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 modulesifself.settings.get('autoreload')andnotwsgi:fromtornadoimportautoreload

autoreload.start()deflisten(self, port, address="", **kwargs):#import is here rather than top level because HTTPServer#is not importable on appengine#开启服务器监听fromtornado.httpserverimportHTTPServer

server= HTTPServer(self, **kwargs)

server.listen(port, address)defadd_handlers(self, host_pattern, host_handlers):ifnothost_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.ifself.handlersandself.handlers[-1][0].pattern =='.*$':

self.handlers.insert(-1, (re.compile(host_pattern), handlers))else:

self.handlers.append((re.compile(host_pattern), handlers))forspecinhost_handlers:ifisinstance(spec, (tuple, list)):assertlen(spec)in(2, 3, 4)#创建映射url与handler的类,URLSpec类中有实例过的handlerspec = URLSpec(*spec)#添加handlers.append(spec)ifspec.name:ifspec.nameinself.named_handlers:

app_log.warning("Multiple handlers named %s; replacing previous value",

spec.name)

self.named_handlers[spec.name]=specdefadd_transform(self, transform_class):

self.transforms.append(transform_class)def__call__(self, request):"""Called by HTTPServer to execute the request."""#请求从这里进入transforms = [t(request)fortinself.transforms]

handler=None

args=[]

kwargs={}

handlers=self._get_host_handlers(request)ifnothandlers:

handler=RedirectHandler(

self, request, url="http://"+ self.default_host +"/")else:#例子走这里forspecinhandlers:#遍历,依次匹配match =spec.regex.match(request.path)#匹配成功ifmatch:#实例过的handlerhandler = spec.handler_class(self, request,*args,**kwargs)ifspec.regex.groups:#None-safe wrapper around url_unescape to handle#unmatched optional groups correctlydefunquote(s):ifsisNone:returnsreturnescape.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.ifspec.regex.groupindex:

kwargs=dict(

(str(k), unquote(v))for(k, v)inmatch.groupdict().items())else:

args= [unquote(s)forsinmatch.groups()]

breakifnothandler:ifself.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 changesifnotself.settings.get("compiled_template_cache", True):

with RequestHandler._template_loader_lock:forloaderinRequestHandler._template_loaders.values():

loader.reset()ifnotself.settings.get('static_hash_cache', True):

StaticFileHandler.reset()#准备开始执行类中的方法ifissubclass(spec.handler_class,tornado.websocket.WebSocketHandler):#如果handler_class是websocket的子类,这里必须要这个,否则 #不能用websocket,因为websocket里面重写了_execute方法handler._execute(transforms,*args, **kwargs)else:

handler._execute(transforms,spec,*args, **kwargs)returnhandler

__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类

classURLSpec(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`."""ifnotpattern.endswith('$'):

pattern+='$'self.regex=re.compile(pattern)assertlen(self.regex.groupindex)in(0, self.regex.groups), \

("groups in url regexes must either be all named or all""positional: %r"%self.regex.pattern)ifisinstance(handler, str):#import the Module and instantiate the class#Must be a fully qualified name (module.ClassName)#实例化handler类handler =import_object(handler)#保存actionself.action=None#如果配置中设置了actioniftype(kwargs)isdictand'action'inkwargs:

self.action=kwargs['action']self.handler_class =handler

self.kwargs= kwargsor{}

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=transformstry:ifself.request.methodnotinself.SUPPORTED_METHODS:raiseHTTPError(405)

self.path_args= [self.decode_argument(arg)forarginargs]

self.path_kwargs= dict((k, self.decode_argument(v, name=k))for(k, v)inkwargs.items())#If XSRF cookies are turned on, reject form submissions without#the proper cookieifself.request.methodnotin("GET","HEAD","OPTIONS")and\

self.application.settings.get("xsrf_cookies"):

self.check_xsrf_cookie()#设置当前的action,后面会用到self.current_action=spec.action#如果设置了隐藏参数if'param_keys'inspec.kwargs:#将隐藏参数和实际请求中的参数一一对应self.params=dict(zip(spec.kwargs['param_keys'],self.path_args));

self._when_complete(self.prepare(), self._execute_method)exceptException as e:

self._handle_request_exception(e)def_when_complete(self, result, callback):try:#不是长连接,走这里,执行下面的_execute_method(self)ifresultisNone:

callback()elifisinstance(result, Future):ifresult.done():ifresult.result()isnotNone:raiseValueError('Expected None, got %r'%result.result())

callback()else:#Delayed import of IOLoop because it's not available#on app enginefromtornado.ioloopimportIOLoop

IOLoop.current().add_future(

result, functools.partial(self._when_complete,

callback=callback))else:raiseValueError("Expected Future or None, got %r"%result)exceptException as e:

self._handle_request_exception(e)def_execute_method(self):ifnotself._finished:#默认的action是请求方法method =getattr(self, self.request.method.lower())ifself.current_action:#变成我的actionmethod =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.cnblogs.com/TheViper/python_rails_style.zip 基于tornado 3.2.2

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

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

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航