Tornado #4 支持restful风格API(利用猴子补丁

2018-06-11  本文已影响114人  平仄_pingze

文中所指的restful风格路由指:访问/user/123,可以进入/user路由,并获取id=123。
Tornado本身不支持,访问/user/123会返回404。

在项目中创建一个文件 monkeypatch.py:


import re
from tornado.routing import PathMatches
from tornado.web import RequestHandler, HTTPError
from tornado.util import basestring_type
from tornado.escape import url_unescape, _unicode

VALID_PARAM_TYPE = ['str', 'int', 'float']

def patch_route_restful():
    '''
    猴子补丁,目的是支持 GET /user/123 形式的restful风格路由
    :return:
    '''

    def __init__(self, path_pattern):
        if isinstance(path_pattern, basestring_type):
            self.param_type = None
            self.param_name = None
            if not path_pattern.endswith('$'):
                # MONKEYPATCH 支持<int:uid>形式结尾的匹配
                param_pattern = re.compile(r'<(.+?):(.+?)>$')
                if re.search(param_pattern, path_pattern):
                    self.param_type, self.param_name = re.search(param_pattern, path_pattern).groups()
                    if self.param_type not in VALID_PARAM_TYPE:
                        raise SyntaxError('Unsupport param type')
                    path_pattern = re.sub(param_pattern, r'(\w+)$', path_pattern)
                else:
                    path_pattern += '$'

            self.regex = re.compile(path_pattern)
        else:
            self.regex = path_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)

        self._path, self._group_count = self._find_groups()

    def _unquote_or_none(s):
        """None-safe wrapper around url_unescape to handle unmatched optional
        groups correctly.

        Note that args are passed as bytes so the handler can decide what
        encoding to use.
        """
        if s is None:
            return s
        return url_unescape(s, encoding=None, plus=False)

    def match(self, request):
        match = self.regex.match(request.path)

        if match is None:
            return None
        if not self.regex.groups:
            return {}

        path_args, path_kwargs = [], {}

        # 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.
        if self.regex.groupindex:
            path_kwargs = dict(
                (str(k), _unquote_or_none(v))
                for (k, v) in match.groupdict().items())
        else:
            # MONKEYPATCH 处理参数
            if match.groups() and match.groups()[0]:
                param_val = _unquote_or_none(match.groups()[0])
                try:
                    param_val = eval(self.param_type + '(' + param_val.decode('utf-8') + ')')
                except:
                    return None
                path_kwargs = {self.param_name: param_val}
            else:
                path_args = [_unquote_or_none(s) for s in match.groups()]

        return dict(path_args=path_args, path_kwargs=path_kwargs)

    def decode_argument(self, value, name=None):
        """Decodes an argument from the request.

        The argument has been percent-decoded and is now a byte string.
        By default, this method decodes the argument as utf-8 and returns
        a unicode string, but this may be overridden in subclasses.

        This method is used as a filter for both `get_argument()` and for
        values extracted from the url and passed to `get()`/`post()`/etc.

        The name of the argument is provided if known, but may be None
        (e.g. for unnamed groups in the url regex).
        """
        try:
            if type(value) != bytes:
                return value
            return _unicode(value)
        except UnicodeDecodeError:
            raise HTTPError(400, "Invalid unicode in %s: %r" %
                            (name or "url", value[:40]))

    PathMatches.__init__ = __init__
    PathMatches.match = match
    RequestHandler.decode_argument = decode_argument

再在初始化app前使用:

import tornado.web
from app.lib.monkeypatch import patch_route_restful

patch_route_restful()

def make_app():
    return tornado.web.Application(routes, db=db)

if __name__ == "__main__":
    app = make_app()
    # more code

这时,路由就支持restful风格了。

可以定义路由和handler如下:

import tornado.web

class UserHandler(tornado.web.RequestHandler):
    def get(self, uid):
        if uid:
            # 返回一个指定id的
        else:
            # 返回多个

routes = [(r'/user/<int:uid>', UserHandler)]
上一篇下一篇

猜你喜欢

热点阅读