Django中如何正确处理被终止的HTTP请求

3
无论何时,当客户端(例如浏览器关闭)中断一个长时间的HTTP请求时,Django视图似乎会引发IOError异常。
如果我只想忽略这些中止的请求,该如何正确地检测它们?仅捕获IOError似乎太广泛了,可能会意外地忽略其他IO问题。
4个回答

2
在Django 1.3及以上版本中,您可以使用日志过滤器类(logging filter)来屏蔽您不感兴趣的异常。以下是我用来狭窄地屏蔽从_get_raw_post_data()引发的IOError异常的日志过滤器类:
import sys, traceback
class _SuppressUnreadablePost(object):
    def filter(self, record):
        _, exception, tb = sys.exc_info()
        if isinstance(exception, IOError):
            for _, _, function, _ in traceback.extract_tb(tb):
                if function == '_get_raw_post_data':
                    return False
        return True

在Django 1.4中,您将能够减少大部分复杂性并抑制新的异常类UnreadablePostError。(请参见此补丁。)

我需要在我的主函数中调用这个函数吗?如果是的话,需要传递哪些参数给这个函数? - Harshit verma

1
Raven现在连接到got_request_exception()信号以捕获未处理的异常,完全绕过日志系统,因此dlowe提出的解决方案不再适用。
但是,raven会查找异常实例上的skip_sentry属性,因此您可以使用中间件在要忽略的错误上设置它:
import sys
import traceback


class FilterPostErrorsMiddleware(object):
    """
    A middleware that prevents unreadable POST errors to reach Sentry.
    """

    def process_exception(self, request, exception):
        if isinstance(exception, IOError):
            tb = sys.exc_info()[2]
            for _, _, function, _ in traceback.extract_tb(tb):
                if function == '_get_raw_post_data':
                    exception.skip_sentry = True
                    break

注意:您必须使用最新版本的raven(例如1.8.4),因为早期版本错误地检查了异常类型上的skip_sentry属性,而不是实例。


1

最好的方法是使用自定义中间件类来实现 process_exception() 方法,如果捕获到 IOException,则返回一个自定义的HTTP响应,例如渲染 errors/request_aborted.html 模板。


但是如果抛出了其他IOError,而不是由于中止而导致的呢?那么我将会抑制该错误... - Assaf Lavie
这适用于未捕获的异常。通常在进行IO时,您会尝试捕获所有可能的异常,以确保仅有中止请求未被捕获。 - Filip Dupanović
但是很难确定IOError可能来自哪里。例如,我经常看到只是通过访问request.POST就会引发它。我需要一种方法来区分客户端断开连接和其他任何类型的IO错误,同时假设几乎任何框架函数都可能引发IOError。 - Assaf Lavie
如果您想要非常精确,您的中间件可以捕获异常并评估异常消息。 - Filip Dupanović
异常信息似乎没有包含任何可以将异常标识为连接终止异常的内容。我认为这也很脆弱,因为消息本身可能会在不同的机器之间略微改变(例如,取决于Django/Python文件所在的目录)。 - Assaf Lavie
请注意,中间件 process_exception() 仅限于处理源自视图代码的异常;如果异常源自其他中间件,则不会调用 process_exception()。 - dlowe

0

如果你想忽略 IOError,那就让它存在。你不需要捕获它。如果你非常必须要捕获它,你可以像 @Filip Dupanović 建议的那样,可能返回一个 django.http.HttpResponseServerError 来将响应代码设置为 500


1
我不想让它存在,因为我记录所有未捕获的错误,这会产生日志噪音。 - Assaf Lavie

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