【问题标题】:Reading POST data in PHP from cUrl从 cUrl 读取 PHP 中的 POST 数据
【发布时间】:2012-11-01 10:24:10
【问题描述】:

我在 PHP 中使用 cUrl 从一些外部服务请求。

有趣的是,服务器响应原始“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)

*/

编辑:我尝试将响应传送到 localhost HTTP 请求,但响应数据可能超出 PHP 进程中允许的内存大小。扩展内存限制不是很实用,这个动作也会大大降低服务器性能。

如果原始问题没有其他选择,您可以建议一种方法来处理非常大的 POST 请求以及 XML 解析,就 PHP 中的 streams 而言。

我知道这很难,请发表评论。我愿意讨论。

【问题讨论】:

  • 或许您可以使用 MIME 邮件解析库。见stackoverflow.com/questions/1238642/…
  • 这是一个纯form-data,不包含邮件头,但我会试一试。在实际尝试之前,我认为这非常接近解决方案。
  • 由于赏金将在农历新年结束之前结束,即在我有机会在办公室拿到代码之前,因此评论得到了评价。
  • 唯一明智的做法是通过本地请求将数据传送到另一个 PHP 脚本。如果您在 PHP 中解析数据,性能将会很糟糕。如果您以块的形式读取和分派传入的数据,则内存使用量不应太高。
  • @Andrew 恐怕代码太多了,这里把它简化成一个可以理解的问题。

标签: php curl http-post


【解决方案1】:

如果您需要响应中的 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

[编辑]

黑暗中的一枪…… 您可以使用这些 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);

[编辑二]

这只是一个尝试,如前所述,我无法让我的卷曲页面响应多部分表单数据。所以在这里对我温柔一点;)

$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 开头创建的临时文件。同样,我无法以任何方式测试上面的代码。所以这可能行不通(但它应该恕我直言;))

[编辑三]

假设我们希望 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

否则我们将不得不使用我们已知的 write 和 header 函数,只需一点点技巧

//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请求中得到的是multipart/form-data的格式,在响应中很少使用,将其作为文件发送会在下一个请求中直接将表单数据放入$_FILES并且 PHP 在解析有效负载时不会做任何事情。
  • 所以$_FILES$_POST 存储在同一个文件中,oakay。但是,当您使用 fopen 再次打开文件并从文件处理程序解析它时解析所述文件而不是 curl 响应时,它仍然应该防止超出 php 内存限制。您可能希望从响应中读取标题和正文大小并将其存储在一个变量中,以便您可以轻松地跳到响应文件的正确偏移量。如果我今天有时间,我会发布一个完整的例子。
  • 好吧..这很奇怪,我努力从 curl 请求中获取 multipart/form-data,但我做不到。即使我卷曲了upload-form.html 而不是表单动作指向的upload.php 也不行。很抱歉,您介意让我知道您的 CURLOPT_URL 网址以实现此目的吗?
  • 快速测试不太实用,因为我使用的是 ebay api,它需要一些设置才能工作。
  • 如果你不拆分响应和fputs它到一个socketconnection,它将充当一个普通的请求,它不会添加任何新的header信息也不会包装任何内容,它只是一个socketconnection.. .我会在上面解释(代码等..)
【解决方案2】:

很抱歉我帮不上什么忙,因为你没有放太多代码,但我记得我在玩 curl_setopt 选项时遇到了类似的问题。

您是否使用过 CURLOPT_BINARYTRANSFER

来自 php 文档 -> CURLOPT_BINARYTRANSFER-> TRUE 以在使用 CURLOPT_RETURNTRANSFER 时返回原始输出。

【讨论】:

  • 情况是我得到了原始多部分/表单数据格式的响应,通常只用于请求。我不希望它是原始的,但是在事情进入我的代码之前,apache 和 PHP 会执行某种解析机制。
【解决方案3】:

只需设置 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);

【讨论】:

    【解决方案4】:

    你可以重新组装你的二进制数据做这样的事情,我希望它有所帮助。

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

    【讨论】:

      【解决方案5】:

      如果你不需要二进制数据,你试过下面吗?

      curl_setopt($c, CURLOPT_NOBODY, true);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-18
        • 2012-04-16
        • 2016-02-07
        • 2020-12-11
        • 2015-09-30
        相关资源
        最近更新 更多