对具有分块编码的PHP7的POST请求未正确返回结果


POST request to PHP7 with chunked encoding does not properly return result

我从客户端发送POST请求(使用curl和自定义nodejs脚本测试),但没有正确返回响应。整个过程在PHP 5.6中运行良好。

环境

整个事情尽可能减少:

  • 在Vagrant VM Ubuntu 14.04 LTS中运行的所有内容
  • nginx 1.9.7来自http://nginx.org/packages/ubuntu/
  • 使用--disable-all --enable-fpm从官方来源编译的PHP7 FPM

我使用的最小nginx站点配置:

server {
  listen 80;
  server_name localhost;
  location / {
    fastcgi_param REQUEST_METHOD $request_method;
    fastcgi_pass  unix:/var/run/php/php7.0-fpm-api.sock;
    fastcgi_param SCRIPT_FILENAME /vagrant/index.php;
  }
}

来自/vagrant/index.php:的示例PHP脚本

<?php
echo str_repeat('.', 512);
flush(); // not necessary, only due testing

我正在使用的卷曲调用:curl -XPOST http://localhost/ -H "Transfer-Encoding: chunked" -d ''

我正在使用的NodeJS脚本:

'use strict';
var http = require('http');
var url = require('url');
var uri = url.parse(process.env.URL);
var options = {
  method: 'POST', protocol: uri.protocol, hostname: uri.hostname,
  port: uri.port, path: uri.path,
};
var data = '';
var httpRequest = http.request(options, function(res) {
    res.on('data', function(chunk) {
      console.log('received data', chunk.length);
      data += chunk;
    });
    res.on('end', function() { console.log('final size', data.length); });
  })
  .on('error', function(err) { console.log(err); });
httpRequest.write('');
httpRequest.end();

将我的测试请求发送到PHP 5.6

$ curl http://localhost/
..........[cut off]
$ curl -XPOST http://localhost/ -H "Transfer-Encoding: chunked" -d ''
..........[cut off]
$ URL=http://localhost/ node php7test.js
received data 512
final size 512

将我的测试请求发送到PHP 7.0

$ curl http://localhost/
..........[cut off]
$ URL=http://localhost/ node php7test.js
final size 0
$ curl -XPOST http://localhost/ -H "Transfer-Encoding: chunked" -d ''
curl: (18) transfer closed with outstanding read data remaining

我为什么要摆弄分块编码

没有商业理由这样做,但我使用了一个非常相似的NodeJS代码,它默认为分块编码,当切换到PHP7时突然停止工作。

我发现在nodejs端可以执行以下操作:显式设置Content-Length标头将删除nodejs发送的隐式Transfer-Encoding: chunked标头,因此可以同时使用这两个PHP版本。

然而,我想了解为什么PHP7在这里表现不同,以及我是否犯了错误或这里到底发生了什么。

更新1:

  • 我比较了5.6和7.0之间的sapi/fpm/源代码,除了PHP内部的变化之外,几乎没有什么区别
  • 内置服务器(php -S)不受影响,所有测试

更新2:

我将PHP源代码一分为二,并能够确定行为何时发生变化:

  • 我能够编译的最后一个提交是有效的:https://github.com/php/php-src/commit/16265a59ac6bd433bfb636e4e44da1ad57cdcda9
  • 第一次提交时,我能够再次编译,但没有成功:https://github.com/php/php-src/commit/86de98cabada88f4667839794c176ea37648498b

在两者之间,git bisect的输出,我无法编译的提交:

$ git bisect skip
There are only 'skip'ped commits left to test.
The first bad commit could be any of:
ba5ecf355fe792a5a2a8e6582d5e081d02b16fbf
e383cb4493031a7cd952cfcaed3297e583149c07
fef18f4bea1980a59a9283c2197bd090aaf500cb
18cf4e0a8a574034f60f4d123407c173e57e54ec
We cannot bisect more!

有一种感觉,这可能是一个bug,我把这封信写给了内部人员,也许他们有一些见解:https://marc.info/?l=php-内部构件&m=145090900217798&w=2

从你的帖子中,我猜你正在使用PHP7.0。如果我的猜测是(bool)TRUE,那么我建议你迁移到PHP7.0.1

PHP7.0.0大约有27个错误,这些错误在PHP7.0.1中被压扁;以及其他固定项目
来源:PHP.net变更日志。


我也看了上面的PHP代码(用我的谷歌眼镜),但它简单得离谱。我怀疑它是否有任何问题。尽管我猜测它与PHP7处理flush()和输出的方式有关。


此外:

注意您的更新(特别是更新1升级2);我真的要表扬你非常令人印象深刻。请务必查看这个已修复的错误#61751。如果我对你的PHP版本是正确的,那么这个修复的错误可能已经解决了你的问题;您只需要迁移到PHP7.0.1.



注意: 我知道我应该检查修复错误的内部,但

这是PHP7中的一个错误,最近用https://github.com/php/php-src/pull/1745。它还没有正式发布,但最终会在一段时间内发布。

在上述PR之前,还报告了一个描述类似问题的错误:https://bugs.php.net/bug.php?id=71466