virtual

(PHP 4, PHP 5, PHP 7)

virtual아파치 하위 요청을 실행합니다

설명

bool virtual ( string $filename )

virtual()은 mod_include의 <!--#include virtual...-->과 동일한 아파치 전용 함수입니다. 이것은 아파치 하위 요청을 실행합니다. CGI 스크립트나 .shtml 파일 등 아파치를 통해서 실행 가능한 무엇이든 가능합니다. CGI 스크립트의 경우, 그 스크립트가 유효한 CGI 헤더를 생성해야 함에 주의하십시오. 최소한 Content-type 헤더가 필요합니다.

하위 요청을 실행할 때, 헤더를 포함한 모든 버퍼를 종료하고 브라우저에 출력합니다.

이 함수는 PHP를 아파치 모듈로 설치하였을 때만 지원합니다.

인수

filename

가상 명령을 실행할 파일.

반환값

성공시엔 가상 명령을 실행하고, 실패시엔 FALSE를 반환합니다.

변경점

버전 설명
4.0.6 이 함수를 PHP 파일에 사용할 수 있습니다. 그러나, 일반적으로 PHP 파일에 대해서는 includerequire를 사용하는 편이 좋습니다.

예제

예제는 apache_note()를 참고하십시오.

주의

Warning

쿼리 문자열은 포함하는 파일로 전해지지만, $_GET은 부모 스크립트에서 복사되며, $_SERVER['QUERY_STRING']만 주어진 쿼리 문자열로 채워집니다. 쿼리 문자열은 아파치 2를 사용할 때만 전달됩니다. 요청된 파일은 아파치 접근 기록에 남지 않습니다.

Note:

요청된 파일에서 설정한 환경 변수는 호출한 스크립트에서 확인할 수 없습니다.

PHP 4.3.3부터 이 함수를 넷스케이프/iPlanet/SunONE 웹서버의 NSAPI 서버 모듈에서도 사용할 수 있습니다.

참고

  • apache_note() - 아파치의 요청 노트를 얻거나 설정한다

add a note add a note

User Contributed Notes 14 notes

up
2
vcaron at bearstech dot com
18 years ago
You can use virtual() to implement your own dispatcher/auth handler in an efficient and effective way.

For instance if you have a bunch of images you would like to be served statically by Apache (its job after all), but with a more com
plex 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

Then in your PHP script, before sending any content or header:

<?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";
?>

Of course very Apache-ish, but it's much more efficient and uniform to rely on Apache rather than passthru() and mime_content_type()
hacks : it does the path lookup and auth/security audit as the admin expects, use the best static serving it can (think 'sendfile')
and you can even chain your request with another embedded script eg. in mod_perl.
up
2
php at nagler-ihlein dot de
4 years ago
Starting with 7.2 various session related things were changed and it seems part of it is conflicting with virtual(). Calling it you may receive a "PHP Warning:  virtual(): Headers already sent. You cannot change the session module's ini settings at this time in ..." although no header was set and virtual() should simply provide static content from a file.

It took half a day to find out that some main session.configuration.php settings (e.g. save_handler, serialize_handler, or save_path) in the Apache config (all levels) are causing this. It seems the settings are regarded as likely to change headers but virtual() is already beyond sending them. Unfortunately the warning is then not only logged to error_log but also part of the content virtual() sends to the client which screws it up - the format of the content like binary image data is no more correct.

If you do not need php's session management you can remove all session.* settings in your apache configuration to avoid the warning. Otherwise a workaround is to switch off E_WARNING before calling virtual().

<?php
$filename
= '/data/image.jpg';
$level = error_reporting();
error_reporting($level & ~E_WARNING);
virtual($filename);
error_reporting($level);
?>
up
2
crazyted at crazyted dot com
22 years ago
I have a header that's include()'ed on each of my pages. I then wanted to add a Perl script to that header file (header.php) via the virtual() command.

Since my header is used by documents in my /www folder along with other folders inside that (and inside those), and virtual() seems to take only relative paths, I had to write some code to dynamically get the path to the perl script.

Hope this helps some ppl out:

  $cwd = getcwd();
  $script_name = "cgi-bin/perl_script.pl";
  $count = substr_count($cwd, '/');
  $count = $count - 3;
  // get rid of extra absolute paths since my directory is /home/user/www

  // Add additional path information
  for($i = 1; $i <= $count; $i++){
    $script_name = "../".$script_name;     
  }
  virtual($script_name);
up
1
Peter Kehl
11 years ago
This documentation is not clear enough. Parameter $filename is not a filename as on the filesystem, but a URI. It can be absolute, starting with /, or relative to URI that involved the PHP script which called virtual(). (I.e. if the PHP script that calls virtual() is invoked via PHP require/require_once/include/include_once mechanism and it passes a relative URI to virtual(), then that URI must be relative to the URI of the topmost PHP script on the inclusion stack.)

