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

Reply via email to