PHP Conference Japan 2024

virtual

(PHP 4, PHP 5, PHP 7, PHP 8)

virtual執行 Apache 子請求

描述

virtual(string $uri): bool

virtual() 是一個 Apache 特定的函式,類似於 <!--#include virtual...-->mod_include 中的用法。它執行一個 Apache 子請求。它對於包含 CGI 腳本或 .shtml 檔案,或者其他任何您想要透過 Apache 解析的內容都很有用。請注意,對於 CGI 腳本,該腳本必須產生有效的 CGI 標頭。至少這意味著它必須產生一個 Content-Type 標頭。

為了執行子請求,所有緩衝區都會終止並刷新到瀏覽器,待處理的標頭也會被發送。

當 PHP 以 Apache 模組網頁伺服器安裝時,此函式才被支援。

參數

uri

將執行 virtual 命令的檔案。

傳回值

成功執行 virtual 命令時會傳回 true,失敗則傳回 false

範例

請參閱 apache_note() 中的範例。

注意事項

警告

查詢字串可以傳遞給包含的檔案,但 $_GET 是從父腳本複製的,只有 $_SERVER['QUERY_STRING'] 會填入傳遞的查詢字串。只有在使用 Apache 2 時才能傳遞查詢字串。請求的檔案不會列在 Apache 存取記錄中。

注意:

在請求的檔案中設定的環境變數對於呼叫腳本是不可見的。

注意:

此函式可以用於 PHP 檔案。然而,對於 PHP 檔案,通常最好使用 includerequire

另請參閱

新增註解

使用者貢獻註解 14 個註解

3
php at nagler-ihlein dot de
4 年前
從 7.2 開始,各種與 session 相關的東西都發生了變更,而且它似乎與 virtual() 有衝突。呼叫它您可能會收到「PHP Warning: virtual(): Headers already sent. You cannot change the session module's ini settings at this time in ...」的警告,即使沒有設定任何標頭,而且 virtual() 應該只是提供來自檔案的靜態內容。

花了半天的時間才發現,Apache 設定(所有層級)中的一些主要的 session.configuration.php 設定(例如 save_handler、serialize_handler 或 save_path)導致了這個問題。似乎這些設定被認為可能會變更標頭,但 virtual() 已經超過了發送它們的時間點。不幸的是,警告不僅會記錄到 error_log 中,還會成為 virtual() 發送到客戶端的內容的一部分,這會把它搞砸 - 像是二進制圖像數據之類的內容格式不再正確。

如果您不需要 php 的 session 管理,您可以移除 apache 設定中的所有 session.* 設定以避免警告。否則,一種解決方法是在呼叫 virtual() 之前關閉 E_WARNING。

<?php
$filename
= '/data/image.jpg';
$level = error_reporting();
error_reporting($level & ~E_WARNING);
virtual($filename);
error_reporting($level);
?>
3
crazyted at crazyted dot com
22 年前
我的每個頁面都有一個 include()'ed 的標頭。然後我想透過 virtual() 命令在該標頭檔案 (header.php) 中新增一個 Perl 腳本。

由於我的標頭被我的 /www 資料夾中的文件以及其中的其他資料夾(以及其中的資料夾)使用,而且 virtual() 似乎只接受相對路徑,所以我必須編寫一些程式碼來動態取得 Perl 腳本的路徑。

希望這能幫助一些人

$cwd = getcwd();
$script_name = "cgi-bin/perl_script.pl";
$count = substr_count($cwd, '/');
$count = $count - 3;
// 移除多餘的絕對路徑,因為我的目錄是 /home/user/www

// 新增其他路徑資訊
for($i = 1; $i <= $count; $i++){
$script_name = "../".$script_name;
}
virtual($script_name);
2
vcaron at bearstech dot com
18 年前
您可以使用 virtual() 以有效且高效的方式實作您自己的分派器/驗證處理程序。

例如,如果您有一堆您希望由 Apache 靜態提供的圖像(畢竟這是它的工作),但具有比 mod_access 允許您執行的更複雜的存取模式(例如使用您的應用程式邏輯的 MySQL 查找),請試試看這個簡單的 Apache 規則
complex access pattern than mod_access allows you to do (say a MySQL lookup with your app logic), try this simple Apache rule

Order Allow,Deny
Allow from env=PHP_ALLOW

然後在您的 PHP 腳本中,在發送任何內容或標頭之前

<?php
$image
= "/some/URL/path/test.png";
if (
client_may_view_image($image)) {
apache_setenv('PHP_ALLOW', '1');
if (
virtual($image))
exit(
0);
echo
"Ops, failed to fetched granted image $image (hammer your webmaster).\n";
} else
echo
"Sorry buddy, you're not allowed in here.\n";
?>

