Web.py 中针对所有 HTTP 方法有选择地隐藏资源并返回 404 状态码

56 阅读2分钟

在 Web.py 中,我们需要基于某种形式的身份验证有选择地隐藏某些资源,但是当我们使用尚未实现的任何 HTTP 方法时,这些资源的存在会被 405 响应所揭示。

例如,在下面的代码中,我们定义了一个名为 secret 的类,该类具有一个 GET 方法,用于检查密码并返回机密信息。如果密码不正确,则会引发 web.notfound() 异常。

import web

urls = (
    '/secret', 'secret',
)

app = web.application(urls, globals())

class secret():
    def GET(self):
        if web.cookies().get('password') == 'secretpassword':
            return "Dastardly secret plans..."
        raise web.notfound()

if __name__ == "__main__":
    app.run()

当我们尝试使用 DELETE 方法访问此资源时,会收到 405 状态码的响应,其中包含 Allow: GET 头字段,表明该资源只允许 GET 方法。

$ curl -v -X DELETE http://localhost:8080/secret
...
> DELETE /secret HTTP/1.1
...
< HTTP/1.1 405 Method Not Allowed
< Content-Type: text/html
< Allow: GET
...

2、解决方案

方法一:使用自定义的应用类

我们可以使用自定义的应用类来添加对默认方法的支持。在下面的代码中,我们扩展了 web.application 类,并添加了一个 _default 方法,该方法可以处理所有未定义的 HTTP 方法。

import types

class application(web.application):
    def _delegate(self, f, fvars, args=[]):
        def handle_class(cls):
            meth = web.ctx.method
            if meth == 'HEAD' and not hasattr(cls, meth):
                meth = 'GET'
            if not hasattr(cls, meth):
                if hasattr(cls, '_default'):
                    tocall = getattr(cls(), '_default')
                    return tocall(*args)
                raise web.nomethod(cls)
            tocall = getattr(cls(), meth)
            return tocall(*args)

        def is_class(o): return isinstance(o, (types.ClassType, type))
        ...

app = application(urls, globals())

class secret():
    def _default(self):
        raise web.notfound()

    def GET(self):
        ...

if __name__ == "__main__":
    app.run()

在上面的代码中,secret 类具有一个 _default 方法,该方法会引发 web.notfound() 异常,从而返回 404 状态码。这样,当我们使用任何未定义的 HTTP 方法访问该资源时,都会收到 404 状态码的响应。

方法二:使用元类

我们可以使用元类来实现处理所有 HTTP 方法的方法。在下面的代码中,我们定义了一个名为 HelloType 的元类,该元类会将 _handle_unknown 方法添加到类中,该方法将处理所有未定义的 HTTP 方法。

class HelloType(type):
    """Metaclass is needed to fool hasattr(cls, method) check"""
    def __getattribute__(obj, name):
        try:
            return object.__getattribute__(obj, name)
        except AttributeError:
            return object.__getattribute__(obj, '_handle_unknown')        

class hello(object):
    __metaclass__ = HelloType
    def GET(self, *args, **kw):
        if web.cookies().get('password') == 'secretpassword':
            return "Dastardly secret plans..."
        raise web.notfound()

    def _handle_unknown(self, *args, **kw):
        """This method will be called for all requests, which have no defined method"""
        raise web.notfound()

    def __getattribute__(obj, name):
        try:
            return object.__getattribute__(obj, name)
        except AttributeError:
            return object.__getattribute__(obj, '_handle_unknown')

在上面的代码中,hello 类具有一个 GET 方法,用于检查密码并返回机密信息。如果密码不正确,则会引发 web.notfound() 异常。_handle_unknown 方法将处理所有未定义的 HTTP 方法,并引发 web.notfound() 异常。