Чтение данных POST в PHP из cUrl
я использую cUrl в PHP для запроса от некоторой внешней службы.
достаточно интересно, сервер отвечает с raw "multipart / form-data" вместо двоичных данных файла.
мой сайт использует общий хостинг, поэтому PECL HTTP не является опцией.
есть ли способ проанализировать эти данные с помощью PHP?
пример кода:
$response = curl_exec($cUrl);
/* $response is raw "multipart/form-data" string
--MIMEBoundaryurn_uuid_DDF2A2C71485B8C94C135176149950475371
Content-Type: application/xop+xml; charset=utf-8; type="text/xml"
Content-Transfer-Encoding: binary
(xml data goes here)
--MIMEBoundaryurn_uuid_DDF2A2C71485B8C94C135176149950475371
Content-Type: application/zip
Content-Transfer-Encoding: binary
(binary file data goes here)
*/
EDIT: я попытался передать ответ на HTTP-запрос localhost, но данные ответа вероятно, превысит допустимый размер памяти в процессе PHP. Расходование лимита mem не очень практично,это действие также значительно снижает производительность сервера.
если нет альтернатив исходному вопросу, вы можете предложить способ обработки очень больших запросов POST вместе с синтаксическим анализом XML с точки зрения потоки в PHP.
Я знаю, это будет трудно, пожалуйста, прокомментируйте. Я открыт для обсуждения.
5 ответов
если вам нужен zip-файл из ответа, я думаю, вы могли бы просто написать файл tmp, чтобы сохранить ответ curl, и поток, который в качестве обходного пути: Никогда не пробовал это с составными завитками, но я думаю, что это должно сработать.
$fh = fopen('/tmp/foo', 'w');
$cUrl = curl_init('http://example.com/foo');
curl_setopt($cUrl, CURLOPT_FILE, $fh); // redirect output to filehandle
curl_exec($cUrl);
curl_close($cUrl);
fclose($fh); // close filehandle or the file will be corrupted
если вам не нужно ничего, кроме xml-части ответа, вы можете отключить заголовки
curl_setopt($cUrl, CURLOPT_HEADER, FALSE);
и добавить опцию, чтобы принимать только xml в качестве ответа
curl_setopt($cUrl, CURLOPT_HTTPHEADER, array('Accept: application/xml'));
//That's a workaround since there is no available curl option to do so but http allows that
[EDIT]
выстрел в темный... можете ли вы проверить с помощью этих настроек curlopt, чтобы увидеть, если изменение этих поможет что-нибудь
$headers = array (
'Content-Type: multipart/form-data; boundary=' . $boundary,
'Content-Length: ' . strlen($requestBody),
'X-EBAY-API-COMPATIBILITY-LEVEL: ' . $compatLevel, // API version
'X-EBAY-API-DEV-NAME: ' . $devID,
'X-EBAY-API-APP-NAME: ' . $appID,
'X-EBAY-API-CERT-NAME: ' . $certID,
'X-EBAY-API-CALL-NAME: ' . $verb,
'X-EBAY-API-SITEID: ' . $siteID,
);
$cUrl = curl_init();
curl_setopt($cUrl, CURLOPT_URL, $serverUrl);
curl_setopt($cUrl, CURLOPT_TIMEOUT, 30 );
curl_setopt($cUrl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($cUrl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($cUrl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($cUrl, CURLOPT_POST, 1);
curl_setopt($cUrl, CURLOPT_POSTFIELDS, $requestBody);
curl_setopt($cUrl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($cUrl, CURLOPT_FAILONERROR, 0 );
curl_setopt($cUrl, CURLOPT_FOLLOWLOCATION, 1 );
curl_setopt($cUrl, CURLOPT_HEADER, 0 );
curl_setopt($cUrl, CURLOPT_USERAGENT, 'ebatns;xmlstyle;1.0' );
curl_setopt($cUrl, CURLOPT_HTTP_VERSION, 1 ); // HTTP version must be 1.0
$response = curl_exec($cUrl);
if ( !$response ) {
print "curl error " . curl_errno($cUrl ) . PHP_EOL;
}
curl_close($cUrl);
[EDIT II]
это просто попытка, как уже упоминалось, я не могу получить мои свернутые страницы, чтобы ответить с данными составной формы. Так что будьте нежны со мной здесь ;)
$content_type = ""; //use last know content-type as a trigger
$tmp_cnt_file = "tmp/tmpfile";
$xml_response = ""; // this will hold the "usable" curl response
$hidx = 0; //header index.. counting the number of different headers received
function read_header($cUrl, $string)// this will be called once for every line of each header received
{
global $content_type, $hidx;
$length = strlen($string);
if (preg_match('/Content-Type:(.*)/', $string, $match))
{
$content_type = $match[1];
$hidx++;
}
/*
should set $content_type to 'application/xop+xml; charset=utf-8; type="text/xml"' for the first
and to 'application/zip' for the second response body
echo "Header: $string<br />\n";
*/
return $length;
}
function read_body($cUrl, $string)
{
global $content_header, $xml_response, $tmp_cnt_file, $hidx;
$length = strlen($string);
if(stripos ( $content_type , "xml") !== false)
$xml_response .= $string;
elseif(stripos ($content_type, "zip") !== false)
{
$handle = fopen($tmp_cnt_file."-".$hidx.".zip", "a");
fwrite($handle, $string);
fclose($handle);
}
/*
elseif {...} else{...}
depending on your needs
echo "Received $length bytes<br />\n";
*/
return $length;
}
и, конечно, установить правильные curlopts
// Set callback function for header
curl_setopt($cUrl, CURLOPT_HEADERFUNCTION, 'read_header');
// Set callback function for body
curl_setopt($cUrl, CURLOPT_WRITEFUNCTION, 'read_body');
не забудьте Не сохранять ответ curl на переменную из-за проблем с памятью, надеюсь, все, что вам нужно будет в $xml_response выше в любом случае.
//$response = curl_exec($cUrl);
curl_exec($cUrl);
и для разбора вашего кода Вы можете обратиться к $xml_response
и временные файлы, созданные вами, начиная с tmp/tmpfile-2
в этом случае. Опять же, я не смог проверить код выше каким-либо образом. Так что это может не сработать (но это должно имхо ;))
[EDIT III]
скажем, мы хотим, чтобы curl записывал все входящие данные непосредственно в другой (исходящий) поток, в этом случае соединение сокета
я не уверен, что это это так же просто, как это:
$fs = fsockopen($host, $port, $errno, $errstr);
$cUrl = curl_init('http://example.com/foo');
curl_setopt($cUrl, CURLOPT_FILE, $fs); // redirect output to sockethandle
curl_exec($cUrl);
curl_close($cUrl);
fclose($fs); // close handle
иначе нам придется использовать наши известные функции записи и заголовка с небольшим трюком
//first open the socket (before initiating curl)
$fs = fsockopen($host, $port, $errno, $errstr);
// now for the new callback function
function socket_pipe($cUrl, $string)
{
global $fs;
$length = strlen($string);
fputs($fs, $string); // add NOTHING to the received line just send it to $fs; that was easy wasn't it?
return $length;
}
// and of course for the CURLOPT part
// Set callback function for header
curl_setopt($cUrl, CURLOPT_HEADERFUNCTION, 'socket_pipe');
// Set the same callback function for body
curl_setopt($cUrl, CURLOPT_WRITEFUNCTION, 'socket_pipe');
// do not forget to
fclose($fs); //when we're done
дело в том, что не редактировать результат и просто трубить его в $fs
необходимо, чтобы apache прослушивал определенный порт, которому вы затем назначаете свой скрипт.
Или вам нужно будет добавить одну строку заголовка непосредственно после fsockopen
fputs($fp, "POST $path HTTP/1.0\n"); //where path is your script of course
извините, я не могу помочь, потому что вы не поставили много кода, но я помню, что у меня была аналогичная проблема, когда я играл с параметрами curl_setopt.
ты CURLOPT_BINARYTRANSFER?
из документации php ->CURLOPT_BINARYTRANSFER-> TRUE для возврата необработанного вывода при использовании CURLOPT_RETURNTRANSFER.
просто установите CURLOPT_RETURNTRANSFER CURLOPT_POST
$c = curl_init($url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
curl_setopt($c, CURLOPT_CONNECTTIMEOUT, 1);
curl_setopt($c, CURLOPT_TIMEOUT, 1);
curl_setopt($c, CURLOPT_POST, 1);
curl_setopt($c, CURLOPT_POSTFIELDS,
array());
$rst_str = curl_exec($c);
curl_close($c);
вы можете повторно собрать двоичные данные, делая что-то вроде этого, я надеюсь, что это поможет.
$file_array = explode("\n\r", $file, 2);
$header_array = explode("\n", $file_array[0]);
foreach($header_array as $header_value) {
$header_pieces = explode(':', $header_value);
if(count($header_pieces) == 2) {
$headers[$header_pieces[0]] = trim($header_pieces[1]);
}
}
header('Content-type: ' . $headers['Content-Type']);
header('Content-Disposition: ' . $headers['Content-Disposition']);
echo substr($file_array[1], 1);