make fastcgi usage threadsafe, ready for future multithreaded fastcgi implementation

get rid of environment overwriting but hooking into php's environment function
set $_ENV correctly for mod_fastcgi
add -b to specify binding for fastcgi
new readme file with information for running under apache2.0 and iis
This commit is contained in:
Shane Caraveo 2002-10-13 09:40:44 +00:00
parent 2c23e56c7f
commit 69f56e8c4a
3 changed files with 286 additions and 91 deletions

View file

@ -1,2 +1,2 @@
CGI
Rasmus Lerdorf, Stig Bakken
CGI / FastCGI
Rasmus Lerdorf, Stig Bakken, Shane Caraveo

126
sapi/cgi/README.FastCGI Normal file
View file

@ -0,0 +1,126 @@
Credits:
Ben Mansell, Stephen Landamore, Daniel Silverstone, Shane Caraveo
Running the FastCGI PHP module
------------------------------
There are two ways to run the resulting 'php' binary after the fastcgi
version has been built:
1) Configure your web server to run the PHP binary itself.
This is the simplest method, obviously you will have to configure your
web server appropriately. Some web servers may also not support this method,
or may not be as efficient.
2) Run PHP separately from the web server.
In this setup, PHP is started as a separate process entirely from the web
server. It will listen on a socket for new FastCGI requests, and deliver
PHP pages as appropriate. This is the recommended way of running PHP-FastCGI.
To run this way, you must start the PHP binary running by giving it a port
number to listen to on the command line, e.g.:
./php -b 8002
(you can also specify a bind address, e.g. ./php -b localhost:8002. However, this
relies on the FastCGI devkit and does not seem to work properly)
You must also configure your web server to connect to the appropriate port
in order to talk to the PHP FastCGI process.
The advantage of running PHP in this way is that it entirely separates the
web server and PHP process, so that one cannot disrupt the other. It also
allows PHP to be on an entirely separate machine from the web server if need
be, you could even have several web servers utilising the same running PHP
process if required!
Using FastCGI PHP with Apache
=============================
First of all, you may well ask 'Why?'. After all, Apache already has mod_php.
However, there are advantages to running PHP with FastCGI. Separating the
PHP code from the web server removes 'bloat' from the main server, and should
improve the performance of non-PHP requests. Secondly, having one permanent
PHP process as opposed to one per apache process means that shared resources
like persistent database connections are used more efficiently.
First of all, make sure that the FastCGI module is enabled. You should have
a line in your config like:
LoadModule fastcgi_module /usr/lib/apache/2.0/mod_fastcgi.so
Don't load mod_php, by the way. Make sure it is commented out!
#LoadModule php4_module /usr/lib/apache/2.0/libphp4.so
Now, we'll create a fcgi-bin directory, just like you would do with normal
CGI scripts. You'll need to create a directory somewhere to store your
FastCGI binaries. We'll use /space/fcgi-bin/ for this example. Remember to
copy the FastCGI-PHP binary in there. (named just 'php')
ScriptAlias /fcgi-bin/ /space/fcgi-bin/
<Location /fcgi-bin/>
Options ExecCGI
SetHandler fastcgi-script
</Location>
To have mod_fastcgi manage your php fastcgi processes for you, use the
configuration directive FCGIServer (see mod_fastcgi docs for more
configuration information):
FastCgiServer /fcgi-bin/php-cgi -processes 5
Next, we need to tell Apache to use the FastCGI binary /fcgi-bin/php to
deliver PHP pages. All that is needed is:
AddType application/x-httpd-fastphp .php
Action application/x-httpd-fastphp /fcgi-bin/php-cgi
Now, if you restart Apache, php pages should now be delivered!
Using FastCGI PHP with IIS or iPlanet
=====================================
FastCGI server plugins are available at www.caraveo.com/fastcgi/
Documentation on these are sparse. iPlanet is not very tested,
and no makefile exists yet for unix based iPlanet servers.
Security
--------
Be sure to run the php binary as an appropriate userid. Also, firewall out
the port that PHP is listening on. In addition, you can set the environment
variable FCGI_WEB_SERVER_ADDRS to control who can connect to the FastCGI.
Set it to a comma separated list of IP addresses, e.g.:
export FCGI_WEB_SERVER_ADDRS=199.170.183.28,199.170.183.71
Tuning
------
There are a few tuning parameters that can be tweaked to control the
performance of FastCGI PHP. The following are environment variables that can
be set before running the PHP binary:
PHP_FCGI_CHILDREN (default value: 8)
This controls how many child processes the PHP process spawns. When the
fastcgi starts, it creates a number of child processes which handle one
page request at a time. So by default, you will be able to handle 8
concurrent PHP page requests. Further requests will be queued.
Increasing this number will allow for better concurrency, especially if you
have pages that take a significant time to create, or supply a lot of data
(e.g. downloading huge files via PHP). On the other hand, having more
processes running will use more RAM, and letting too many PHP pages be
generated concurrently will mean that each request will be slow.
PHP_FCGI_MAX_REQUESTS (default value: 500)
This controls how many requests each child process will handle before
exitting. When one process exits, another will be created. This tuning is
necessary because several PHP functions are known to have memory leaks. If the
PHP processes were left around forever, they would be become very inefficient.

