From 69f56e8c4abb16e88f0810a286948c25dcdc8b9b Mon Sep 17 00:00:00 2001 From: Shane Caraveo Date: Sun, 13 Oct 2002 09:40:44 +0000 Subject: [PATCH] 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 --- sapi/cgi/CREDITS | 4 +- sapi/cgi/README.FastCGI | 126 ++++++++++++++++++++ sapi/cgi/cgi_main.c | 247 +++++++++++++++++++++++++--------------- 3 files changed, 286 insertions(+), 91 deletions(-) create mode 100644 sapi/cgi/README.FastCGI diff --git a/sapi/cgi/CREDITS b/sapi/cgi/CREDITS index 734f5c5a78..a1c6a0be59 100644 --- a/sapi/cgi/CREDITS +++ b/sapi/cgi/CREDITS @@ -1,2 +1,2 @@ -CGI -Rasmus Lerdorf, Stig Bakken +CGI / FastCGI +Rasmus Lerdorf, Stig Bakken, Shane Caraveo diff --git a/sapi/cgi/README.FastCGI b/sapi/cgi/README.FastCGI new file mode 100644 index 0000000000..6c865c4b8b --- /dev/null +++ b/sapi/cgi/README.FastCGI @@ -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/ + + Options ExecCGI + SetHandler fastcgi-script + + +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. diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index 7b276df557..88305f8613 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -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 | Look for php.ini file in this directory\n" +#ifdef PHP_FASTCGI + " -b | 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 -- 2.40.0