2009年8月1日 星期六

PHP 學習筆記 header:下載與轉址等等

開始摸PHP也有幾天了,把一些心得PO上來

之前碰到一個須求,我要用PHP控制seriver上的檔案讓人下載
以前我都是用javascript的location.href轉址轉過去那個file
如果是一般二進位檔或許會正常進行下載動作,但是一些文字檔還是圖片檔
像是.txt,.doc等等,會照瀏覽器預設行為動作
但是我希望他統一都是用下載的方式
後來發現可以更改HTTP Request來控制瀏覽器的行為
在此我須要PHP的header函數幫我做這件事
http://tw.php.net/manual/en/function.header.php

開頭就說了header函數的功用

header — Send a raw HTTP header

透過header函數我們可以修改 HTTP Header
而要讓瀏覽器啟動下載必要的一行函數

header('Content-type:application/force-download');

只要有這行,就可以把瀏覽器上的output變成一個file下載
而我最常用的範例如下

header('Content-type:application/force-download'); //告訴瀏覽器 為下載
header('Content-Transfer-Encoding: Binary'); //編碼方式
header('Content-Disposition:attachment;filename='.$filename); //檔名
@readfile($filename);

透過readfile把檔案內容全部輸出到瀏覽器後,透過header的修正,就會變成下載檔案的行為
同時也可以用echo來輸出一般文字檔

舉個簡單的例子

header('Content-type:application/force-download'); //告訴瀏覽器 為下載
header('Content-Transfer-Encoding: Binary'); //編碼方式
header('Content-Disposition:attachment;filename=hello.txt'); //檔名
echo 'Hello PHP';

上面例子會通知瀏覽器下載檔案,檔名為hello.txt




這是比較簡便的做法,完整一點的例子如下,可以控制傳輸方式,檔案size等資訊

<?php
$filename = "theDownloadedFileIsCalledThis.mp3";
$myFile = "/absolute/path/to/my/file.mp3";

$mm_type="application/octet-stream";

header("Cache-Control: public, must-revalidate");
header("Pragma: hack"); // WTF? oh well, it works...
header("Content-Type: " . $mm_type);
header("Content-Length: " .(string)(filesize($myFile)) );
header('Content-Disposition: attachment; filename="'.$filename.'"');
header("Content-Transfer-Encoding: binary\n");
readfile($myFile);
?>

又或者可以控制檔案讀取方式

// We'll be outputting a PDF
header('Content-type: application/pdf');

// It will be called downloaded.pdf
//header('Content-Disposition: attachment; filename="downloaded.pdf"');

// The PDF source is in original.pdf
readfile('2007.pdf');

上面這段程式碼
如果加了header('Content-Disposition: attachment; filename="downloaded.pdf"');
這行會變成通知瀏覽器下載此pdf
如果不加,則是告訴瀏覽器output是pdf檔,瀏覽器會使用他預設讀取pdf的程式如adobe reader來作存取,如下圖是不加的執行結果


header還有許多奇妙的功用
像是防止網頁過期資訊

警告: 網頁已經過期 已經使用您在表格傳送的資訊,來建立您要求的網頁。這個網頁已經無法再使用。基於安全性考量,Internet Explorer 不會自動為您重新傳送資訊。

可以加上下面這行址令解決

header ('Cache-Control: private, pre-check=0, post-check=0, max-age=0');




同時可以用他達到轉址的效果如下

header("Location: http://www.google.com.tw");


不過要記得一件事,使用header轉址的時候記得要加上exit或flush
來防止無法控制的結果

考慮下面一段程式碼

header("Location: http://www.google.com.tw");
sleep(1);
header("Location: http://www.yahoo.com.tw");

他最後結果會到yahoo而非google,因為header下了以後不會馬上執行
結果又執行了一次header函數把原先的目標給蓋掉了
但是改成

header("Location: http://www.google.com.tw");
flush();
sleep(1);
header("Location: http://www.yahoo.com.tw");

或是

header("Location: http://www.google.com.tw");
exit;
sleep(1);
header("Location: http://www.yahoo.com.tw");

就可以了,不過比較建議用第二種
如果用flush要避免flush之後再去修改header,譬如說

flush();
header("Location: http://www.yahoo.com.tw");


執行上面的程式會出現以下訊息

Cannot modify header information - headers already sent

沒有留言: