PHP curl_exec возвращает как HTTP/1.1 100 Continue, так и HTTP/1.1 200 OK, разделенные пробелом
Я вызываю службу из PHP с помощью cURL, например:
$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 варианта:
- Укажите, что завиток не должен ожидать
100-continue
? (Как?)
- Укажите, что завиток должен отправлять только заголовки последнего ответа? (Как?)
- Вручную проверять заголовки
100 Continue
и игнорировать их и следующую пустую строку? (В этом случае, есть ли другие подобные вещи, которые могут произойти, что я должен проверить вручную?)
Если мне не хватает чего-то очевидного, я уверен, что люди наткнулись на это и решили его много раз!
Ответы
Ответ 1
Я выберу # 1.
Вы можете заставить curl посылать пустой заголовок "Expect", добавив:
curl_setopt($ch, CURLOPT_HTTPHEADER,array("Expect:"));
к вашему коду
Если вы хотите проверить его вручную, вы должны определить свой собственный обратный вызов заголовка и, возможно, написать обратный вызов (посмотрите CURLOPT_HEADERFUNCTION и CURLOPT_WRITEFUNCTION в curl_setopt doc), который просто игнорирует все заголовки "HTTP/1.1 100 Continue".
Ответ 2
Здесь другой метод, который использует описанный в комментарии подход, анализируя ответ в заголовке 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==";
Я тестировал это, и это приводит к следующему выводу:
==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
Я столкнулся с этим с 100 и 302 и т.д., это раздражает, но иногда необходимо (gdata calls и т.д.), поэтому я бы сказал, оставлять завиток, возвращая все заголовки и немного вытягивая тело.
Я обрабатываю это так (не могу найти мой фактический код, но вы получите идею):
$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);
Я жалуюсь на долговязый хакерский метод (предпочел бы, если бы скручивание означало содержание тела каким-то образом), но с годами он работал хорошо. сообщите нам, как вы это делаете.
Ответ 4
У меня была та же проблема, но это решение действительно замечает работу для меня, окончательно я нашел этот метод и все его прекрасное:
мы должны подготовить поля данных перед отправкой:
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- почтовое поле с нормальными данными: (name1 = val1, name2 = val2,...)
2- почтовое поле с файловыми данными: (name_file 1, path_file1, name_file2 = path_file2,..)
и окончательно вызовите эту функцию перед выполнением завитка как это.
$ r = curl_custom_postfields ($ curl, $post, $postfields_files);