PHP curl_exec 返回 HTTP/1.1 100 Continue 和 HTTP/1.1 200 OK,以空格


PHP curl_exec returns both HTTP/1.1 100 Continue and HTTP/1.1 200 OK separated by space

我使用 cURL 从 PHP 调用服务,如下所示:

$response = curl_exec($ch);

请求/响应标头如下所示:

请求:

POST /item/save HTTP/1.1
Host: services.mydomain.com
Accept: */*
Content-Length: 429
Expect: 100-continue
Content-Type: multipart/form-data

响应:

HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Date: Fri, 06 Jul 2012 08:37:01 GMT
Server: Apache
Vary: Accept-Encoding,User-Agent
Content-Length: 256
Content-Type: application/json; charset=utf-8

后跟正文(JSON 编码的数据)。

问题是常见的是通过遇到的第一个空行在响应中拆分标头和正文,除了在这种情况下,空行在100 Continue之后,因此其他所有内容都被推入正文 - 这不再是有效的 json :-)

所以我的问题是:处理这个问题的常用方法是什么?我有 3 个选项:

  1. 指定卷曲不应预期100-continue ?(怎么?
  2. 指定 curl 应该只发回最后一个响应的标头?(怎么?
  3. 手动检查100 Continue标题并忽略它们及其以下空行?(在这种情况下,是否还有其他类似的事情可能发生,我应该手动检查?

除非我错过了一些明显的东西,否则我相信人们已经偶然发现了这个问题并解决了很多次!

我会选择#1。您可以通过添加以下内容来强制 curl 发送空的"预期"标头:

curl_setopt($ch, CURLOPT_HTTPHEADER,array("Expect:"));

到您的代码

如果你想手动检查它,你应该定义你自己的头回调,也许写回调(在curl_setopt文档中查找CURLOPT_HEADERFUNCTION和CURLOPT_WRITEFUNCTION),它必须简单地忽略所有"HTTP/1.1 100 Continue"头。

这是另一种使用我在评论中描述的方法,通过使用CURLINFO_HEADER_SIZE将响应解析为标头与正文:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://test/curl_test.php");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
// sets multipart/form-data content-type
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
  'field1' => 'foo',
  'field2' => 'bar'
));
$data = curl_exec($ch);
// if you want the headers sent by CURL
$sentHeaders = curl_getinfo($ch, CURLINFO_HEADER_OUT);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$header = substr($data, 0, $headerSize);
$body = substr($data, $headerSize);
echo "==Sent Headers=='n$sentHeaders'n==End Sent Headers=='n";
echo "==Response Headers=='n$headers'n==End Response Headers=='n";
echo "==Response Body=='n$body'n==End Body==";

我已经对此进行了测试,它会产生以下输出:

==
发送的标头==POST/curl_test.php HTTP/1.1主机:测试接受:*/*内容长度:242预期:100-续内容类型:多部分/表单数据;边界=----------------------------D86AC263CE1B==结束发送标头====响应标头==HTTP/1.1 100 继续HTTP/1.1 200 OK日期:2012 年 7 月 6 日星期五 14:21:53 GMT服务器: Apache/2.4.2 (Win32) PHP/5.4.4X-Powered-By: PHP/5.4.4内容长度:112内容类型:文本/纯文本==结束响应标头====响应正文==**表单数据**数组(2) {  ["字段 1"]=>  字符串(3) "呜"  ["字段 2"]=>  字符串(3) "酒吧"}**结束表单数据**==端体==

我遇到了同样的问题,但这个解决方案确实对我有用,最后我找到了这种方法,而且很好:

我们必须在发送数据发布字段之前准备它们:

function curl_custom_postfields($curl, array $assoc = array(), array $files = array()) {
/**
* For safe multipart POST request for PHP5.3 ~ PHP 5.4.
* @param resource $ch cURL resource
* @param array $assoc "name => value"
* @param array $files "name => path"
* @return bool
*/
// invalid characters for "name" and "filename"
static $disallow = array("'0", "'"", "'r", "'n");
// build normal parameters
foreach ($assoc as $key => $value) {
    $key = str_replace($disallow, "_", $key);
    $body[] = implode("'r'n", array(
        "Content-Disposition: form-data; name='"{$key}'"",
        "",
        filter_var($value), 
    ));
}
// build file parameters
foreach ($files as $key => $value) {
    switch (true) {
        case false === $value = realpath(filter_var($value)):
        case !is_file($value):
        case !is_readable($value):
            continue; // or return false, throw new InvalidArgumentException
    }
    $data = file_get_contents($value);
    $value = call_user_func("end", explode(DIRECTORY_SEPARATOR, $value));
    $key = str_replace($disallow, "_", $key);
    $value = str_replace($disallow, "_", $value);
    $body[] = implode("'r'n", array(
        "Content-Disposition: form-data; name='"{$key}'"; filename='"{$value}'"",
        "Content-Type: application/octet-stream",
        "",
        $data, 
    ));
}
// generate safe boundary 
do {
    $boundary = "---------------------" . md5(mt_rand() . microtime());
} while (preg_grep("/{$boundary}/", $body));
// add boundary for each parameters
array_walk($body, function (&$part) use ($boundary) {
    $part = "--{$boundary}'r'n{$part}";
});
// add final boundary
$body[] = "--{$boundary}--";
$body[] = "";
// set options
return @curl_setopt_array($curl, array(
    CURLOPT_POST       => true,
    CURLOPT_POSTFIELDS => implode("'r'n", $body),
    CURLOPT_HTTPHEADER => array(
        "Expect: 100-continue",
        "Content-Type: multipart/form-data; boundary={$boundary}", // change Content-Type
    ),
));}

您必须准备两个数组:1-具有正常数据的帖子字段:(名称1 = val1,名称2 = val2,...)2-带有文件数据的发布字段:(name_file 1,path_file1,name_file2 = path_file2,..)

并在像这样执行 curl 之前最终调用此函数。$r = curl_custom_postfields($curl, $post, $postfields_files);

我在 100 秒和 302 秒等中遇到了这个问题,这很烦人,但有时需要(gdata 调用等),所以我会说让 curl 返回所有标题并提取正文略有不同。

这样处理它(找不到我的实际代码,但你会明白的):

$response = curl_exec($ch);
$headers = array();
$body = array();
foreach(explode("'n'n", $response) as $frag){
  if(preg_match('/^HTTP'/[0-9'.]+ [0-9]+/', $frag)){
    $headers[] = $frag;
  }else{
    $body[] = $frag;
  }
}
echo implode("'n'n", $headers);
echo implode("'n'n", $body);

我嫉妒冗长的黑客方法(如果 curl 以某种方式标记正文内容,我会更喜欢它),但它多年来效果很好。 让我们知道您的进展。