未读取请求正文时的ERR_INCOMPLETE_CHUNKED_ENCODING

时间:2016-11-11 16:47:00

标签: django nginx safari wsgi

以下是我对一个有趣的错误的讨伐,虽然我已经解决了,但我不明白。我找到了一个解决方案,但我真的希望有人可以提供一些有关错误的实际原因的见解。

问题背景

此问题首先出现在我们的Django应用程序的生产服务器上。堆栈如下

  • AngularJS
  • nginx的
  • uWSGI
  • Django的
  • 的PostgreSQL

我们的团队在Safari上发现了一个POST请求的问题。 Safari会响应响应并吐出ERR_INCOMPLETE_CHUNKED_ENCODING。邮寄请求如下:

class ContractCloseOutSubmitView(APIView):
    def post(self, request, contract_id):
        contract = get_object_or_404(
            ContractCloseOut, pk=contract_id)
        if contract.submit():
            return Response({"detail": "Closed successfully."}, status=200)
        else:
            raise ParseError("Could not submit.")

请求是将对象标记为已关闭的简单请求。我们在网站的许多地方使用这种模式,所以这个问题显然需要引起注意。

解决方案

我们的第一个线索是请求 关闭了对象。也就是说,必须已到达行if contract.submit()。这会将问题缩小到响应范围。我做了一些阅读,这个错误有很多原因。我们试过了:

  • 在Django中明确设置内容长度
  • 一些黑客强迫nginx更可靠地判断内容长度
  • 以不同的方式重写代码

没有任何效果,问题仍未在当地发生。所以我们决定在本地复制整个堆栈并进行测试。一次一个,我们删除了网络层的元素,并确定删除nginx并直接与uWSGI交谈解决了这个问题。

所以现在我们确信问题在于nginx,但仍然没有解决方案。好吧,在Google的第三页深处,我遇到了一篇StackOverflow帖子,其中有一个简短的评论,提到阅读请求正文对nginx的缓冲区有什么影响以及如何它判断内容长度(这是我不太熟悉的东西)。无论如何,显然解决问题的方法是简单地读取缓冲区。也就是说,使用请求正文。所以我尝试通过将其值赋给变量来触摸请求体:

class ContractCloseOutSubmitView(APIView):
    def post(self, request, contract_id):

        data = request.data #touchie touchie

        contract = get_object_or_404(
            ContractCloseOut, pk=contract_id)
        if contract.submit():
            return Response({"detail": "Closed successfuly."}, status=200)
        else:
            raise ParseError("Could not submit.")

TL;博士

  • 收到对非常简单的电话的回复时遇到ERR_INCOMPLETE_CHUNKED_ENCODING
  • 问题仅发生在生产环境中的Safari中
  • 在调试时,我们一次删除了一个堆栈,并确定删除nginx可以解决问题
  • 请求是POST,但请求正文中没有任何信息,因此忽略了正文
  • 我发现对StackOverflow问题的评论提到正在阅读 POST请求的正文对nginx的请求缓冲区有某种影响
  • 简单地将请求主体分配给变量(从而读取缓冲区)解决了问题

毕竟,我只是想知道为什么会发生这种情况。什么是不读取可能导致此错误的请求正文?为什么只有nginx?为什么只有Safari?

我希望StackOverflow社区可以帮我解释一下这个!解决它很有趣。我还做了一个简短的presentation与一些同事分享。

1 个答案:

答案 0 :(得分:0)

您没有指定nginx的版本。如果它几个月没有更新,可能是这个封闭的问题:

https://trac.nginx.org/nginx/ticket/959

  

以前,流的窗口保持为零以防止a   客户在请求之前发送请求正文(请参阅   887cca40ba6a了解详情)。直到这样的初始窗口被确认   所有带数据的请求都被拒绝(详见0aa07850922f)。   这种方法揭示了一些问题:一些客户(特别是MS   IE / Edge,Safari,iOS应用程序)显示错误甚至崩溃   流被拒绝;这要求每个请求至少有一个RTT   在客户端收到窗口更新并能够发送之前使用body   数据。为了克服这些问题的新指令   " http2_body_preread_size"介绍。它设置了初始窗口   并配置一个特殊的每个流预读缓冲区,用于   在请求和处理正文之前保存所有传入的数据。

我的猜测是创建引用,在设置ack来回传递后保持缓冲区打开。

尝试升级nginx,因为此补丁已合并。如果你升级并仍然有问题,这是一个回归,我打开一个新的nginx票。