【发布时间】:2017-03-25 22:53:38
【问题描述】:
以下是我对一个有趣的错误的讨伐记录,虽然我已经解决了,但我不明白。我找到了一个解决方案,但我真的希望有人能提供一些关于该错误的实际原因的见解。
问题背景
这个问题首先出现在我们的 Django 应用的生产服务器上。堆栈如下
- AngularJS
- nginx
- uWSGI
- 姜戈
- PostgreSQL
我们的团队发现 Safari 上的单个 POST 请求存在问题。 Safari 会阻塞响应并吐出ERR_INCOMPLETE_CHUNKED_ENCODING。 post请求如下:
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 中显式设置内容长度
- 一些 hack 迫使 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 与一些同事分享。
【问题讨论】: