PHP CURL如何處理上傳內存中文件,避免磁盤IO開銷

普通的CURL上傳磁盤文件的方式

發送方 send.php的代碼以下:php

<?php
$target_url = "http://localhost/upload.php";
$filename = realpath("test.txt");
/*
 * 第一種寫法,可是在5.5以上版本不推薦使用
 * @$filename 是文件路徑,必須有
 * filename=test.txt 是接收方收到的文件名,爲空時 則取 filename 文件路徑中的 basename部分
 * type=text/plain 文檔類型,能夠爲空
 */
/*
$post_data = array(
    'extra_info' => '123456',
    'file_contents' => "@$filename;filename=test.txt;type=text/plain",
);
*/
/*
 * 第二種寫法,推薦新版本php中使用
 * CURLFile參數解釋
 * @$filename 須要上傳的文件,建議使用絕對路徑
 * @$mimetype: 默認是 application/octet-stream,此處留空
 * @$postname: 接收方$_FILES數組中的文件名,此處爲 test.txt
 */
$file = new CURLFile($filename, '', 'test.txt');
$post_data = array(
    'extra_info' => '123456',
    'file_contents' => $file,
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$result = curl_exec($ch);
curl_close($ch);
echo $result;

處理上傳文件的代碼upload.php示例:html

<?php
$upload_dir = realpath('./') . '/';
$uploadfile = $upload_dir . basename($_FILES['file_contents']['name']);
echo '

<

pre>';
if(move_uploaded_file($_FILES['file_contents']['tmp_name'], $uploadfile)) {
    echo 'ok!';
} else {
    echo 'failed!';
}
//調試信息
var_dump($_FILES);
var_dump($_POST);

利用 PUT 方法上傳內存中的文件

有些時候腳本產生的臨時小文件,利用普通的上傳方式,則須要先把文件寫入磁盤,再做爲文件上傳。產生了額外的開銷。最好的辦法是直接上傳。
從新實現send.php 代碼以下:web

<?php 
/**
 * php://temp 會在內存量達到預約義的限制後(默認是 2MB)存入臨時文件中。
 */
$fh = fopen('php://temp', 'rw+');
$string = 'test';
fwrite($fh, $string);
rewind($fh);
$ch = curl_init('http://localhost/putfile.php');
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($ch, CURLOPT_PUT, true);
curl_setopt($ch, CURLOPT_INFILE, $fh);
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($string));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$result = curl_exec($ch);
curl_close($ch);
fclose($fh);

沒有用 php://memory,會報錯 Warning: curl_setopt(): cannot represent a stream of type MEMORY as a STDIO FILE* 暫無好的解決方案
處理上傳的文件的腳本也須要修改下:後端

<?php
//修改自PHP手冊中的代碼
$putdata = fopen("php://input", "r");

/* Open a file for writing */
$fp = fopen("myputfile.txt", "w");

/* Read the data 1 KB at a time
   and write to the file */
while ($data = fread($putdata, 1024))
  fwrite($fp, $data);

/* Close the streams */
fclose($fp);
fclose($putdata);

這個方法,適合上傳小於2MB的文件,不然仍是會生成臨時文件。固然該參數能夠經過php.ini修改數組

本身構造請求的主體 實現任意大小文件的直接內存上傳

經過CURL 上傳文件,無論是磁盤文件仍是內存中的字符串也好,其實都是基於HTTP協議的請求。
若是本身構造這段請求,便再也不侷限於文件的形式了。安全

<?php
/**
 * 參考rfc1867協議 第6部分的 examples 中的格式 : http://www.ietf.org/rfc/rfc1867.txt
 * 以下:第一行是header 中的(省略了其餘header),其餘部分是主體部分
Content-type: multipart/form-data, boundary=AaB03x

--AaB03x
content-disposition: form-data; name="field1"

Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain

 ... contents of file1.txt ...
--AaB03x--
 * 或
 Content-type: multipart/form-data, boundary=AaB03x

--AaB03x
content-disposition: form-data; name="field1"

Joe Blow
--AaB03x
content-disposition: form-data; name="pics"
Content-type: multipart/mixed, boundary=BbC04y

--BbC04y
Content-disposition: attachment; filename="file1.txt"
Content-Type: text/plain

... contents of file1.txt ...
--BbC04y
Content-disposition: attachment; filename="file2.gif"
Content-type: image/gif
Content-Transfer-Encoding: binary

...contents of file2.gif...
--BbC04y--
--AaB03x--
 * CURL POST,對 CURLOPT_POSTFIELDS 的設置,假若是字符串能夠解釋爲主體部分
 */
//生成分隔符
$delimiter = '-------------' . uniqid();
//須要上傳的文件數組
$fileFields = array(
    'file1' => array(
        'name' => 'test1.txt',
        'type' => 'text/plain',
        'content' => '...this is my file content...'
    ),
    'file2' => array(
        'name' => 'test.txt',
        'type' => 'text/plain',
        'content' => '... this is my two file'
    ),
);
//後端接受的$_POST的數組值
$postFields = array(
    'myname' => 'joe',
);
//@var $data 保存主體的字符串
$data = '';

//先將post的普通數據生成主體字符串
foreach ($postFields as $name => $content) {
    $data .= "--" . $delimiter . "\r\n";
    $data .= 'Content-Disposition: form-data; name="' . $name . '"';
    //multipart/form-data 不須要urlencode,參見 http:stackoverflow.com/questions/6603928/should-i-url-encode-post-data
    $data .= "\r\n\r\n" . $content . "\r\n";
}
//將上傳的文件生成主體字符串
foreach ($fileFields as $name => $file) {
    $data .= "--" . $delimiter . "\r\n";
    $data .= 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $file['name'] . "\" \r\n";
    $data .= 'Content-Type: ' . $file['type'] . "\r\n\r\n";//多了個文檔類型

    $data .= $file['content'] . "\r\n";
}
//主體結束的分隔符
$data .= "--" . $delimiter . "--";

$target_url = "http://localhost/upload.php";
$handle = curl_init($target_url);
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_HTTPHEADER , array(
    'Content-Type: multipart/form-data; boundary=' . $delimiter,
    'Content-Length: ' . strlen($data))
); 
curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($handle, CURLOPT_POSTFIELDS, $data);
$result = curl_exec($handle);
curl_close($handle);
//echo $result;

這種方式實現稍顯複雜,但不須要更改處理上傳的代碼,跟第一種磁盤文件的方法同樣。
其餘參考app

深刻淺出php下的文件上傳 提到了一些安全處理的技巧
簡述php中curl的使用curl

相關文章
相關標籤/搜索