shane Sun Oct 13 05:40:44 2002 EDT Added files: /php4/sapi/cgi README.FastCGI
Modified files: /php4/sapi/cgi CREDITS cgi_main.c Log: 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
Index: php4/sapi/cgi/CREDITS diff -u php4/sapi/cgi/CREDITS:1.1 php4/sapi/cgi/CREDITS:1.2 --- php4/sapi/cgi/CREDITS:1.1 Mon Nov 20 05:31:08 2000 +++ php4/sapi/cgi/CREDITS Sun Oct 13 05:40:44 2002 @@ -1,2 +1,2 @@ -CGI -Rasmus Lerdorf, Stig Bakken +CGI / FastCGI +Rasmus Lerdorf, Stig Bakken, Shane Caraveo Index: php4/sapi/cgi/cgi_main.c diff -u php4/sapi/cgi/cgi_main.c:1.182 php4/sapi/cgi/cgi_main.c:1.183 --- php4/sapi/cgi/cgi_main.c:1.182 Mon Sep 23 07:35:22 2002 +++ php4/sapi/cgi/cgi_main.c Sun Oct 13 05:40:44 2002 @@ -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 @@ * Process group */ static pid_t pgroup; - +#endif #endif @@ -143,7 +135,7 @@ #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 @@ #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 @@ 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 @@ { #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 @@ 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 @@ 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 @@ * 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 @@ /* {{{ 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 @@ 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 @@ " -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 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 @@ 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 @@ 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 @@ #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 @@ 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 @@ #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 @@ 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 @@ argv0 = NULL; } } +#ifdef PHP_FASTCGI + } +#endif if (!cgi #ifdef PHP_FASTCGI @@ -588,6 +624,24 @@ 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 @@ #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 @@ if( !children ) { fprintf( stderr, "PHP_FCGI_CHILDREN is not valid\n" ); - exit( 1 ); + return FAILURE; } } @@ -734,8 +819,8 @@ } #endif /* WIN32 */ - -#endif + } +#endif /* FASTCGI */ zend_first_try { if (!cgi @@ -762,6 +847,7 @@ #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 @@ #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 @@ */ 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 @@ #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 Index: php4/sapi/cgi/README.FastCGI +++ php4/sapi/cgi/README.FastCGI 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.
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php