RFC1867文件上传的实现

文件上传是这个很普遍的问题,图片上传到网络相册、网盘上传文件、文章附带图片上传等都涉及到文件的上传。在写代码的时候各种编程语言都封装好了上传的操作,程序员操作起来也很简单,但是这样也就造成了很多人只懂操作,不知道原理。

前段时间搞小文件存储系统的时候,涉及到一个问题,就是关于文件上传的实现。我的提供了对外下载并存储的服务,意思就是接口接收一个外部图片的链接,比如: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 .. "rn"
  str = str .. 'Content-Disposition: form-data; name="' .. t["name"] .. '"; filename="' .. t["name"] .. '"rn';
  str = str .. "Content-Type: image/pngrnrn"
  str = str .. t["data"] .. "rn"
  str = str .. "--" .. boundary .. "rn"

  return str
end

local gen_request = function(t)
  local boundary = gen_boundary()
  local s = encode(t, boundary)

  source = "POST /"..t["fid"].." HTTP/1.0rn"
  source = source .. "Content-Type: multipart/form-data; boundary="..boundary.."rn"
  source = source .. "Content-length:"..#s.."rnrn"

  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..”rn,”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("%snContent-Disposition: form-data; name="%s"nn%sn",
                $this->boundary,
                $name,
                $value
            );
        }else{
            if(!$fileName){
                $fileName = rand(1000,9999).rand(1000,9999).rand(1000,9999);
            }
            $line =sprintf("%snContent-Disposition: form-data; name="%s"; filename="%s"nContent-Type: %snn%sn",
                $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."nr";
    }

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

参考资料

HTML中基于表单的文件上传 模拟HTML表单上传文件(RFC 1867)

文章来源:

Author:花生
link:http://wenjun.org/?p=1155