當然,這很 Apache-ish,但是依靠 Apache 而不是 passthru() 和 mime_content_type() 更有效率和統一
hack:它執行路徑查找和管理員期望的驗證/安全性稽核,使用它能提供的最佳靜態服務(想想 'sendfile')
你甚至可以將你的請求與另一個嵌入的腳本鏈接起來,例如在 mod_perl 中。
2
Peter Kehl
11 年前
這份文件說明不夠清楚。參數 $filename 不是檔案系統上的檔案名稱,而是一個 URI。它可以是絕對的,以 / 開頭,或是相對於調用 virtual() 的 PHP 腳本的 URI。 (也就是說,如果調用 virtual() 的 PHP 腳本是透過 PHP 的 require/require_once/include/include_once 機制調用的,並且它將相對 URI 傳遞給 virtual(),那麼該 URI 必須相對於包含堆疊中最頂層的 PHP 腳本的 URI。)

不確定如果調用 virtual() 的請求是透過 Apache 重寫規則處理的,相對 URI 將如何運作。
2
phpforum at joolee dot nl
15 年前
下面發布的大部分腳本的問題是,virtual() 會在發出子請求之前刷新待處理的標頭。使用 virtual() 請求圖片仍然會返回 text/html 類型的文件。
一個解決方法是先設置 content-type。但是這需要先取得 content-type。

我目前使用以下腳本。缺點是 Apache 會發出 2 個子請求。

<?PHP
$file
= '/resources/7z.gif';
$file_info = apache_lookup_uri($file);
header('content-type: ' . $file_info -> content_type);
virtual($file);
die();
?>
1
david at audiogalaxy dot com
25 年前
當 virtual 是頁面的第一個輸出時,它會在請求的檔案之後返回 HTTP 實體標頭。

當然,避免看到標頭的解決方法是在調用 virtual 之前輸出一些東西(例如 echo " ";)。
2
php at n-wise dot com
21 年前
我看到了上面關於查詢字串長度的註解... 但不知道那是什麼,所以修改了程式碼,讓它可以發布到腳本。
它可能只在 nix 系統上運作,因為它使用了 echo 函數...
這段程式碼還會評估結果,所以你可以讓 cgi 動態建立 PHP(最好注意發佈的變數不包含腳本!)
<?
$CGISCRIPT="./cgi-bin/cgiscript.cgi";
// 準備傳遞給這個 PHP 頁面的參數
$QSTRING = $QUERY_STRING;

foreach ($HTTP_POST_VARS as $header=> $value ){
if($QSTRING==""){
$QSTRING = $header.'='.urlencode($value);
}else{
$QSTRING = $QSTRING.'&'.$header.'='.urlencode($value);
}
}

putenv('REQUEST_METHOD=POST');
putenv('CONTENT_TYPE=application/x-www-form-urlencoded');
putenv('CONTENT_LENGTH='.strlen($QSTRING));
putenv('QUERY_STRING='.$QSTRING);
unset($return_array);
exec('echo "'.$QSTRING.'"| '.$CGISCRIPT, $return_array, $return_val);

//我的腳本的第一行是 "Content...." ... 所以移除它!
$firstline=array_shift($return_array);
//評估程式碼
eval('?>'.implode($return_array,''));

?>
0
Anonymous at spam dot org
17 年前
請注意,QUERY_STRING 看起來會被繼承,因此要發出一個沒有查詢字串的虛擬請求,需要明確地在子請求的 URL 後面附加一個 "?"(以產生一個「空」查詢字串)。當然,如果想要的 URL 有自己的查詢字串,那將會覆蓋,並且不應附加額外的 "?"。

這是使用 PHP 4.4.7 (於 2007 年 5 月發布) 的情況。
0
ruibal_DELETED_p*AT*gmail__dot__com
18 年前
當 php 安裝為 apache 模組時,這對於編寫你自己的 php 前處理器/資訊記錄器來說非常有用。例如,對 pre.php 下的任何 URI 的請求將首先由 pre.php 執行,然後返回給使用者。
<?
$docroot = $_SERVER['DOCUMENT_ROOT'];
$script_root = str_replace( basename($_SERVER['SCRIPT_NAME']),'',$_SERVER['SCRIPT_NAME'] );
$script_ext = substr( $_SERVER['SCRIPT_NAME'], strrpos( $_SERVER['SCRIPT_NAME'],'.' ) );
$fakework_root = $script_root.basename( $_SERVER['SCRIPT_NAME'] ).'/';
$framework_root = $script_root.'_'.basename( $_SERVER['SCRIPT_NAME'], $script_ext ).'/';
$frequest_root = dirname( $framework_root.substr( $_SERVER['PATH_INFO'], 1 )).'/';
$frequest_name = basename( $_SERVER['PATH_INFO'] );
$frequest_ext = (strrpos($frequest_name,'.')===FALSE ? FALSE : strtolower(substr( $frequest_name, ( strrpos( $frequest_name, '.' )+1 ) ) ) );
$frequest_full = $frequest_root.$frequest_name;
$doc_frequest = $docroot.$frequest_full;
$doc_framework = $docroot.$framework_root;

