利用PUT方式上傳檔案的方法研究

大江小浪發表於2010-05-31

雖然沒有POST方法使用廣泛,但是PUT方法卻是向伺服器上傳檔案最有效率的方法。POST上傳檔案時,我們通常需要將所有的資訊組合成 multipart 傳送過去,然後伺服器再解碼這些資訊,解碼過程則必不可少的會消耗記憶體和CPU資源,這種現象在上傳大檔案時尤其明顯。而PUT方法則允許你通過與伺服器建立的socket連結傳遞檔案的內容,而不附帶其他的資訊。

最近一個專案上需要利用這種方式來進行檔案的上傳,下面介紹一下在 Apache + PHP 的環境下如何進行PUT方式的檔案上傳。

Apache 有一個模組 mod_actions,先看看官方的說明:

This module has two directives. The Action directive lets you run CGI scripts whenever a file of a certain MIME content type is requested. The Script directive lets you run CGI scripts whenever a particular method is used in a request. This makes it much easier to execute scripts that process files.

也就是說,這個模組可以指定對於特定 MIME 型別的檔案處理,或者對於特定指令碼的請求進行指定的處理。我用到的就是 Script 這個選項。

在Apache 配置檔案的 Directory 中指定

Script PUT /receive.php

這個含義就是,對於所有對伺服器的PUT請求,都交給根目錄下的 receive.php 去處理,當然我們也可以選擇 perl 或者其他的CGI指令碼來進行處理。

接下來就是這個 receive.php 指令碼的編寫了,他的主要任務就是將請求的檔案寫到指定的位置

<?php
/**
* Process The PUT File, receive and move a file to corresponsed location
* Created by shiqiang<cocowool@gmail.com> at 2010-05-24
*
**/

class Receive {
    var $default_log_file = “logs/error.log”;
    var $default_server_info = “logs/server.log”;
    var $default_header_info = “logs/header.log”;
    var $default_prefix = “/data1/vhosts”;    //Default project location prefix;
    var $default_module = “test.cn”;
    var $max_filesize = 2048000;
    var $request_uri;

    function Receive(){
        $this->request_uri = $_SERVER[`REQUEST_URI`];
    }
    function saveFile(){
        //receive data and save
        $putdata = fopen(“php://input”, “r”);

        $path = $this->getPath($this->request_uri);
        $fp = fopen($path, `w`);
        while($data = fread($putdata, 1024) ){
            fwrite($fp, $data);
        }

        fclose($fp);
        fclose($putdata);

        //Log The filesize check and limit check
        if( filesize($path) != $_SERVER[`CONTENT_LENGTH`] ){
            $this->errorLog( “[warn] ” . date(“Y-m-d H:i:s”)  . ” The file`s ($path) size dosen`t match Server Filesize = ” . filesize($path) . “; Put Filesize = ” . $_SERVER[`CONTENT_LENGTH`]. ”
” );
            header(`HTTP/1.1 526 Receive Data Error`);
        }

        if( filesize($path) > $this->max_filesize ){
            $this->errorLog( “[warn] ” . date(“Y-m-d H:i:s”)  . ” The file`s ($path) size exceed the system limit”);
        }
    }

    //Log Error Info
    function errorLog( $info ){
        $f = fopen($this->default_log_file, `a+`);
        fwrite($f, $info);
        flcose($f);
    }

    function serverLog(){
        $f = fopen($this->default_server_info, `w`);
        $info = $_SERVER;
        $str = “The Last Request Server Info:
“;
        foreach ($info as $key => $value){
            $str .= “$key = $value
“;
        }
        $str .= $this->getPath($this->request_uri) . ”
“;
        $str .= “PHP_UPLOADED_FILE_NAME=” . $PHP_UPLOADED_FILE_NAME . ”
“;
        fwrite($f , $str);
        fclose($f);
    }

    //Log the Request Header info
    function headerLog(){
        $f = fopen($this->default_header_info, `w`);
        $info = get_headers();
        $str = “The Last Request header Info:
“;
        foreach ($info as $key => $value){
            $str .= “$key = $value
“;
        }
        fwrite($f , $str);
        fclose($f);
    }

    //get the path where the file should be
    function getPath($uri){
        $module = $this->defalt_module;    //Default storage module

        $referer = $this->request_uri;
        preg_match(`/(?<=/)(.*?)(?=/)/s`, $referer, $match);

        if( !empty($match) && !empty($match[0]) ){
            $module = $match[0];
        }

        $path = $this->default_prefix;
        $path .= `/` . $module . `/htdocs`;
        $fullpath = substr($uri, strlen($match[0]) + 1, strlen($uri) );

        $arr = explode(`/`, ltrim($fullpath, `/`));
        foreach($arr as $v){
            if( !strstr($v, `.`) ){
                $path .= `/` . $v;
                //exec(“echo $path >> dir.txt”);
                if( !is_dir($path) ){
                    //For php > 5.0
                    //mkdir($path, “0766”, true);
                    mkdir($path, 0766);
                }
            }else{
                $path .= `/` . $v;
            }
        }

        return $path;
    }

}

$instance = new Receive();
$instance->serverLog();
//$instance->headerLog();
$instance->saveFile();

?>

這個指令碼,使用PHP手冊中的接收PUT方式的方法,詳細的使用,GOOGLE的時候,並沒有找到很多,所以可能對於錯誤情況,考慮的也不是很全面,如果有使用過這個方法的歡迎和我討論。

Technorati 標籤: PHP,PUT,Script

參考資料:
1、PUT Upload
2、RFC 2616


相關文章