26

我正在使用 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. 指定 curl 不应该期望100-continue?(如何?)
  2. 指定 curl 应该只发回最后一个响应的标题?(如何?)
  3. 手动检查100 Continue标题并忽略它们及其后面的空行?(在这种情况下,是否还有其他类似的事情可能发生,我应该手动检查?)

除非我遗漏了一些明显的东西,否则我相信人们已经偶然发现并解决了很多次!

4

4 回答 4

20

我会选择#1。您可以通过添加以下内容强制 curl 发送空的“Expect”标头:

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

到你的代码

如果您想手动检查它,您应该定义自己的标头回调并且可能编写回调(在curl_setopt doc中查找 CURLOPT_HEADERFUNCTION 和 CURLOPT_WRITEFUNCTION ),它只需忽略所有“HTTP/1.1 100 Continue”标头。

于 2012-07-06T09:17:32.160 回答
7

这是另一种方法,它使用我在评论中描述的方法,将响应解析为 header vs. body 使用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==";

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

==发送标头==
发布 /curl_test.php HTTP/1.1
主持人:测试
接受: */*
内容长度:242
期望:100-继续
内容类型:multipart/form-data;边界=----------------------------
d86ac263ce1b

==结束发送的标头==

==响应标头==
HTTP/1.1 100 继续

HTTP/1.1 200 正常
日期:格林威治标准时间 2012 年 7 月 6 日星期五 14:21:53
服务器:Apache/2.4.2 (Win32) PHP/5.4.4
X-Powered-By: PHP/5.4.4
内容长度:112
内容类型:文本/纯文本

==结束响应头==

==响应正文==
**形成数据**
数组(2){
  [“字段1”]=>
  字符串(3)“富”
  [“字段2”]=>
  字符串(3)“酒吧”
}
**结束表格数据**
==结束身体==
于 2012-07-06T14:23:48.573 回答
0

我遇到过 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);

我讨厌这种冗长的 hackish 方法(如果 curl 以某种方式标记了正文内容,我会更喜欢它),但它多年来一直运作良好。让我们知道您的身体情况如何。

于 2012-07-06T09:24:04.027 回答
0

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

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

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- 带有普通数据的 post 字段:(name1 = val1, name2 = val2, ...) 2- 带有文件数据的 post 字段:(name_file 1, path_file1, name_file2 = path_file2, ..)

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

于 2016-06-04T18:24:52.450 回答