View file

@ -82,22 +82,14 @@
#include "fcgi_config.h"
#include "fcgiapp.h"
/* don't want to include fcgios.h, causes conflicts */
#ifdef PHP_WIN32
extern int OS_SetImpersonate(void);
#endif
FCGX_Stream *in, *out, *err;
FCGX_ParamArray envp;
/* Our original environment from when the FastCGI first started */
char **orig_env;
/* The environment given by the FastCGI */
char **cgi_env;
/* The manufactured environment, from merging the base environ with
* the parameters set by the per-connection environment
*/
char **merge_env;
static void (*php_php_import_environment_variables)(zval *array_ptr TSRMLS_DC);
#ifndef PHP_WIN32
/* these globals used for forking children on unix systems */
/**
* Number of child processes that will get created to service requests
*/
@ -112,7 +104,7 @@ static int parent = 1;
* Process group
*/
static pid_t pgroup;
#endif
#endif
@ -143,7 +135,7 @@ static int _print_extension_info(zend_extension *module, void *arg TSRMLS_DC)
#define STDOUT_FILENO 1
#endif
static inline size_t sapi_cgibin_single_write(const char *str, uint str_length)
static inline size_t sapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC)
{
#ifdef PHP_WRITE_STDOUT
long ret;
@ -153,7 +145,12 @@ static inline size_t sapi_cgibin_single_write(const char *str, uint str_length)
#ifdef PHP_FASTCGI
if (!FCGX_IsCGI()) {
return FCGX_PutStr( str, str_length, out );
FCGX_Request *request = (FCGX_Request *)SG(server_context);
long ret = FCGX_PutStr( str, str_length, request->out );
if (ret <= 0) {
return 0;
}
return ret;
}
#endif
#ifdef PHP_WRITE_STDOUT
@ -174,7 +171,7 @@ static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC)
while (remaining > 0)
{
ret = sapi_cgibin_single_write(ptr, remaining);
ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);
if (!ret) {
php_handle_aborted_connection();
}
@ -190,7 +187,8 @@ static void sapi_cgibin_flush(void *server_context)
{
#ifdef PHP_FASTCGI
if (!FCGX_IsCGI()) {
if( FCGX_FFlush( out ) == -1 ) {
FCGX_Request *request = (FCGX_Request *)server_context;
if(!request || FCGX_FFlush( request->out ) == -1 ) {
php_handle_aborted_connection();
}
} else
@ -244,7 +242,8 @@ static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
while (read_bytes < count_bytes) {
#ifdef PHP_FASTCGI
if (!FCGX_IsCGI()) {
tmp_read_bytes = FCGX_GetStr( pos, count_bytes-read_bytes, in );
FCGX_Request *request = (FCGX_Request *)SG(server_context);
tmp_read_bytes = FCGX_GetStr( pos, count_bytes-read_bytes, request->in );
pos += tmp_read_bytes;
} else
#endif
@ -258,12 +257,55 @@ static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
return read_bytes;
}
static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC)
{
#ifdef PHP_FASTCGI
/* when php is started by mod_fastcgi, no regular environment
is provided to PHP. It is always sent to PHP at the start
of a request. So we have to do our own lookup to get env
vars. This could probably be faster somehow. */
if (!FCGX_IsCGI()) {
int cgi_env_size = 0;
FCGX_Request *request = (FCGX_Request *)SG(server_context);
while( request->envp[ cgi_env_size ] ) {
if (strnicmp(name,request->envp[cgi_env_size],name_len) == 0) {
return (request->envp[cgi_env_size])+name_len+1;
}
cgi_env_size++;
}
}
#endif
/* if cgi, or fastcgi and not found in fcgi env
check the regular environment */
return getenv(name);
}
static char *sapi_cgi_read_cookies(TSRMLS_D)
{
return getenv("HTTP_COOKIE");
return sapi_cgibin_getenv((char *)"HTTP_COOKIE",strlen("HTTP_COOKIE") TSRMLS_CC);
}
#ifdef PHP_FASTCGI
void cgi_php_import_environment_variables(zval *array_ptr TSRMLS_DC)
{
if (!FCGX_IsCGI()) {
FCGX_Request *request = (FCGX_Request *)SG(server_context);
char **env, *p, *t;
for (env = request->envp; env != NULL && *env != NULL; env++) {
p = strchr(*env, '=');
if (!p) { /* malformed entry? */
continue;
}
t = estrndup(*env, p - *env);
php_register_variable(t, p+1, array_ptr TSRMLS_CC);
efree(t);
}
}
/* call php's original import as a catch-all */
php_php_import_environment_variables(array_ptr TSRMLS_CC);
}
#endif
static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
{
@ -271,7 +313,6 @@ static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
* variables
*/
php_import_environment_variables(track_vars_array TSRMLS_CC);
/* Build the special-case PHP_SELF variable for the CGI version */
php_register_variable("PHP_SELF", (SG(request_info).request_uri ? SG(request_info).request_uri:""), track_vars_array TSRMLS_CC);
}
@ -308,10 +349,11 @@ static int php_cgi_startup(sapi_module_struct *sapi_module)
/* {{{ sapi_module_struct cgi_sapi_module
*/
static sapi_module_struct cgi_sapi_module = {
"cgi", /* name */
#ifdef PHP_FASTCGI
"cgi-fcgi", /* name */
"CGI/FastCGI", /* pretty name */
#else
"cgi", /* name */
"CGI", /* pretty name */
#endif
@ -324,7 +366,7 @@ static sapi_module_struct cgi_sapi_module = {
sapi_cgibin_ub_write, /* unbuffered write */
sapi_cgibin_flush, /* flush */
NULL, /* get uid */
NULL, /* getenv */
sapi_cgibin_getenv, /* getenv */
php_error, /* error handler */
@ -366,6 +408,9 @@ static void php_cgi_usage(char *argv0)
" -v Version number\n"
" -C Do not chdir to the script's directory\n"
" -c <path>|<file> Look for php.ini file in this directory\n"
#ifdef PHP_FASTCGI
" -b <address:port>|<port> Bind Path for external FASTCGI Server mode\n"
#endif
" -a Run interactively\n"
" -d foo[=bar] Define INI entry foo with value 'bar'\n"
" -e Generate extended information for debugger/profiler\n"
@ -381,8 +426,8 @@ static void php_cgi_usage(char *argv0)
*/
static void init_request_info(TSRMLS_D)
{
char *content_length = getenv("CONTENT_LENGTH");
char *content_type = getenv("CONTENT_TYPE");
char *content_length = sapi_cgibin_getenv("CONTENT_LENGTH",strlen("CONTENT_LENGTH") TSRMLS_CC);
char *content_type = sapi_cgibin_getenv("CONTENT_TYPE",strlen("CONTENT_TYPE") TSRMLS_CC);
const char *auth;
#if 0
@ -391,7 +436,7 @@ static void init_request_info(TSRMLS_D)
char *script_filename;
script_filename = getenv("SCRIPT_FILENAME");
script_filename = sapi_cgibin_getenv("SCRIPT_FILENAME",strlen("SCRIPT_FILENAME") TSRMLS_CC);
/* Hack for annoying servers that do not set SCRIPT_FILENAME for us */
if (!script_filename) {
script_filename = SG(request_info).argv0;
@ -403,7 +448,7 @@ static void init_request_info(TSRMLS_D)
requires we get the info from path translated. This can be removed at
such a time that apache nt is fixed */
if (!script_filename) {
script_filename = getenv("PATH_TRANSLATED");
script_filename = sapi_cgibin_getenv("PATH_TRANSLATED",strlen("PATH_TRANSLATED") TSRMLS_CC);
}
#endif
@ -424,11 +469,11 @@ static void init_request_info(TSRMLS_D)
#endif /* 0 */
SG(request_info).request_method = getenv("REQUEST_METHOD");
SG(request_info).query_string = getenv("QUERY_STRING");
SG(request_info).request_uri = getenv("PATH_INFO");
SG(request_info).request_method = sapi_cgibin_getenv("REQUEST_METHOD",strlen("REQUEST_METHOD") TSRMLS_CC);
SG(request_info).query_string = sapi_cgibin_getenv("QUERY_STRING",strlen("QUERY_STRING") TSRMLS_CC);
SG(request_info).request_uri = sapi_cgibin_getenv("PATH_INFO",strlen("PATH_INFO") TSRMLS_CC);
if (!SG(request_info).request_uri) {
SG(request_info).request_uri = getenv("SCRIPT_NAME");
SG(request_info).request_uri = sapi_cgibin_getenv("SCRIPT_NAME",strlen("SCRIPT_NAME") TSRMLS_CC);
}
SG(request_info).path_translated = NULL; /* we have to update it later, when we have that information */
SG(request_info).content_type = (content_type ? content_type : "" );
@ -436,7 +481,7 @@ static void init_request_info(TSRMLS_D)
SG(sapi_headers).http_response_code = 200;
/* The CGI RFC allows servers to pass on unvalidated Authorization data */
auth = getenv("HTTP_AUTHORIZATION");
auth = sapi_cgibin_getenv("HTTP_AUTHORIZATION",strlen("HTTP_AUTHORIZATION") TSRMLS_CC);
php_handle_auth_data(auth TSRMLS_CC);
}
/* }}} */
@ -506,30 +551,16 @@ int main(int argc, char *argv[])
#endif
#ifdef PHP_FASTCGI
int env_size, cgi_env_size;
int max_requests = 500;
int requests = 0;
int fastcgi = !FCGX_IsCGI();
char *bindpath = NULL;
int fcgi_fd = 0;
FCGX_Request request;
#ifdef PHP_WIN32
int impersonate = 0;
#endif
if (fastcgi) {
/* Calculate environment size */
env_size = 0;
while( environ[ env_size ] ) { env_size++; }
/* Also include the final NULL pointer */
env_size++;
/* Allocate for our environment */
orig_env = malloc( env_size * sizeof( char *));
if( !orig_env ) {
perror( "Can't malloc environment" );
exit( 1 );
}
memcpy( orig_env, environ, env_size * sizeof( char *));
}
#endif
#endif /* PHP_FASTCGI */
#ifdef HAVE_SIGNAL_H
#if defined(SIGPIPE) && defined(SIG_IGN)
@ -556,7 +587,9 @@ int main(int argc, char *argv[])
setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */
#endif
#ifdef PHP_FASTCGI
if (!fastcgi) {
#endif
/* Make sure we detect we are a cgi - a bit redundancy here,
but the default case is that we have to check only the first one. */
if (getenv("SERVER_SOFTWARE")
@ -570,6 +603,9 @@ int main(int argc, char *argv[])
argv0 = NULL;
}
}
#ifdef PHP_FASTCGI
}
#endif
if (!cgi
#ifdef PHP_FASTCGI
@ -588,6 +624,24 @@ int main(int argc, char *argv[])
ap_php_optarg = orig_optarg;
}
#ifdef PHP_FASTCGI
if (!cgi && !fastcgi) {
/* if we're started on command line, check to see if
we are being started as an 'external' fastcgi
server by accepting a bindpath parameter. */
while ((c=ap_php_getopt(argc, argv, OPTSTRING))!=-1) {
switch (c) {
case 'b':
bindpath= strdup(ap_php_optarg);
break;
}
}
ap_php_optind = orig_optind;
ap_php_optarg = orig_optarg;
}
#endif
#ifdef ZTS
compiler_globals = ts_resource(compiler_globals_id);
@ -648,15 +702,46 @@ consult the installation file that came with this distribution, or visit \n\
#endif /* FORCE_CGI_REDIRECT */
#ifdef PHP_FASTCGI
/* How many times to run PHP scripts before dying */
if( getenv( "PHP_FCGI_MAX_REQUESTS" )) {
max_requests = atoi( getenv( "PHP_FCGI_MAX_REQUESTS" ));
if( !max_requests ) {
fprintf( stderr,
"PHP_FCGI_MAX_REQUESTS is not valid\n" );
exit( 1 );
if (bindpath) {
/* Pass on the arg to the FastCGI library, with one exception.
* If just a port is specified, then we prepend a ':' onto the
* path (it's what the fastcgi library expects)
*/
int port = atoi( bindpath );
if( port ) {
char bindport[ 32 ];
snprintf( bindport, 32, ":%s", bindpath );
fcgi_fd = FCGX_OpenSocket( bindport, 128 );
} else {
fcgi_fd = FCGX_OpenSocket( bindpath, 128 );
}
if( fcgi_fd < 0 ) {
fprintf( stderr, "Couldn't create FastCGI listen socket on port %s\n", bindpath);
#ifdef ZTS
tsrm_shutdown();
#endif
return FAILURE;
}
fastcgi = !FCGX_IsCGI();
}
if (fastcgi) {
/* How many times to run PHP scripts before dying */
if( getenv( "PHP_FCGI_MAX_REQUESTS" )) {
max_requests = atoi( getenv( "PHP_FCGI_MAX_REQUESTS" ));
if( !max_requests ) {
fprintf( stderr,
"PHP_FCGI_MAX_REQUESTS is not valid\n" );
return FAILURE;
}
}
/* make php call us to get _ENV vars */
php_php_import_environment_variables = php_import_environment_variables;
php_import_environment_variables = cgi_php_import_environment_variables;
/* library is already initialized, now init our request */
FCGX_Init();
FCGX_InitRequest( &request, fcgi_fd, 0 );
#ifndef PHP_WIN32
/* Pre-fork, if required */
@ -665,7 +750,7 @@ consult the installation file that came with this distribution, or visit \n\
if( !children ) {
fprintf( stderr,
"PHP_FCGI_CHILDREN is not valid\n" );
exit( 1 );
return FAILURE;
}
}
@ -734,8 +819,8 @@ consult the installation file that came with this distribution, or visit \n\
}
#endif /* WIN32 */
#endif
}
#endif /* FASTCGI */
zend_first_try {
if (!cgi
@ -762,6 +847,7 @@ consult the installation file that came with this distribution, or visit \n\
#ifdef PHP_FASTCGI
/* start of FAST CGI loop */
/* Initialise FastCGI request structure */
#ifdef PHP_WIN32
/* attempt to set security impersonation for fastcgi
@ -775,26 +861,15 @@ consult the installation file that came with this distribution, or visit \n\
#endif
while (!fastcgi
|| FCGX_Accept( &in, &out, &err, &cgi_env ) >= 0) {
if (fastcgi) {
/* set up environment */
cgi_env_size = 0;
while( cgi_env[ cgi_env_size ] ) { cgi_env_size++; }
merge_env = malloc( (env_size+cgi_env_size)*sizeof(char*) );
if( !merge_env ) {
perror( "Can't malloc environment" );
exit( 1 );
}
memcpy( merge_env, orig_env, (env_size-1)*sizeof(char *) );
memcpy( merge_env + env_size - 1,
cgi_env, (cgi_env_size+1)*sizeof(char *) );
environ = merge_env;
}
|| FCGX_Accept_r( &request ) >= 0) {
#endif
init_request_info(TSRMLS_C);
#ifdef PHP_FASTCGI
SG(server_context) = (void *) &request;
#else
SG(server_context) = (void *) 1; /* avoid server_context==NULL checks */
#endif
init_request_info(TSRMLS_C);
SG(request_info).argv0 = argv0;
@ -1009,9 +1084,9 @@ consult the installation file that came with this distribution, or visit \n\
*/
char *env_path_translated=NULL;
#if DISCARD_PATH
env_path_translated = getenv("SCRIPT_FILENAME");
env_path_translated = sapi_cgibin_getenv("SCRIPT_FILENAME",strlen("SCRIPT_FILENAME") TSRMLS_CC);
#else
env_path_translated = getenv("PATH_TRANSLATED");
env_path_translated = sapi_cgibin_getenv("PATH_TRANSLATED",strlen("PATH_TRANSLATED") TSRMLS_CC);
#endif
if(env_path_translated) {
#ifdef __riscos__
@ -1116,19 +1191,13 @@ consult the installation file that came with this distribution, or visit \n\
#ifdef PHP_FASTCGI
if (!fastcgi) break;
/* only fastcgi will get here */
/* TODO: We should free our environment here, but
* some platforms are unhappy if they've altered our
* existing environment and we then free() the new
* environ pointer
*/
requests++;
if( max_requests && ( requests == max_requests )) {
FCGX_Finish();
FCGX_Finish_r(&request);
if (bindpath) free (bindpath);
break;
}
/* end of fastcgi loop */
/* end of fastcgi loop */
}
#endif