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

时间:2022-10-19 17:50:51

I'm calling a service from PHP using cURL, like this:

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

$response = curl_exec($ch);

and the request/response headers look something like this:

并且请求/响应标头看起来像这样:

Request:

请求:

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

Response:

响应:

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

followed by the body (json encoded data).

然后是身体(json编码数据)。

The problem is that the common thing is to split headers and body in the response by the first empty line encountered, except in this case, the empty line is after the 100 Continue and therefore everything else gets pushed into the body–and that is not valid json anymore :-)

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

So my question is this: What's the common way to deal with this? I have 3 options lined up:

所以我的问题是:解决这个问题的常用方法是什么?我排列了3个选项:

  1. Specify that curl should not expect 100-continue? (How?)
  2. 指定curl不应该期望100-continue? (怎么样?)
  3. Specify that curl should only send back the headers of the last response? (How?)
  4. 指定curl应该只发回最后一个响应的标题? (怎么样?)
  5. Manually check for 100 Continue headers and disregard them and their following empty line? (In that case, are there other similar things that could happen, that I should manually check for?)
  6. 手动检查100个继续标题并忽略它们及其后面的空行? (在这种情况下,还有其他可能发生的类似事情,我应该手动检查吗?)

Unless I'm missing something obvious, I'm sure people have stumbled upon this and solved it many times!

除非我遗漏了一些明显的东西,否则我肯定人们偶然发现了这个问题并多次解决了!

4 个解决方案

#1


18  

I will opt for #1. You can force curl to send empty "Expect" header, by adding:

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

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

to your code

你的代码

If you want check it manually, you should define your own header callback and maybe write callback (look for CURLOPT_HEADERFUNCTION and CURLOPT_WRITEFUNCTION in curl_setopt doc), which has simply to ignore all "HTTP/1.1 100 Continue" headers.

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

#2


5  

Here's another method that uses the approach I described in the comment by parsing the response into header vs. body using CURLINFO_HEADER_SIZE:

这是另一种方法,它使用我在评论中描述的方法,通过使用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==";

I've tested this, and it results in the following output:

我已对此进行了测试,结果如下:

==Sent Headers==
POST /curl_test.php HTTP/1.1
Host: test
Accept: */*
Content-Length: 242
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------
d86ac263ce1b

==End Sent Headers==

==Response Headers==
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Date: Fri, 06 Jul 2012 14:21:53 GMT
Server: Apache/2.4.2 (Win32) PHP/5.4.4
X-Powered-By: PHP/5.4.4
Content-Length: 112
Content-Type: text/plain

==End Response Headers==

==Response Body==
**FORM DATA**
array(2) {
  ["field1"]=>
  string(3) "foo"
  ["field2"]=>
  string(3) "bar"
}
**END FORM DATA**
==End Body==

#3


0  

I have come across this with 100s and 302s etc it's annoying but sometimes needed (gdata calls etc) so i would say leave curl returning all headers and extract the body a little differently.

我遇到了这个100s和302s等它很烦人但有时需要(gdata调用等)所以我会说让curl返回所有标题并提取身体有点不同。

I handle it like this (can't find my actual code but you'll get the idea):

我像这样处理它(找不到我的实际代码,但你会得到这个想法):

$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);

I begrudge the longwinded hackish method (would prefer it if curl marked the body content somehow) but it has worked well over the years. let us know how you get on.

我不喜欢那种长篇大论的hackish方法(如果卷曲以某种方式标记了身体内容,我会更喜欢它),但多年来它一直运作良好。让我们知道您的身体情况如何。

#4


0  

i had the same problem but this solution does note work for me, finaly i have found this methode and all its fine:

我有同样的问题,但这个解决方案确实为我工作,最终我找到了这个方法,一切都很好:

we have to prepare data post fields before sending them:

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

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
    ),
));}

you have to prepare two arrays: 1- post field with normal data: (name1 = val1, name2 = val2, ...) 2- post field with file data: (name_file 1, path_file1, name_file2 = path_file2, ..)

你必须准备两个数组:1- post field with normal data:(name1 = val1,name2 = val2,...)2- post field with file data:(name_file 1,path_file1,name_file2 = path_file2,..)

and finaly call this function before executing curl like this. $r = curl_custom_postfields($curl, $post, $postfields_files);

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

#1


18  

I will opt for #1. You can force curl to send empty "Expect" header, by adding:

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

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

to your code

你的代码

If you want check it manually, you should define your own header callback and maybe write callback (look for CURLOPT_HEADERFUNCTION and CURLOPT_WRITEFUNCTION in curl_setopt doc), which has simply to ignore all "HTTP/1.1 100 Continue" headers.

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

#2


5  

Here's another method that uses the approach I described in the comment by parsing the response into header vs. body using CURLINFO_HEADER_SIZE:

这是另一种方法,它使用我在评论中描述的方法,通过使用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==";

I've tested this, and it results in the following output:

我已对此进行了测试,结果如下:

==Sent Headers==
POST /curl_test.php HTTP/1.1
Host: test
Accept: */*
Content-Length: 242
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------
d86ac263ce1b

==End Sent Headers==

==Response Headers==
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Date: Fri, 06 Jul 2012 14:21:53 GMT
Server: Apache/2.4.2 (Win32) PHP/5.4.4
X-Powered-By: PHP/5.4.4
Content-Length: 112
Content-Type: text/plain

==End Response Headers==

==Response Body==
**FORM DATA**
array(2) {
  ["field1"]=>
  string(3) "foo"
  ["field2"]=>
  string(3) "bar"
}
**END FORM DATA**
==End Body==

#3


0  

I have come across this with 100s and 302s etc it's annoying but sometimes needed (gdata calls etc) so i would say leave curl returning all headers and extract the body a little differently.

我遇到了这个100s和302s等它很烦人但有时需要(gdata调用等)所以我会说让curl返回所有标题并提取身体有点不同。

I handle it like this (can't find my actual code but you'll get the idea):

我像这样处理它(找不到我的实际代码,但你会得到这个想法):

$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);

I begrudge the longwinded hackish method (would prefer it if curl marked the body content somehow) but it has worked well over the years. let us know how you get on.

我不喜欢那种长篇大论的hackish方法(如果卷曲以某种方式标记了身体内容,我会更喜欢它),但多年来它一直运作良好。让我们知道您的身体情况如何。

#4


0  

i had the same problem but this solution does note work for me, finaly i have found this methode and all its fine:

我有同样的问题,但这个解决方案确实为我工作,最终我找到了这个方法,一切都很好:

we have to prepare data post fields before sending them:

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

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
    ),
));}

you have to prepare two arrays: 1- post field with normal data: (name1 = val1, name2 = val2, ...) 2- post field with file data: (name_file 1, path_file1, name_file2 = path_file2, ..)

你必须准备两个数组:1- post field with normal data:(name1 = val1,name2 = val2,...)2- post field with file data:(name_file 1,path_file1,name_file2 = path_file2,..)

and finaly call this function before executing curl like this. $r = curl_custom_postfields($curl, $post, $postfields_files);

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