Not sure how relative URIs work if the request which calls virtual() was processed through Apache rewrite rules.
up
1
phpforum at joolee dot nl
15 years ago
Problem with most of the scripts posted below is that virtual() flushes the pending headers before making the subrequest. Requesting an image with virtual() still returns a text/html type document.
A workaround is to set the content-type first. But that requires getting the content-type first.

I'm using the following script for now. A disadvantage is that Apache makes 2 subrequests.

<?PHP
$file
= '/resources/7z.gif';
$file_info = apache_lookup_uri($file);
header('content-type: ' . $file_info -> content_type);
virtual($file);
die();
?>
up
1
php at n-wise dot com
21 years ago
I saw the note above about the length of the query string... but didn't know what it was, so have altered the code so it can post to the script.
Probly only works on nix systems as it makes use of the echo function...
This code also will look evaluate the result, so you can get cgi to dynamically create PHP (probly best to watch out that posted variables do not include script!)
<?
$CGISCRIPT
="./cgi-bin/cgiscript.cgi";
// preparing the arguments passed to this PHP page
$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);

//The 1st line of my script was "Content...." ... so remove it!
$firstline=array_shift($return_array);
//evaluate the code
eval('?>'.implode($return_array,''));

?>
up
0
david at audiogalaxy dot com
25 years ago
Virtual returns the HTTP entity header after the requested file, when it's the first output to the page.

The work-around to prevent seeing the header is, of course, to output something (such as echo " "; ) before calling virtual.
up
-1
Anonymous at spam dot org
17 years ago
Note that QUERY_STRING seems to get inherited, so to make a virtual request WITHOUT one, one needs to explicitly append a "?" to the URL of the sub-request (to cause the creation of a "null" query string).  Of course, if the desired URL has its own query string, that will override and an additional "?" should not be appended.

This was with PHP 4.4.7 (released May 2007).
up
-1
ruibal_DELETED_p*AT*gmail__dot__com
18 years ago
when php is installed as an apache module, this works pretty well for writing your own php preprocessor/information logger. For example, requests to any URI underneath pre.php will first be executed by pre.php, then returned to the user.
<?
$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 );
// - - - - - - - - - - - - - - - - - - - - - - - - - - -
//  Do processing of data stored in $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 );

?>

So all files in http://server/sub/pre.php/path/ are really located in http://server/sub/_pre/path/

All this needs is some kind of caching mechanism.

But yeah, this could be modified to add watermarks with the image functions, convert to xml with Tidy, check for extensions better with mimeTypes, proxy content with cURL, validate $_SERVER['HTTP_REFERER'] or $_SERVER['HTTP_USER_AGENT'], etc etc

This gives you much more over than the auto_prepend_file, and auto_append_file, for certain functionality

The key is the virtual function _because_ it delivers the modified content with an apache subrequest.
up
-1
s dot dan at free dot fr
23 years ago
Another way of passing arguments:
If you have some CGI programs that depend on some libraries where you can't change the source code (in my case an online payment library), you can pass the arguments by changing some environment variables.

Of course the CGI program has to get the GET/POST variables in the usual manner.
It simulates, more or less, a direct call from the server to a CGI program:

// preparing the arguments passed to this PHP page
$QSTRING = $QUERY_STRING;

// pay attention to the maximum length of the QUERY string.
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);

Now you can parse the output of 'my_CGI' in return_array.
up
-1
logang at deltatee dot com
23 years ago
If you want to pass all post and get values to the cgi script you can use this code:

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

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

It takes all the values of $HTTP_POST_VARS and appends them in the proper format to the values you get in $QUERY_STRING
up
-2
abentley at panoramicfeedback dot com
21 years ago
Here's an update to tomwk's code:
function safe_virtual( $filename )
{
   $curDir = getcwd();
   virtual ( $filename );
   chdir( $curDir );
}

This is better if you've already changed your current directory to be something other than your script's directory.  It works for PHP4 and above.
up
-2
jhibbard at gmail dot com
15 years ago
While the virtual() function has it's promising sides, there exists issues when using it in relation to a cache system such as eAccellerator.  The issue becomes that the first time you load with a virtual file, it will seem to work fine.  But once the cache is put into play, the virtual call will end up returning nothing at all, and basically returning a blank page.

Please realize that this is not an issue with virtual(), but instead an issue of the cacheing application.  Should anyone else have a similar issue, hopefully this will shed some light onto this subject.

Jonathon Hibbard
up
-2
chardin at ssc dot wisc dot edu
22 years ago
If you are having problems using virtual include becuase of files being stored in different directories, a root-relative path will make things much easier:

virtual ("/root directory/directory/filename.htm/");

where root directory is the root directory of your site (ask your sys admin if you dont know what it is) Don't include the protocol or host name.

This will also allow you to move your files around your site without having to redirect your includes which is *very* helpfull
To Top