--- /dev/null
+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.
#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
*/
* Process group
*/
static pid_t pgroup;
-
+#endif
#endif
#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;
#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
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();
}
{
#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
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
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)
{
* 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);
}
/* {{{ 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
sapi_cgibin_ub_write, /* unbuffered write */
sapi_cgibin_flush, /* flush */
NULL, /* get uid */
- NULL, /* getenv */
+ sapi_cgibin_getenv, /* getenv */
php_error, /* error handler */
" -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"
*/
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
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;
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
#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 : "" );
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);
}
/* }}} */
#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)
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")
argv0 = NULL;
}
}
+#ifdef PHP_FASTCGI
+ }
+#endif
if (!cgi
#ifdef PHP_FASTCGI
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);
#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 */
if( !children ) {
fprintf( stderr,
"PHP_FCGI_CHILDREN is not valid\n" );
- exit( 1 );
+ return FAILURE;
}
}
}
#endif /* WIN32 */
-
-#endif
+ }
+#endif /* FASTCGI */
zend_first_try {
if (!cgi
#ifdef PHP_FASTCGI
/* start of FAST CGI loop */
+ /* Initialise FastCGI request structure */
#ifdef PHP_WIN32
/* attempt to set security impersonation for fastcgi
#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;
*/
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__
#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