Project

General

Profile

FcgiStatAccelWithMoreHttpHeaders » History » Revision 7

Revision 6 (stbuehler, 2009-03-23 12:12) → Revision 7/8 (stbuehler, 2012-08-11 10:42)

h2. Intro 

 Fobax and darix built a nice hack that implements non-blocking stat() system calls, which is very useful for lighty < 1.5. 
 [[HowtoSpeedUpStatWithFastcgi]] 

 However, where we previously used mod_expire to dump caching related directives in our http headers, we now have to do this manually. 

 Therefore we created this extended version of fcgi-stat-accel. 

 h2. Changes 

 We do a stat() call to retrieve the needed information to generate Expires, max-age, Etag and Last-Modified headers.    We therefore could also remove the fopen() and fclose() system calls. 
 (I'm led to believe that stat() is better suited then fopen()+fclose() for a task like this anyway) 
 Currently we didn't implement checking whether the file is readable or not, as we don't need this, maybe you do .. 

 Some things are hardcoded ( eg only use ETAG_USE_SIZE not MTIME or INODE ) and the expiration time.    Adapt if needed 

 h2. The code 

 <pre><code lang="c"> 
 /* 
   Originally written by Fobax. 
   Edited by darix to support controlling thread count at runtime.  
   Edited by poison and Dieter_be to support some http headers derived from the files. 

   Please do not remove any of the above. 
  
   compile with:  

   $ gcc -lfcgi -lpthread fcgi-stat-accel.c -o fcgi-stat-accel 

   fcgi-stat-accel will use the PHP_FCGI_CHILDREN environment variable to set the thread count. 

   The default value, if spawned from lighttpd, is 20. 
 */ 

 #include "fcgi_config.h" 

 #include <pthread.h> 
 #include <sys/types.h> 
 #include <unistd.h>    
 #include "fcgiapp.h"   
 #include <string.h>    
 #include <sys/types.h> 
 #include <sys/stat.h>  

 #include <stdlib.h> 
 #include <stdio.h>  

 #include "etag.h" 
 #include "buffer.h" 


 #define THREAD_COUNT 20 


 #define FORBIDDEN(stream) \ 
         FCGX_FPrintF(stream, "Status: 403 Forbidden\r\nContent-Type: text/html\r\n\r\n<h1>403 Forbidden</h1>\n"); 
 #define NOTFOUND(stream, filename) \ 
         FCGX_FPrintF(stream, "Status: 404 Not Found\r\nContent-Type: text/html\r\n\r\n<h1>404 Not Found</h1>\r\n%s", filename); 
 #define SENDFILE(stream, filename, headers) \ 
         FCGX_FPrintF(stream, "%sX-LIGHTTPD-send-file: %s\r\n\r\n", headers, filename); 

 #define EXPIRATION_TIME (int) 60*60*24*30 


 int genheaders (char* mybuffer, size_t bufferlen, const char* file) 
 { 
         char timebuf[32]; //possibly unsafe    
         char lastmodbuf[32]; //possibly unsafe 
         char etag[128]; //possibly unsafe 
         struct stat statbuf; 
         time_t exp; 
         time_t lastmod;   
         buffer *etag_raw; 
         buffer *etag_ok ; 
 
         //create buffers for Etag 
         etag_raw = buffer_init(); 
         etag_ok = buffer_init(); 
                
         // Stat the file 
         if (stat (file, &statbuf) != 0) 
         { 
                 return -1; 
         } 

         // Clear the buffer 
         memset (mybuffer, 0, bufferlen); 

         // Get the local time 
         exp = time (NULL) + EXPIRATION_TIME; 
         lastmod = statbuf.st_mtime; 

         strftime (timebuf, (sizeof (timebuf) / sizeof (char)) - 1, "%a, %d %b %Y %H:%M:%S GMT", gmtime (&(exp))); 
         strftime (lastmodbuf, (sizeof (lastmodbuf) / sizeof (char)) - 1, "%a, %d %b %Y %H:%M:%S GMT", gmtime (&(lastmod))); 

         etag_create(etag_raw, &statbuf, ETAG_USE_SIZE); 
         etag_mutate(etag_ok, etag_raw); 

         buffer_free(etag_raw); 

         snprintf (mybuffer, bufferlen, "Cache-Control: max-age=%d\r\nETag: \%s\r\nExpires: %s\r\nLast-Modified: %s\r\n", EXPIRATION_TIME, etag_ok->ptr, timebuf , lastmodbuf); 

         buffer_free(etag_ok); 

         return 0; 
 } 
 
 static void *doit(void *a){ 
         FCGX_Request request; 
         int rc; 
         char *filename; 
         char extraheaders[192]; 
         int r; 

         FCGX_InitRequest(&request, 0, FCGI_FAIL_ACCEPT_ON_INTR); 

         while(1){ 
                 //Some platforms require accept() serialization, some don't. The documentation claims it to be thread safe 
 //                static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER; 
 //                pthread_mutex_lock(&accept_mutex); 
                 rc = FCGX_Accept_r(&request); 
 //                pthread_mutex_unlock(&accept_mutex); 

                 if(rc < 0) 
                         break; 

         //get the filename 
                 if((filename = FCGX_GetParam("SCRIPT_FILENAME", request.envp)) == NULL){ 
                         FORBIDDEN(request.out); 
         //don't try to open directories 
                 }else if(filename[strlen(filename)-1] == '/'){ 
                         FORBIDDEN(request.out); 
         //open the file 
                 }else if((r = genheaders (extraheaders, 191, filename)) != 0){ 
                         NOTFOUND(request.out, filename); 
         //no error, serve it 
                 }else{ 
                         SENDFILE(request.out, filename, extraheaders); 
                 } 

                 FCGX_Finish_r(&request); 
         } 
         return NULL; 
 } 

 int main(void){ 
         int i,j,thread_count; 
         pthread_t* id; 
         char* env_val; 

         FCGX_Init(); 

         thread_count = THREAD_COUNT; 
         env_val = getenv("PHP_FCGI_CHILDREN"); 
         if (env_val != NULL) { 
                 j = atoi(env_val); 
                 if (j != 0) { 
                         thread_count = j; 
                 }; 
         }; 

         id = malloc(sizeof(*id) * thread_count); 

         for (i = 0; i < thread_count; i++) { 
                 pthread_create(&id[i], NULL, doit, NULL); 
         } 

         doit(NULL); 
         free(id);   
         return 0;   
 } 
 </code></pre> 



 h2. Final words 

 Thanks for lighty, it is great !  

 ..and so is fcgi-stat-accel !  

 If you have any remarks or questions, feel free to append to this page