限制Pyramid上POST请求的最大大小

4
我正在使用Pyramid编写Web应用程序,并希望限制POST请求的最大长度,以防止人们发布大量数据并耗尽服务器上的所有内存。然而,我几乎到处查找(Pyramid、WebOb、Paster)都没有找到任何可实现此功能的选项。我看到Paster有HTTP头数、每个头的长度等限制,但我没有看到任何关于请求体大小的内容。
该服务器仅接受JSON-RPC的POST请求,因此我不需要允许大型请求体大小。在Pyramid堆栈中是否有一种方法可以实现这一点?
以防这一点不明显,必须接受并将整个请求体加载到内存中,然后检查长度并返回4xx错误代码的解决方案会破坏我所要做的事情,这不是我想要的。

现在为了测试,Web服务器是“paster serve”。然而,在其文档中我找不到任何关于它的信息。我也认为可能有一种方法可以在Pyramid WSGI处理程序的深处(如果WSGI以流模式使用)进行此操作,但我对这个想法并不确定。但我认为在paster(一个Web服务器)中应该有这方面的内容(和默认值),这就是为什么我很惊讶为什么找不到它。 - Christian Hudon
在paster文档中,我根本看不到任何关于那种配置选项的内容。如果他们有论坛或邮件列表,你可能需要在那里提问。否则,你可能需要深入源代码。 - agf
2个回答

2
并不是直接回答您的问题。据我所知,您可以创建一个WSGI应用程序,如果请求体小于配置设置,它将加载请求,并将其传递给下一个WSGI层。如果超过了设置,您可以停止读取并直接返回错误。
但说实话,我真的不明白在pyramid中这样做的意义。例如,如果您在nginx、apache或其他服务器后面运行pyramid,您总是可以通过前端服务器限制请求的大小。
除非您想直接使用Waitress或Paster运行pyramid而没有任何代理,否则您应该在前端服务器中处理请求体大小,这比Python更有效率。
编辑:
我进行了一些研究,这并不是一个完整的答案,但以下是我想到的一些方法。据我所知,您必须读取environ ['wsgi_input']。这是一个类似文件的对象,从nginx或apache等服务器接收数据块。
您真正需要做的是读取该文件,直到达到最大长度。如果达到了最大长度,请引发错误;如果没有,请继续请求。
你可能想看一下这个答案

你说得对。对于部署,我将在负载均衡代理服务器中处理这个问题。但是我有点惊讶在Paster中没有找到这样的功能。谢谢。 - Christian Hudon

1

你可以用多种方式来实现,这里有几个例子。一个是使用基于WebOb的WSGI中间件(在安装Pyramid时自动安装),另一个是使用Pyramid的event mechanism

"""
restricting execution based on request body size
"""
from pyramid.config import Configurator
from pyramid.view import view_config
from pyramid.events import NewRequest, subscriber
from webob import Response, Request
from webob.exc import HTTPBadRequest
import unittest


def restrict_body_middleware(app, max_size=0):
    """
    this is straight wsgi middleware and in this case only depends on
    webob. this can be used with any wsgi compliant web
    framework(which is pretty much all of them)
    """
    def m(environ, start_response):
        r = Request(environ)
        if r.content_length <= max_size:
            return r.get_response(app)(environ, start_response)
        else:
            err_body = """
            request content_length(%s) exceeds
            the configured maximum content_length allowed(%s)
            """ % (r.content_length, max_size)
            res = HTTPBadRequest(err_body)
            return res(environ, start_response)

    return m


def new_request_restrict(event):
    """
    pyramid event handler called whenever there is a new request
    recieved

    http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch/narr/events.html
    """
    request = event.request
    if request.content_length >= 0:
        raise HTTPBadRequest("too big")


@view_config()
def index(request):
    return Response("HI THERE")


def make_application():
    """
    make appplication with one view
    """
    config = Configurator()
    config.scan()
    return config.make_wsgi_app()


def make_application_with_event():
    """
    make application with one view and one event subsriber subscribed
    to NewRequest
    """
    config = Configurator()
    config.add_subscriber(new_request_restrict, NewRequest)
    return config.make_wsgi_app()


def make_application_with_middleware():
    """
    make application with one view wrapped in wsgi middleware
    """
    return restrict_body_middleware(make_application())



class TestWSGIApplication(unittest.TestCase):
    def testNoRestriction(self):
        app = make_application()
        request = Request.blank("/", body="i am a request with a body")
        self.assert_(request.content_length > 0, "content_length should be > 0")
        response = request.get_response(app)
        self.assert_(response.status_int == 200, "expected status code 200 got %s" % response.status_int)

    def testRestrictedByMiddleware(self):
        app = make_application_with_middleware()
        request = Request.blank("/", body="i am a request with a body")
        self.assert_(request.content_length > 0, "content_length should be > 0")
        response = request.get_response(app)
        self.assert_(response.status_int == 400, "expected status code 400 got %s" % response.status_int)

    def testRestrictedByEvent(self):
        app = make_application_with_event()
        request = Request.blank("/", body="i am a request with a body")
        self.assert_(request.content_length > 0, "content_length should be > 0")
        response = request.get_response(app)
        self.assert_(response.status_int == 400, "expected status code 400 got %s" % response.status_int)



if __name__ == "__main__":
    unittest.main()

1
谢谢,这太棒了!我一直想学习足够的WSGI知识来编写中间件等,而这是一个不错、易于阅读的例子。但有两个问题需要快速解答。首先,在WSGI、WebOb等中是否有确保内容长度标头始终设置的机制?(我的理解是HTTP客户端不一定总是会设置这个。)其次,在调用此WSGI中间件时,整个请求正文是否已经全部读入内存? - Christian Hudon
2
如果你想的话,你可以编写中间件来检查任何标头是否存在。我猜测如果没有发送,webob可能会返回None。关于你的第二个问题,我不确定它是否会将其读入内存。我认为这些底层细节应该由前端服务器(如apache或nginx)处理(这就是为什么我赞成Loic的答案的原因 :))。wsgi的工作是处理Web服务器和Python代码之间的通信,而webob的工作是表示并帮助您构建HTTP请求和响应。 - Tom Willis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接