文件上传是这个很普遍的问题,图片上传到网络相册、网盘上传文件、文章附带图片上传等都涉及到文件的上传。在写代码的时候各种编程语言都封装好了上传的操作,程序员操作起来也很简单,但是这样也就造成了很多人只懂操作,不知道原理。
前段时间搞小文件存储系统的时候,涉及到一个问题,就是关于文件上传的实现。我的提供了对外下载并存储的服务,意思就是接口接收一个外部图片的链接,比如:http://pic.5442.com/2013/0328/10/19.jpg 这个是别人网站的一个图片,如果它用了防盗链的话我就不能直接用,我需要的是将它下载下来存到我的图片server,然后返回一个我的server的链接给用户用于访问这个图片。由于我的接口接受的是这张图片的链接,所以我得先将它下载下来,然后放到我的server里面。如果是将文件直接存储在硬盘上的话,简单的php的move操作就行,但是我用了weedfs来管理这些小文件,而weedfs对外是一个http的文件上传接口,所以我就必须自己构造文件的上传,这就涉及到了自己来实现 《RFC1867》
我是用lua来写的这个小系统,所以我只实现了lua版本的,代码如下
文件名:multipart-post.lua local gen_boundary = function() local t = {"BOUNDARY-"} for i=2,17 do t[i] = string.char(math.random(65, 90)) end t[18] = "-BOUNDARY" return table.concat(t) end local encode = function(t, boundary) local str = "--" .. boundary .. "\r\n" str = str .. 'Content-Disposition: form-data; name="' .. t["name"] .. '"; filename="' .. t["name"] .. '"\r\n'; str = str .. "Content-Type: image/png\r\n\r\n" str = str .. t["data"] .. "\r\n" str = str .. "--" .. boundary .. "\r\n" return str end local gen_request = function(t) local boundary = gen_boundary() local s = encode(t, boundary) source = "POST /"..t["fid"].." HTTP/1.0\r\n" source = source .. "Content-Type: multipart/form-data; boundary="..boundary.."\r\n" source = source .. "Content-length:"..#s.."\r\n\r\n" return source .. s end return { encode = encode, gen_request = gen_request, } 调用代码 -- 上传 local gen = (require "multipart-post").gen_request local str = gen({fid = result['fid'], name = image_url_md5 .. ".jpg", data = image["body"]}) ... local bytes, err = sock:send(str)
这个是一个lua版本的实现,这里看到在header里面添加了一个 Content-Type: multipart/form-data; boundary=”..boundary..”\r\n,”multipart/form-data”是POST情况下,上传文件的一个Content-type,而boundary是上传的内容的一个分界符
下面贴一个别人的PHP版本的实现
/*** * RFC http://www.ietf.org/rfc/rfc1867.txt implement for PHP * * HTML Form表单上传multipart/form-data的PHP实现 * @author misko_lee * @version 0.1.0 * @email imiskolee@gmial.com */ class FormUpload { static $MIME_JPEG = 'image/jpeg'; static $MIME_PNG = 'image/png'; static $MIME_BMP ='image/bmp'; static $MIME_TEXT = 'text/plan'; static $MIME_HTML = 'text/html'; private $boundary = ''; private $payload = ''; private $url = ''; private $curl = null; public function __construct($url = ''){ $this->init(); $this->url = $url; } public function init(){ $this->boundary = $this->genBoundary(); $this->payload = ''; $this->curl = curl_init(); } public function setCUrlOpt($opt,$val){ curl_setopt($this->curl,$opt,$val); } public function addPart($name,$value = '',$mimeType = '',$fileName=''){ $line = ''; if(!$mimeType){ $line =sprintf("%s\nContent-Disposition: form-data; name=\"%s\"\n\n%s\n", $this->boundary, $name, $value ); }else{ if(!$fileName){ $fileName = rand(1000,9999).rand(1000,9999).rand(1000,9999); } $line =sprintf("%s\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\nContent-Type: %s\n\n%s\n", $this->boundary, $name, $fileName, $mimeType, $value ); } $this->payload .= $line; } public function getHeader(){ return 'Content-type:multipart/form-data; boundary='.substr($this->boundary,2,strlen($this->boundary)); } private function genBoundary(){ return '------CURL_FORM_UPLOAD_BOUNDARY'.rand(1000,9999).rand(1000,9999); } public function getPayload(){ return $this->payload.$this->boundary."\n\r"; } public function submit(){ $headers = array( $this->getHeader() ); curl_setopt($this->curl,CURLOPT_HTTPHEADER,$headers); curl_setopt($this->curl,CURLOPT_POST,1); curl_setopt($this->curl,CURLOPT_POSTFIELDS,$this->getPayload()); curl_setopt($this->curl,CURLOPT_URL,$this->url); curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, TRUE); return curl_exec($this->curl); } }
参考资料