支持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