虽然没有POST方法使用广泛,但是PUT方法却是向服务器上传文件最有效率的方法。POST上传文件时,我们通常需要将所有的信息组合成 multipart 传送过去,然后服务器再解码这些信息,解码过程则必不可少的会消耗内存和CPU资源,这种现象在上传大文件时尤其明显。而PUT方法则允许你通过与服务器建立的socket链接传递文件的内容,而不附带其他的信息。
最近一个项目上需要利用这种方式来进行文件的上传,下面介绍一下在 Apache + PHP 的环境下如何进行PUT方式的文件上传。
Apache 有一个模块 mod_actions,先看看官方的说明:
This module has two directives. TheActiondirective lets you run CGI scripts whenever a file of a certainMIME content type is requested. TheScriptdirective 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 脚本的编写了,他的主要任务就是将请求的文件写到指定的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 <?php  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" ;         var  $default_module  = "test.cn" ;      var  $max_filesize  = 2048000 ;      var  $request_uri ;     function  Receive ( ) {          $this ->request_uri = $_SERVER ['REQUEST_URI' ];      }      function  saveFile ( ) {                   $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 );                  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' ]. "\r\n"  );              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" );          }      }          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:\r\n" ;          foreach  ($info  as  $key  => $value ){              $str  .= "$key  = $value \r\n" ;          }          $str  .= $this ->getPath($this ->request_uri) . "\r\n" ;          $str  .= "PHP_UPLOADED_FILE_NAME="  . $PHP_UPLOADED_FILE_NAME  . "\r\n" ;          fwrite($f  , $str );          fclose($f );      }          function  headerLog ( ) {          $f  = fopen($this ->default_header_info, 'w' );          $info  = get_headers();          $str  = "The Last Request header Info:\r\n" ;          foreach  ($info  as  $key  => $value ){              $str  .= "$key  = $value \r\n" ;          }          fwrite($f  , $str );          fclose($f );      }          function  getPath ($uri  ) {          $module  = $this ->defalt_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 ;                                   if ( !is_dir($path ) ){                                                                mkdir($path , 0766 );                  }              }else {                  $path  .= '/'  . $v ;              }          }         return  $path ;      } } $instance  = new  Receive(); $instance ->serverLog(); $instance ->saveFile();?> 
 
这个脚本,使用PHP手册中的接收PUT方式的方法,详细的使用,GOOGLE的时候,并没有找到很多,所以可能对于错误情况,考虑的也不是很全面,如果有使用过这个方法的欢迎和我讨论。
参考资料: 1、PUT Upload  2、RFC 2616