$DO_PARSE = in_array( $frequest_ext, $chk_exts );
if( $DO_PARSE )
{
$tmpfname = tempnam( $doc_framework.'tmp', 'aj_' ).($frequest_ext? ('.'.$frequest_ext) : '');
if( ($to_parse=@file_get_contents($doc_frequest))===FALSE )
$to_parse="404";
$tmpvname = str_replace( $docroot, '', $tmpfname );
$tmpvname = str_replace( '\\\\', '/', $tmpvname );
// - - - - - - - - - - - - - - - - - - - - - - - - - - -
// 處理儲存在 $to_parse 中的資料
// - - - - - - - - - - - - - - - - - - - - - - - - - - -
$to_parse = striptags( $to_parse );

// - - - - - - - - - - - - - - - - - - - - - - - - - - -
$handle = fopen($tmpfname, "w");
fwrite($handle, $to_parse);
fclose($handle);
@virtual( $tmpvname.$getvars );
unlink( $tmpfname );
}
else
@virtual( $frequest_full.$getvars );

?>

因此,http://server/sub/pre.php/path/ 中的所有檔案實際上都位於 http://server/sub/_pre/path/

這一切只需要某種快取機制。

但是,是的,這可以修改為使用影像功能添加浮水印、使用 Tidy 轉換為 xml、使用 mimeTypes 更好地檢查擴展名、使用 cURL 代理內容、驗證 $_SERVER['HTTP_REFERER'] 或 $_SERVER['HTTP_USER_AGENT'] 等等

對於某些功能,這比 auto_prepend_file 和 auto_append_file 提供了更多功能

關鍵是 virtual 函數,_因為_ 它使用 apache 子請求傳遞修改後的內容。
0
s dot dan at free dot fr
23 年前
傳遞參數的另一種方法
如果您有一些 CGI 程式依賴於無法更改原始碼的某些函式庫(在我的情況下是一個線上支付函式庫),您可以透過更改一些環境變數來傳遞參數。

當然,CGI 程式必須以通常的方式取得 GET/POST 變數。
它或多或少模擬了從伺服器直接呼叫 CGI 程式

// 準備傳遞給這個 PHP 頁面的參數
$QSTRING = $QUERY_STRING;

// 請注意 QUERY 字串的最大長度。
while (list ($header, $value) = each ($HTTP_POST_VARS)){
if (empty($QSTRING))
$QSTRING = $header.'='.$value;
else
$QSTRING = $QSTRING.'&'.$header.'='.$value;
}

putenv('REQUEST_METHOD=GET');
putenv('QUERY_STRING='.$QSTRING);

unset($return_array);
exec('my_CGI', $return_array, $return_val);

現在您可以在 return_array 中解析 'my_CGI' 的輸出。
0
logang at deltatee dot com
23 年前
如果您想將所有 post 和 get 值傳遞給 cgi 腳本,您可以使用此程式碼

<?php
$QSTRING
= $QUERY_STRING;
while (list (
$header, $value) = each ($HTTP_POST_VARS))
{
$QSTRING = $QSTRING.'&'.$header.'='.$value;
}

virtual($script.'?'.$QSTRING);
?>

它會取得 $HTTP_POST_VARS 的所有值,並以正確的格式將它們附加到 $QUERY_STRING 中取得的值
-1
jhibbard at gmail dot com
14 年前
雖然 virtual() 函數有其前景看好的一面,但在與 eAccellerator 等快取系統相關時,存在一些問題。問題在於,當您第一次載入虛擬檔案時,它看起來會運作良好。但是一旦快取生效,虛擬呼叫最終將完全不返回任何內容,基本上返回一個空白頁面。

請注意,這不是 virtual() 的問題,而是快取應用程式的問題。如果其他人遇到類似問題,希望這能對這個主題有所啟發。

Jonathon Hibbard
-1
chardin at ssc dot wisc dot edu
22 年前
如果您因為檔案儲存在不同的目錄中而導致使用虛擬包含時出現問題,則根目錄相對路徑會讓事情變得更容易

virtual ("/根目錄/目錄/filename.htm/");

其中根目錄是您網站的根目錄(如果您不知道是什麼,請諮詢您的系統管理員)。不要包含協定或主機名稱。

這也將允許您在網站中移動檔案,而不必重定向包含,這*非常*有用
-1
abentley at panoramicfeedback dot com
21 年前
以下是 tomwk 程式碼的更新
function safe_virtual( $filename )
{
$curDir = getcwd();
virtual ( $filename );
chdir( $curDir );
}

如果您已經將目前目錄更改為不是您腳本的目錄,這會更好。它適用於 PHP4 及更高版本。
To Top