支持ETag的PHP的SendFile
在动态生成缩略图的时候,需要用PHP把生成缩略图的文件输出,此外,很多时候把文件放在browser不能直接访问的地方,用php代理,最常见的做法是read文件,echo出去,但是这样会白白浪费带宽,此外,也无法使用squid的负载缓存。昨晚实现了一个判断ETag的方法,本来还想实现断点续传,太困就没有继续,原理也简单,只要判断一下IF-RANGE,然后fseek到指定位置,读出,echo出去就ok了,等有空了再完善,现在这个是用于缩略图的,不支持多点续传也无所谓了。
###source
/**
* 输出文件内容,支持Etag缓存
*
* @param string $file
* @return unknown
* @todo support if-range,断点续传
*/
function sendFile($file){
$clientHeaders = fetchClientHeaders();
$fileMTime = filemtime($file);
$fileSize = filesize($file);
$etag="\"".dechex(fileinode($file)).":6r0x:".dechex($fileMTime).":".dechex($fileSize)."\"";
$ifMatch =$ifNoneMatch=$ifModifiedSince=$ifUnModifiedSince =true;
$code = null;
$serverHeaders["Content-type"]= FileUtil::getMimeType(FileUtil::getFileExtension($file));
//如果服务端的etag匹配IF-MATCH记录或*存在时则刷新,否则304?
if ($hdr_inm=$clientHeaders["IF-MATCH"]) {
$ifMatch=false;
$inms=explode(",", trim($hdr_inm));
foreach ($inms as $inm_tag) {
$inm_tag=trim($inm_tag);
if ($inm_tag==$etag || $inm_tag=="*") {
$code=304;
$serverHeaders["ETag"]=$etag;
$ifMatch=true;
}
}
}
//如果服务端的etag不匹配Client的IF-NONE-MATCH记录或*存在时刷新,否则输出304
if ($hdr_inm=$clientHeaders["IF-NONE-MATCH"]) {
$inms=explode(",", trim($hdr_inm));
foreach ($inms as $inm_tag) {
$inm_tag=trim($inm_tag);
if ($inm_tag==$etag || $inm_tag=="*") {
$code=304;
$serverHeaders["ETag"]=$etag;
$ifNoneMatch=false;
}
}
}
//如果文件未修改时(修改时间<指定时间)刷新,否则输出304
if ($lmdate=$clientHeaders["IF-UNMODIFIED-SINCE"]) {
$lmdate=(float)strtotime($lmdate);
if ($fileMTime>$lmdate) {
$code=304;
$serverHeaders["Last-Modified"]=gmdate("D, d M Y H:i:s T", $fileMTime);
$ifUnModifiedSince=false;
}
}
//如果文件修改时间>指定时间则刷新,否则输出304
if ($lmdate=$clientHeaders["IF-MODIFIED-SINCE"]) {
$lmdate=(float)strtotime($lmdate);
if ($fileMTime<=$lmdate) {
$code=304;
$serverHeaders["Last-Modified"]=gmdate("D, d M Y H:i:s T", $fileMTime);
$ifModifiedSince=false;
}
}
//output 304
if (!($ifMatch && $ifNoneMatch && $ifModifiedSince && $ifUnModifiedSince)) {
return outputHeader($serverHeaders,$code);
}
$code = 200;
$serverHeaders["Last-Modified"]=gmdate("D, d M Y H:i:s T", $fileMTime);
$serverHeaders["ETag"]=$etag;
outputHeader($serverHeaders,$code);
$fp = @fopen($file,'rb');
if($fp){
fpassthru($fp);
@fclose($fp);
}
}
/**
* 获得客户端的所有发送的Header
*
* 注:
* 不能使用Apache的getallheader,我们需要支持FCGI
*
* @return array
*/
function fetchClientHeaders(){
$headers = array();
foreach($_SERVER as $h=>$v){
if(ereg('HTTP_(.+)',$h,$hp))
$headers[str_replace('_','-',strtoupper($hp[1]))]=$v;
}
return $headers;
}
/**
* 输出Headers
*
* @param array $headers
* @param int $code
*/
function outputHeader($headers,$code=null){
$HTTP_HEADERS=array(100 => "100 Continue",
200 => "200 OK",
201 => "201 Created",
204 => "204 No Content",
206 => "206 Partial Content",
300 => "300 Multiple Choices",
301 => "301 Moved Permanently",
302 => "302 Found",
303 => "303 See Other",
304 => "304 Not Modified",
307 => "307 Temporary Redirect",
400 => "400 Bad Request",
401 => "401 Unauthorized",
403 => "403 Forbidden",
404 => "404 Not Found",
405 => "405 Method Not Allowed",
406 => "406 Not Acceptable",
408 => "408 Request Timeout",
410 => "410 Gone",
413 => "413 Request Entity Too Large",
414 => "414 Request URI Too Long",
415 => "415 Unsupported Media Type",
416 => "416 Requested Range Not Satisfiable",
417 => "417 Expectation Failed",
500 => "500 Internal Server Error",
501 => "501 Method Not Implemented",
503 => "503 Service Unavailable",
506 => "506 Variant Also Negotiates");
foreach ($headers as $h=>$v){
@header("$h:$v");
}
if(isset($HTTP_HEADERS[$code])){
@header($HTTP_HEADERS[$code],true,$code);
}
}
Comments
Leave a Reply