f41b982009-05-07Martin Stjernholm // This is a roxen module. Copyright © 2000 - 2009, Roxen IS.
42cd712001-09-03Martin Nilsson 
9da3d62000-01-25Per Hedbor inherit "cgi.pike": normalcgi;
0917d32013-03-04Anders Johansson constant cvs_version = "$Id$";
9da3d62000-01-25Per Hedbor  #include <roxen.h> #include <module.h> #if !defined(__NT__) && !defined(__AmigaOS__) # define UNIX 1 #else # define UNIX 0 #endif
425bcc2010-02-23Eiichiro ITANI // #define FCGI_ALL_DEBUG #ifdef FCGI_ALL_DEBUG # define FCGI_DEBUG # define FCGI_TRACE_FUNC # define FCGI_IO_DEBUG # define FCGI_THREAD_DEBUG # define FCGI_PACKET_DEBUG # define FCGI_PROCESS_DEBUG #endif #ifdef FCGI_DEBUG # define DWERR(X) werror(debug_output("FCGI",__LINE__,X)); #else /* !FCGI_DEBUG */ # define DWERR(X) #endif /* FCGI_DEBUG */ #ifdef FCGI_IO_DEBUG # define IO_DEBUG(X) DWERR(X) #else # define IO_DEBUG(X) #endif #ifdef FCGI_THREAD_DEBUG # define THREAD_DEBUG(X) DWERR(X) # define THIS_THREAD_ID sprintf("%d: ",Thread.this_thread()->id_number()) #else # define THREAD_DEBUG(X) # define THIS_THREAD_ID "" #endif #ifdef FCGI_TRACE_FUNC # define DTFUNC(X) werror(debug_output("FCGI_TRACE",__LINE__,sprintf("%s %O",X, this))) # define THIS_THREAD_ID sprintf("%d: ",Thread.this_thread()->id_number()) #else # define DTFUNC(X) #endif #ifdef FCGI_PACKET_DEBUG # define PACKET_DEBUG(X) DWERR(X) #else # define PACKET_DEBUG(X) #endif #ifdef FCGI_PROCESS_DEBUG # define PROCESS_DEBUG(X) DWERR(X) #else # define PROCESS_DEBUG(X) #endif //#define BACKTRACE_HERE(X) DWERR("######\n"+X+"\n"+describe_backtrace(backtrace())+"\n######\n")
55aee22000-04-29Per Hedbor constant module_unique = 0;
edc3242000-01-30Per Hedbor constant module_type = MODULE_LOCATION | MODULE_FILE_EXTENSION;
f7bd0d2001-03-03Per Hedbor constant module_name = "Scripting: Fast CGI support";
9da3d62000-01-25Per Hedbor constant module_doc = "Support for the <a href=\"http://www.fastcgi.com/\">Fast CGI 1 interface</a>";
edc3242000-01-30Per Hedbor #define FCGI_RESPONDER 1 #define FCGI_AUTHORIZER 2 #define FCGI_FILTER 3 #define FCGI_KEEP_CONN 1 #define FCGI_BEGIN_REQUEST 1 #define FCGI_ABORT_REQUEST 2 #define FCGI_END_REQUEST 3 #define FCGI_PARAMS 4 #define FCGI_STDIN 5 #define FCGI_STDOUT 6 #define FCGI_STDERR 7 #define FCGI_DATA 8 #define FCGI_GET_VALUES 9 #define FCGI_GET_VALUES_RESULT 10 #define FCGI_REQUEST_COMPLETE 0 #define FCGI_CANT_MPX_CONN 1 #define FCGI_OVERLOADED 2 #define FCGI_UNKNOWN_ROLE 3 #define MAX_FCGI_PREQ 1
9da3d62000-01-25Per Hedbor  class FCGIChannel {
fc40392008-08-15Martin Stjernholm  protected
9da3d62000-01-25Per Hedbor  { Stdio.File fd;
edc3242000-01-30Per Hedbor  array request_ids = allocate(MAX_FCGI_PREQ+1); array(mapping) stream = allocate(MAX_FCGI_PREQ+1); string buffer = "";
9da3d62000-01-25Per Hedbor  function default_cb;
5b08de2000-01-25Per Hedbor  function close_callback;
425bcc2010-02-23Eiichiro ITANI  // For debugging. Just to log thread id into debug message. mixed reader_thread,writer_thread;
9da3d62000-01-25Per Hedbor  void got_data( string f ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::got_data");
9da3d62000-01-25Per Hedbor  Packet p; buffer += f;
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(sprintf("Channel::got_data got strlen(%d)",strlen(f)));
9da3d62000-01-25Per Hedbor  while( (p = Packet( buffer ))->ready() ) {
425bcc2010-02-23Eiichiro ITANI  PACKET_DEBUG(sprintf("Packet: FCGIChannel::got_data() got :%O",p));
9da3d62000-01-25Per Hedbor  mapping s; buffer = p->get_leftovers(); if( stream[p->requestid] && (s = stream[ p->requestid ][ p->type ] ) ) { s->cb( p->data ); } else {
425bcc2010-02-23Eiichiro ITANI  if( request_ids[p->requestid] ) {
9da3d62000-01-25Per Hedbor  request_ids[p->requestid]( p );
425bcc2010-02-23Eiichiro ITANI  } else if( default_cb ) {
9da3d62000-01-25Per Hedbor  default_cb( p );
425bcc2010-02-23Eiichiro ITANI  }
9da3d62000-01-25Per Hedbor  } } } void read_thread() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::read_thread");
5b08de2000-01-25Per Hedbor  string s;
425bcc2010-02-23Eiichiro ITANI  while( (s = fd->read( 1024, 1 ) ) && strlen(s) ) {
9da3d62000-01-25Per Hedbor  got_data( s );
425bcc2010-02-23Eiichiro ITANI  }
5b08de2000-01-25Per Hedbor  catch(fd->close()); end_cb();
9da3d62000-01-25Per Hedbor  }
5b08de2000-01-25Per Hedbor  Thread.Condition cond = Thread.Condition();
7541392002-10-01Martin Stjernholm  Thread.Mutex cond_mutex = Thread.Mutex();
9da3d62000-01-25Per Hedbor  string wbuffer = ""; void write_thread() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::write_thread");
5b08de2000-01-25Per Hedbor  int ok=1; catch
9da3d62000-01-25Per Hedbor  {
5b08de2000-01-25Per Hedbor  while( 1 )
9da3d62000-01-25Per Hedbor  {
5b08de2000-01-25Per Hedbor  int written;
425bcc2010-02-23Eiichiro ITANI  while( strlen(wbuffer) ) {
5b08de2000-01-25Per Hedbor  if( fd ) { written = fd->write( wbuffer ); if( written <= 0 ) { ok=0;
0b63c32010-01-19Eiichiro ITANI  break;
5b08de2000-01-25Per Hedbor  } wbuffer = wbuffer[written..];
0b63c32010-01-19Eiichiro ITANI  } else {
5b08de2000-01-25Per Hedbor  ok=0;
0b63c32010-01-19Eiichiro ITANI  break; }
425bcc2010-02-23Eiichiro ITANI  }
7541392002-10-01Martin Stjernholm  if(!ok) break; Thread.MutexKey lock = cond_mutex->lock(); if (!sizeof (wbuffer)) cond->wait (lock); lock = 0;
9da3d62000-01-25Per Hedbor  }
5b08de2000-01-25Per Hedbor  }; catch(fd->close()); end_cb();
9da3d62000-01-25Per Hedbor  }
5b08de2000-01-25Per Hedbor  void do_setup_channels()
9da3d62000-01-25Per Hedbor  {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::do_setup_channels"); THREAD_DEBUG(sprintf("Thread Setting up read/write/close callbacks for FD: %O",fd));
5b08de2000-01-25Per Hedbor  fd->set_id( 0 ); fd->set_blocking();
425bcc2010-02-23Eiichiro ITANI  reader_thread = thread_create( read_thread ); THREAD_DEBUG(sprintf("created read_thread, thread id %O for ch %O", reader_thread->id_number(),this)); writer_thread = thread_create( write_thread ); THREAD_DEBUG(sprintf("created write_thread, thread id %O for ch %O", writer_thread->id_number(),this));
9da3d62000-01-25Per Hedbor  } void write( string what ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::write");
7541392002-10-01Martin Stjernholm  Thread.MutexKey lock = cond_mutex->lock();
9da3d62000-01-25Per Hedbor  wbuffer += what; cond->signal();
7541392002-10-01Martin Stjernholm  lock = 0;
9da3d62000-01-25Per Hedbor  }
5b08de2000-01-25Per Hedbor  void end_cb()
9da3d62000-01-25Per Hedbor  {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::end_cb");
5b08de2000-01-25Per Hedbor  if( fd ) {
425bcc2010-02-23Eiichiro ITANI  if( close_callback ) { PROCESS_DEBUG(sprintf("FCGIChannel close callback from end_cb(): %O, calling %O", this_object(),close_callback));
5b08de2000-01-25Per Hedbor  close_callback( this_object() );
425bcc2010-02-23Eiichiro ITANI  }
5b08de2000-01-25Per Hedbor  catch(fd->close()); foreach( values( stream )-({0}), mapping q ) foreach( values( q ), mapping q ) catch( function_object(q->cb)->close() ); fd = 0; }
9da3d62000-01-25Per Hedbor  }
fc40392008-08-15Martin Stjernholm  } /* end of protected */
5b08de2000-01-25Per Hedbor 
9da3d62000-01-25Per Hedbor 
5b08de2000-01-25Per Hedbor  void setup_channels() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::setup_channels");
5b08de2000-01-25Per Hedbor  do_setup_channels(); }
9da3d62000-01-25Per Hedbor  void send_packet( Packet p ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::send_packet"); PACKET_DEBUG(sprintf("Packet: sending packet to scipt <- %O", p));
9da3d62000-01-25Per Hedbor  write( (string)p ); } void set_close_callback( function to ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::set_close_callback");
9da3d62000-01-25Per Hedbor  close_callback = to; } void set_default_callback( function to ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::set_default_callback");
9da3d62000-01-25Per Hedbor  default_cb = to; } void free_requestid( int i ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::free_requestid");
9da3d62000-01-25Per Hedbor  request_ids[i]=0; stream[i]=([]); } int get_requestid( function f ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::get_requestid");
edc3242000-01-30Per Hedbor #if MAX_FCGI_PREQ == 1 if( request_ids[1] ) return -1; request_ids[1] = f; stream[1] = ([]); return 1; #else
5b08de2000-01-25Per Hedbor  for( int i = 1; i<sizeof( request_ids ); i++ )
9da3d62000-01-25Per Hedbor  if(!request_ids[i] ) { request_ids[i] = f; stream[i]=([]); return i; } return -1;
edc3242000-01-30Per Hedbor #endif
9da3d62000-01-25Per Hedbor  } int number_of_reqids() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::number_of_reqids"); int nr = sizeof( values( request_ids ) - ({0}) ); return nr;
9da3d62000-01-25Per Hedbor  }
425bcc2010-02-23Eiichiro ITANI 
9da3d62000-01-25Per Hedbor  void unregister_stream( int a, int b ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::unregister_stream"); PROCESS_DEBUG(sprintf("unregister_stream %d,%d for %O, stream is %O",a,b,this_object(),stream[b]));
9da3d62000-01-25Per Hedbor  m_delete( stream[b], a ); } void register_stream( int a, int b, function c, string n ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::register_stream");
9da3d62000-01-25Per Hedbor  stream[b][a] = (["cb":c, "name":n]); }
425bcc2010-02-23Eiichiro ITANI  void destroy() { DTFUNC("FCGIChannel::destroy"); }
9da3d62000-01-25Per Hedbor  void create( Stdio.File f ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIChannel::create"); IO_DEBUG(sprintf("Create FCGIChannel with FD:%O\n",f));
9da3d62000-01-25Per Hedbor  fd = f;
edc3242000-01-30Per Hedbor // setup_channels();
9da3d62000-01-25Per Hedbor  } } class Packet {
fc40392008-08-15Martin Stjernholm  protected
9da3d62000-01-25Per Hedbor  { int readyp; string leftovers;
fc40392008-08-15Martin Stjernholm  } /* end of protected */
9da3d62000-01-25Per Hedbor 
5b08de2000-01-25Per Hedbor  mixed cast( string to ) { if( to == "string" ) return encode(); }
9da3d62000-01-25Per Hedbor 
5b08de2000-01-25Per Hedbor  string _sprintf( int c ) { return sprintf("Packet(%d,%d,%d,%O)",type, requestid,contentlength,data); }
9da3d62000-01-25Per Hedbor  int version; int type; int requestid; int contentlength; string data; int ready() { return readyp; } string get_leftovers() { return leftovers; } string encode() {
8000db2000-04-26Frank Kemmer // int paddinglen = strlen(data)&8; int dLen = strlen(data); int eLen = (dLen + 7) & (0xFFFF - 7); // align to an 8-byte boundary int paddinglen = eLen - dLen;
425bcc2010-02-23Eiichiro ITANI  PACKET_DEBUG(sprintf("Packet: PADDING: %d", paddinglen));
8000db2000-04-26Frank Kemmer 
edc3242000-01-30Per Hedbor  return sprintf( "%c%c%2c%2c%c\0%s%s",1,type,requestid,strlen(data),paddinglen ,data, "X"*paddinglen);
9da3d62000-01-25Per Hedbor  } void create( string|int s, int|void r, string|void d ) { if( stringp( s ) ) { int paddinglen; if( strlen( s ) < 8 ) return;
edc3242000-01-30Per Hedbor  sscanf( s, "%c%c%2c%2c%c%*c" "%s",
9da3d62000-01-25Per Hedbor  version, type, requestid, contentlength, paddinglen,
5b08de2000-01-25Per Hedbor  /* reserved, */leftovers );
9da3d62000-01-25Per Hedbor  if( strlen( leftovers ) < contentlength + paddinglen ) return; data = leftovers[..contentlength-1]; leftovers = leftovers[contentlength + paddinglen..]; readyp = 1; } else { readyp = 1; version= 1; type = s; requestid = r; contentlength = strlen( d ); data = d;
425bcc2010-02-23Eiichiro ITANI  PACKET_DEBUG(sprintf("Packet: created packet %O",this_object()));
9da3d62000-01-25Per Hedbor  } } } class Stream { constant id = 0; constant name = "UNKNOWN"; int writer, closed;
fc40392008-08-15Martin Stjernholm  protected
9da3d62000-01-25Per Hedbor  { int reqid; FCGIChannel fd; string buffer = "";
5b08de2000-01-25Per Hedbor  function read_callback, close_callback, close_callback_2;
9da3d62000-01-25Per Hedbor  mixed fid;
7555712010-01-13Henrik Grubbström (Grubba)  // Note: buffer is accessed both from the read_thread, // and from other threads due to the implementation // of set_blocking() and set_nonblocking(). Thread.Mutex read_cb_mutex = Thread.Mutex(); #define LOCK() read_cb_mutex->lock() #define UNLOCK(KEY) destruct(key)
9da3d62000-01-25Per Hedbor  }
5b08de2000-01-25Per Hedbor  string _sprintf( ) { return sprintf("FCGI.Stream(%s,%d)", name, reqid); }
9da3d62000-01-25Per Hedbor  void destroy() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::destroy"); if( fd ) { IO_DEBUG(sprintf("Stream destroy(%O) fd %O",this_object(),fd));
5b08de2000-01-25Per Hedbor  fd->unregister_stream( id, reqid );
425bcc2010-02-23Eiichiro ITANI  }
9da3d62000-01-25Per Hedbor  }
7555712010-01-13Henrik Grubbström (Grubba)  protected void do_close(int level) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::do_close (level"+level+")"); IO_DEBUG(sprintf("Stream::do_close(%O) with level %d, close_callback is %O, close_callback_2 is %O\n", this_object(),level,close_callback,close_callback_2));
7555712010-01-13Henrik Grubbström (Grubba)  if (level == 2) { if( close_callback_2 ) { closed = 1; // Delay 1 second to ensure that any data is cleared first. call_out(close_callback_2, 1, this_object()); } } else { closed = 1; if (close_callback) { // Delay 1 second to ensure that any data is cleared first. call_out(close_callback, 1, fid); } } }
9da3d62000-01-25Per Hedbor  void close() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::close");
9da3d62000-01-25Per Hedbor  if( closed ) return;
edc3242000-01-30Per Hedbor  closed = 1;
5b08de2000-01-25Per Hedbor 
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(sprintf("FCGI::Stream(%s):close() %O closed, fd: %O",name,this_object(),fd )); if( writer ) { PACKET_DEBUG(sprintf("Packet: Stream(%s):close() sending close packet to script",name));
edc3242000-01-30Per Hedbor  fd->send_packet( Packet( id, reqid, "" ) );
425bcc2010-02-23Eiichiro ITANI  } else { PACKET_DEBUG(sprintf("Packet: Stream(%s):close() sending abort packet to script",name));
5b08de2000-01-25Per Hedbor  catch(fd->send_packet( packet_abort_request( reqid ) ));
425bcc2010-02-23Eiichiro ITANI  }
edc3242000-01-30Per Hedbor  catch {
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(sprintf("FCGI::Stream(%s)::close() calling with fid %O, close_callback %O", name,fid,close_callback));
5b08de2000-01-25Per Hedbor  if( close_callback ) close_callback( fid ); }; catch {
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(sprintf("FCGI::Stream(%s)::close() calling with %O, close_callback_2 %O", name,this_object(),close_callback_2));
5b08de2000-01-25Per Hedbor  if( close_callback_2 ) close_callback_2( fid ); };
9da3d62000-01-25Per Hedbor  } string read( int nbytes, int noblock ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::read"); IO_DEBUG(sprintf("%O::read(%d,%d)",this_object(),nbytes,noblock));
5b08de2000-01-25Per Hedbor  if(!nbytes) { if( noblock ) {
edc3242000-01-30Per Hedbor  while( !closed && !strlen( buffer ) ) sleep(0.1);
7555712010-01-13Henrik Grubbström (Grubba)  mixed key = LOCK();
5b08de2000-01-25Per Hedbor  string b = buffer; buffer="";
7555712010-01-13Henrik Grubbström (Grubba)  UNLOCK(key);
5b08de2000-01-25Per Hedbor  return b; } while( !closed ) sleep( 0.1 );
7555712010-01-13Henrik Grubbström (Grubba)  mixed key = LOCK();
5b08de2000-01-25Per Hedbor  string b = buffer; buffer=0;
7555712010-01-13Henrik Grubbström (Grubba)  UNLOCK(key);
5b08de2000-01-25Per Hedbor  return b; } if( !closed && !noblock )
9da3d62000-01-25Per Hedbor  { while( !closed && (strlen( buffer ) < nbytes) ) /* assume MT */ sleep( 0.1 ); }
edc3242000-01-30Per Hedbor  while( !closed && !strlen( buffer ) ) sleep(0.1);
7555712010-01-13Henrik Grubbström (Grubba)  mixed key = LOCK();
9da3d62000-01-25Per Hedbor  string b = buffer[..nbytes-1]; buffer = buffer[nbytes..];
7555712010-01-13Henrik Grubbström (Grubba)  UNLOCK(key);
9da3d62000-01-25Per Hedbor  return b; } int write( string data ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::write"); IO_DEBUG(sprintf("%O::write will write %d len string %O\n", this_object(),strlen(data),data));
9da3d62000-01-25Per Hedbor  if( closed ) error("Stream closed\n");
edc3242000-01-30Per Hedbor  if( !strlen( data ) ) return 0;
9da3d62000-01-25Per Hedbor  if( strlen( data ) < 65535 )
5b08de2000-01-25Per Hedbor  fd->send_packet( Packet( id, reqid, data ) );
9da3d62000-01-25Per Hedbor  else
5f7f272010-02-26Eiichiro ITANI  foreach( data / 8192.0, string d )
5b08de2000-01-25Per Hedbor  fd->send_packet( Packet( id, reqid, d ) );
9da3d62000-01-25Per Hedbor  return strlen(data); } void got_data( string d ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::got_data");
9da3d62000-01-25Per Hedbor  if( closed ) {
da0ef42010-04-08Henrik Grubbström (Grubba)  IO_DEBUG(sprintf("%O::got_data ***Got data for closed stream(%s)!***", this_object(),name));
9da3d62000-01-25Per Hedbor  return; }
425bcc2010-02-23Eiichiro ITANI  DWERR(sprintf("stream::got_data called with strlen %d",strlen(d)));
7555712010-01-13Henrik Grubbström (Grubba)  mixed key = LOCK();
edc3242000-01-30Per Hedbor  buffer += d;
7555712010-01-13Henrik Grubbström (Grubba)  UNLOCK(key);
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(sprintf("%O::got_data arg %O, curbuffer %O. calling read_callback: %O", this_object(),d,buffer,read_callback));
edc3242000-01-30Per Hedbor  if( read_callback )
9da3d62000-01-25Per Hedbor  {
edc3242000-01-30Per Hedbor  do_read_callback(); if( !strlen( d ) ) { /* EOS record. */
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(sprintf("%O::got_data passed null string, so close this stream", this_object()));
7555712010-01-13Henrik Grubbström (Grubba)  do_close(1);
edc3242000-01-30Per Hedbor  }
7555712010-01-13Henrik Grubbström (Grubba)  return;
9da3d62000-01-25Per Hedbor  }
425bcc2010-02-23Eiichiro ITANI  if( !strlen( d ) ) { IO_DEBUG(sprintf("%O::got_data passed null string, read_callback not exist, so close2 this stream", this_object()));
7555712010-01-13Henrik Grubbström (Grubba)  do_close(2);
425bcc2010-02-23Eiichiro ITANI  }
9da3d62000-01-25Per Hedbor  } void set_close_callback( function f ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::set_close_callback(%O)",this_object(),f)); IO_DEBUG(sprintf("%O::set_close_callback: %O",this_object(),f));
9da3d62000-01-25Per Hedbor  close_callback = f;
edc3242000-01-30Per Hedbor  if( f && closed )
7555712010-01-13Henrik Grubbström (Grubba)  do_close(1);
9da3d62000-01-25Per Hedbor  } void set_read_callback( function f ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::set_read_callback(%O)",this_object(),f)); IO_DEBUG(sprintf("%O::set_read_callback(%O)",this_object(),f));
9da3d62000-01-25Per Hedbor  read_callback = f;
edc3242000-01-30Per Hedbor  if( f && strlen( buffer ) )
9da3d62000-01-25Per Hedbor  do_read_callback(); }
5b08de2000-01-25Per Hedbor  void set_nonblocking( function a, function n, function w ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::set_nonblocking",this_object())); IO_DEBUG(sprintf("%O::set_nonblocking: a: %O w: %O", this_object(),a,w));
5b08de2000-01-25Per Hedbor  /* It is already rather nonblocking.. */
edc3242000-01-30Per Hedbor  set_read_callback( a ); set_close_callback( w );
5b08de2000-01-25Per Hedbor  } void set_second_close_callback(function q) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::set_second_close_callback(%O)",this_object(),q)); IO_DEBUG(sprintf("%O::set_second_close_callback: %O",this_object(),q));
5b08de2000-01-25Per Hedbor  close_callback_2 = q;
edc3242000-01-30Per Hedbor  if( closed )
7555712010-01-13Henrik Grubbström (Grubba)  do_close(2);
5b08de2000-01-25Per Hedbor  } void set_blocking() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::set_blocking",this_object()));
5b08de2000-01-25Per Hedbor  set_close_callback( 0 ); set_read_callback( 0 ); }
9da3d62000-01-25Per Hedbor  void set_id( mixed to ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::set_id(%O)",this_object(),to));
9da3d62000-01-25Per Hedbor  fid = to; }
7555712010-01-13Henrik Grubbström (Grubba)  // MUST be called from the backend thread! protected void really_do_read_callback() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC(sprintf("%O::really_do_read_callback",this_object())); IO_DEBUG(sprintf("%O::really_do_read_callback called fid: %O, callback is %O\n buffer: %d,", this_object(),fid,read_callback,strlen(buffer)));
7555712010-01-13Henrik Grubbström (Grubba)  mixed key = LOCK(); string data = buffer; buffer = ""; UNLOCK(key); if( strlen( data ) ) { read_callback(fid, data); } } // Called from: // FCGIChannel::got_data ==> got_data // FCGIChannel::read_thread/read_cb ==> FCGIChannel::got_data ==> got_data // set_read_callback // set_nonblocking ==> set_read_callback // set_blocking ==> set_read_callback // which may execute concurrently, which causes a race on buffer. // // We attempt to solve this by sequencing through the main backend.
9da3d62000-01-25Per Hedbor  void do_read_callback() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::do_read_callback");
edc3242000-01-30Per Hedbor  if( strlen( buffer ) ) {
7555712010-01-13Henrik Grubbström (Grubba)  call_out(really_do_read_callback, 0);
edc3242000-01-30Per Hedbor  }
9da3d62000-01-25Per Hedbor  } void create( FCGIChannel _fd, int requestid, int _writer ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Stream::create");
9da3d62000-01-25Per Hedbor  fd = _fd; writer = _writer; reqid = requestid; fd->register_stream( id, requestid, got_data, name ); } } string encode_param( string p ) { if( strlen( p ) < 127 ) return sprintf("%c%s", strlen(p), p ); p = sprintf( "%4c%s", strlen(p), p );
8000db2000-04-26Frank Kemmer  p[0] |= 128; } string encode_param_length(string p) { if( strlen( p ) < 128 ) return sprintf("%c", strlen(p)); else { p = sprintf( "%4c", strlen( p ) ); p[0] |= 128; return p; }
9da3d62000-01-25Per Hedbor } class Params { inherit Stream;
edc3242000-01-30Per Hedbor  constant id = FCGI_PARAMS;
9da3d62000-01-25Per Hedbor  constant name = "Params"; void write_mapping( mapping m )
8000db2000-04-26Frank Kemmer  {
9da3d62000-01-25Per Hedbor  string data = ""; foreach( indices( m ), string i )
8000db2000-04-26Frank Kemmer  data += encode_param_length((string)i) + encode_param_length(m[i]) + i + m[i];
9da3d62000-01-25Per Hedbor  write( data ); } } class Stdin { inherit Stream;
edc3242000-01-30Per Hedbor  constant id = FCGI_STDIN;
9da3d62000-01-25Per Hedbor  constant name = "Stdin"; } class Stdout { inherit Stream;
edc3242000-01-30Per Hedbor  constant id = FCGI_STDOUT;
9da3d62000-01-25Per Hedbor  constant name = "Stdout"; } class Stderr { inherit Stream;
edc3242000-01-30Per Hedbor  constant id = FCGI_STDERR;
425bcc2010-02-23Eiichiro ITANI  constant name = "Stderr";
9da3d62000-01-25Per Hedbor } /* server -> client */ Packet packet_begin_request( int requestid, int role, int flags ) { return Packet( FCGI_BEGIN_REQUEST, requestid, sprintf("%2c%c\0\0\0\0\0", role, flags ) ); } Packet packet_get_values( string ... values ) { string data = ""; foreach( values, string q ) data += encode_param( q ) + encode_param( "" ); return Packet( FCGI_GET_VALUES, 0, data ); } Packet packet_abort_request( int requestid ) {
5f7f272010-02-26Eiichiro ITANI  PACKET_DEBUG(sprintf("Abort request for %d",requestid));
9da3d62000-01-25Per Hedbor  return Packet( FCGI_ABORT_REQUEST, requestid, "" ); } class FCGIRun { int rid;
5b08de2000-01-25Per Hedbor  int is_done;
9da3d62000-01-25Per Hedbor  RequestID id; FCGIChannel parent; CGIScript me; Stream stdin, stdout, stderr; function done_callback;
5b08de2000-01-25Per Hedbor  class FakePID { int status() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIRun::FakePID::status"); PROCESS_DEBUG(sprintf("FCGIRun: Status check status is %d, parent %O and pid %d", is_done,parent,parent->attached_pid));
5b08de2000-01-25Per Hedbor  return is_done; }
edc3242000-01-30Per Hedbor  void kill( int with ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIRun:FakePID:kill");
edc3242000-01-30Per Hedbor  catch(stdout->close()); catch(stderr->close()); catch(stdin->close()); is_done = 1; }
5b08de2000-01-25Per Hedbor  } FakePID fake_pid() { return FakePID(); } void done() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIRun::done"); PROCESS_DEBUG(sprintf("FCGIRun: done for rid %d, parent:%O, calling %O", rid,parent,done_callback));
5b08de2000-01-25Per Hedbor  parent->free_requestid( rid ); if( done_callback ) done_callback( this_object() ); is_done = 1; }
9da3d62000-01-25Per Hedbor  void handle_packet( Packet p ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIRun::handle_packet");
9da3d62000-01-25Per Hedbor  /* stdout / stderr routed to the above streams.. */ if( p->type == FCGI_END_REQUEST )
edc3242000-01-30Per Hedbor  {
5b08de2000-01-25Per Hedbor  done();
edc3242000-01-30Per Hedbor  }
425bcc2010-02-23Eiichiro ITANI  else {
511ab42001-08-24Leif Stensson  werror(" Unexpected FCGI packet: %O\n", p );
425bcc2010-02-23Eiichiro ITANI  }
9da3d62000-01-25Per Hedbor  } void set_done_callback( function to ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIRun::set_done_callback");
9da3d62000-01-25Per Hedbor  done_callback = to; }
425bcc2010-02-23Eiichiro ITANI  void destroy () { DTFUNC("FCGIRun::destroy"); PROCESS_DEBUG(sprintf("FCGIRun::destroy called, I'm %O",me)); }
9da3d62000-01-25Per Hedbor  void create( RequestID i, FCGIChannel p, CGIScript c ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGIRun::create");
9da3d62000-01-25Per Hedbor  Params params; me = c; rid = p->get_requestid( handle_packet );
5b08de2000-01-25Per Hedbor  parent = p;
9da3d62000-01-25Per Hedbor  /* Now _this_ is rather ugly... */
5b08de2000-01-25Per Hedbor  if( rid == -1 )
9da3d62000-01-25Per Hedbor  { werror("Warning: FCGI out of request IDs for script\n"); call_out( create, 1, i, p ); return; }
5b08de2000-01-25Per Hedbor 
9da3d62000-01-25Per Hedbor  stdin = Stdin( p, rid, 1); stdout = Stdout( p, rid, 0); stderr = Stderr( p, rid, 0); params = Params( p, rid, 1);
5b08de2000-01-25Per Hedbor  stdout->set_second_close_callback( done );
9da3d62000-01-25Per Hedbor  parent->send_packet( packet_begin_request( rid, FCGI_RESPONDER, FCGI_KEEP_CONN ) ); params->write_mapping( c->environment );
8000db2000-04-26Frank Kemmer  params->close();
9da3d62000-01-25Per Hedbor  } } class FCGI {
fc40392008-08-15Martin Stjernholm  protected
9da3d62000-01-25Per Hedbor  { Stdio.Port socket; array all_pids = ({}); mapping options = ([]); array argv;
5b08de2000-01-25Per Hedbor  array(FCGIChannel) channels = ({});
9da3d62000-01-25Per Hedbor  int current_conns;
5b08de2000-01-25Per Hedbor  void do_connect( object fd, mixed|void q ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGI::do_connect"); THREAD_DEBUG(sprintf("FCGI::do_connect with fd: %O, q: %O\n",fd,q)); IO_DEBUG(" Connecting...\n" );
5f7f272010-02-26Eiichiro ITANI  while( !fd->connect( "localhost",(int)(socket->query_address()/" ")[1]) )
5b08de2000-01-25Per Hedbor  {
425bcc2010-02-23Eiichiro ITANI  IO_DEBUG(" Connection failed...\n" );
5b08de2000-01-25Per Hedbor  sleep( 0.1 ); } q();
425bcc2010-02-23Eiichiro ITANI  THREAD_DEBUG("FCGI::do_connect done");
5b08de2000-01-25Per Hedbor  }
9da3d62000-01-25Per Hedbor  FCGIChannel new_channel( ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGI::new_channel"); PROCESS_DEBUG(sprintf("FCGI::new_channel all_process is :%O\n options: %O\n",all_pids,options));
5b08de2000-01-25Per Hedbor  while( current_conns >= (options->MAX_CONNS*sizeof(all_pids)) )
9da3d62000-01-25Per Hedbor  start_new_script(); current_conns++; Stdio.File fd = Stdio.File();
5b08de2000-01-25Per Hedbor  fd->open_socket(); FCGIChannel ch = FCGIChannel( fd ); fd->set_blocking();
425bcc2010-02-23Eiichiro ITANI  mixed th; th = thread_create( do_connect, fd, ch->setup_channels ); THREAD_DEBUG(sprintf("%O::new_channel created thread id %O for do_connect()", this,th->id_number()));
9da3d62000-01-25Per Hedbor  channels += ({ ch });
5b08de2000-01-25Per Hedbor  ch->set_close_callback( lambda(object c) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("Channel close callback lambda"); PROCESS_DEBUG(sprintf("Close callback for %O called",c));
9da3d62000-01-25Per Hedbor  channels -= ({ c }); current_conns--; });
5b08de2000-01-25Per Hedbor  return ch;
9da3d62000-01-25Per Hedbor  }
425bcc2010-02-23Eiichiro ITANI  void destroy() { DTFUNC("FCGI::destroy"); }
9da3d62000-01-25Per Hedbor  void reaperman() {
425bcc2010-02-23Eiichiro ITANI  // Suppress logging when no process managed. if (sizeof(all_pids)) { DTFUNC("FCGI::reaperman"); } if (! this_object()) { PROCESS_DEBUG("*** reaperman, object is destructed already ***"); } PROCESS_DEBUG(sprintf("*** reaperman, current status is %O ***",all_pids));
9da3d62000-01-25Per Hedbor  call_out( reaperman, 1 ); foreach( all_pids, object p ) if( !p || p->status() ) // done all_pids -= ({ p }); } void start_new_script( ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGI::start_new_script"); PROCESS_DEBUG(sprintf("FCGI::start_new_script, argv: %O\n options: %O\n",argv,options));
92ced82011-09-12Henrik Grubbström (Grubba)  all_pids += ({ Process.Process( /*({ "/bin/truss"}) +*/ argv, options ) });
9da3d62000-01-25Per Hedbor  } string values_cache = ""; void parse_values( ) { while( strlen( values_cache ) ) { string index, value;
5b08de2000-01-25Per Hedbor  int len;
9da3d62000-01-25Per Hedbor  sscanf( values_cache, "%2c%s", len, values_cache ); index = values_cache[..len-1]; values_cache = values_cache[len..]; sscanf( values_cache, "%2c%s", len, values_cache ); value = values_cache[..len-1]; values_cache = values_cache[len..]; options[ index-"FCG_" ] = (int)value; options[ index ] = value;
425bcc2010-02-23Eiichiro ITANI  PACKET_DEBUG(sprintf("Packet: parse_value() %O == %O", index, value ));
9da3d62000-01-25Per Hedbor  } } void maintenance_packet( Packet p ) { switch( p->type ) { case FCGI_GET_VALUES_RESULT: if( strlen( p->data ) ) values_cache += p->data; else { parse_values(); values_cache = ""; } break; default: werror("FCGI: Unknown maintenance style package: %O\n", p ); } } void create( CGIScript s ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGI::create"); PROCESS_DEBUG(sprintf("FCGI::create with %O, fcgirun: %O",s,s->fcgi));
9da3d62000-01-25Per Hedbor  socket = Stdio.Port( 0, 0, "localhost" ); #ifdef __NT__ if( s->nt_opencommand ) argv = s->nt_opencommand( s->command, s->arguments ); else #endif argv = ({ s->command }) + s->arguments; options = ([ "stdin":socket, "cwd":dirname( s->command ), "noinitgroups":1, ]); #if UNIX if(!getuid()) { if (s->uid >= 0) options->uid = s->uid; else { // Some OS's (HPUX) have negative uids in /etc/passwd, // but don't like them in setuid() et al. // Remap them to the old 16bit uids.
5b08de2000-01-25Per Hedbor  options->uid = 0xffff & s->uid;
9da3d62000-01-25Per Hedbor  if (options->uid <= 10) { // Paranoia options->uid = 65534; } }
5b08de2000-01-25Per Hedbor  if (s->gid >= 0)
9da3d62000-01-25Per Hedbor  { options->gid = s->gid; } else { // Some OS's (HPUX) have negative gids in /etc/passwd, // but don't like them in setgid() et al. // Remap them to the old 16bit gids. options->gid = 0xffff & s->gid; if (options->gid <= 10) { // Paranoia options->gid = 65534; } } options->setgroups = s->extra_gids;
ed540b2001-01-13Martin Nilsson  if( !s->uid && query("warn_root_cgi") )
5b08de2000-01-25Per Hedbor  report_warning( "FCGI: Running "+s->command+" as root (as per request)" );
9da3d62000-01-25Per Hedbor  }
ed540b2001-01-13Martin Nilsson  if(query("nice"))
9da3d62000-01-25Per Hedbor  { m_delete(options, "priority");
ed540b2001-01-13Martin Nilsson  options->nice = query("nice");
9da3d62000-01-25Per Hedbor  } if( s->limits ) options->rlimit = s->limits; #endif
5b08de2000-01-25Per Hedbor  start_new_script();
9da3d62000-01-25Per Hedbor  options->MPXS_CONNS = 0; options->MAX_REQS = 1; options->MAX_CONNS = 1; /* sensible (for a stupid script) defaults */
edc3242000-01-30Per Hedbor #if MAX_FCGI_PREQ > 1 // This breaks the fastcgi library... *$#W(#$)#"$! // FCGIChannel c = stream(); // if(!c) // error( "Impossible!\n"); // c->set_default_callback( maintenance_packet ); // c->send_packet( packet_get_values("FCGI_MAX_CONNS", // "FCGI_MAX_REQS", // "FCGI_MPXS_CONNS") ); #endif
7555712010-01-13Henrik Grubbström (Grubba)  call_out(reaperman, 1);
9da3d62000-01-25Per Hedbor  }
7555712010-01-13Henrik Grubbström (Grubba) 
fc40392008-08-15Martin Stjernholm  } /* end of protected */
9da3d62000-01-25Per Hedbor  FCGIChannel stream() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("FCGI::stream");
edc3242000-01-30Per Hedbor  channels -= ({ 0 }); // Not really needed right now, since libfcgi with // friends does _not_ support multiplexing anyway. // // Also, see the comment above, we don't even try to get the // parameters. // // But the code is here, it might start working in libfcgi. // I rather doubt that, though. libfcgi must be the worst code // I have seen in quite some time...
5b08de2000-01-25Per Hedbor  //
edc3242000-01-30Per Hedbor #if MAX_FCGI_PREQ == 1 foreach( channels, FCGIChannel ch ) if( !ch->number_of_reqids() ) return ch; #else
425bcc2010-02-23Eiichiro ITANI  DWERR("*** Check for free channel ***");
edc3242000-01-30Per Hedbor  channels -= ({ 0 }); foreach( channels, FCGIChannel ch ) { if( catch { if( ch && options->MPXS_CONNS ) { if( ch->number_of_reqids() < options->MAX_REQS ) return ch; } else if( !ch->number_of_reqids() ) return ch; } ) channels -= ({ ch }); } #endif
425bcc2010-02-23Eiichiro ITANI  PROCESS_DEBUG("*** No free channel found, create new channel***"); return new_channel();
9da3d62000-01-25Per Hedbor  }
dfa9ad2010-01-19Eiichiro ITANI  // Just for debugging array show_all_pids() { return all_pids; } array show_channels() { return channels; }
9da3d62000-01-25Per Hedbor } mapping(string:FCGI) fcgis = ([]); FCGIRun do_fcgiscript( CGIScript f ) {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("fastcgi.pike:do_fcgiscript");
9da3d62000-01-25Per Hedbor  if( fcgis[ f->command ] ) return FCGIRun( f->mid, fcgis[ f->command ]->stream(), f );
425bcc2010-02-23Eiichiro ITANI  DWERR("do_fcgiscript: access to new script");
5b08de2000-01-25Per Hedbor  fcgis[ f->command ] = FCGI( f ); return do_fcgiscript( f );
9da3d62000-01-25Per Hedbor } class CGIScript {
5b08de2000-01-25Per Hedbor  inherit normalcgi::CGIScript;
9da3d62000-01-25Per Hedbor  int ready;
5b08de2000-01-25Per Hedbor  FCGIRun fcgi;
9da3d62000-01-25Per Hedbor  Stdio.File stdin; Stdio.File stdout;
425bcc2010-02-23Eiichiro ITANI  Stdio.File stderr;
9da3d62000-01-25Per Hedbor 
edc3242000-01-30Per Hedbor  Stdio.Stream get_fd()
5b08de2000-01-25Per Hedbor  {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("CGIScript::get_fd");
edc3242000-01-30Per Hedbor  // // Send input (if any) to the script. //
5b08de2000-01-25Per Hedbor  if( tosend ) stdin->write( tosend ); stdin->close(); stdin=0;
edc3242000-01-30Per Hedbor  //
5b08de2000-01-25Per Hedbor  // And then read the output.
edc3242000-01-30Per Hedbor  //
425bcc2010-02-23Eiichiro ITANI  PROCESS_DEBUG(sprintf("fastcgi::CGIScript::get_fd and then read the output from %O, blocking status is %O", fcgi->show_request_origin(),blocking));
5b08de2000-01-25Per Hedbor  if(!blocking) {
511ab42001-08-24Leif Stensson #ifdef FCGI_DEBUG
edc3242000-01-30Per Hedbor  werror( "***** Non-Blocking ******\n");
511ab42001-08-24Leif Stensson #endif
edc3242000-01-30Per Hedbor  Stdio.Stream fd = stdout; fd = CGIWrapper( fd, mid, kill_script )->get_fd();
ed540b2001-01-13Martin Nilsson  if( query("rxml") )
edc3242000-01-30Per Hedbor  fd = RXMLWrapper( fd, mid, kill_script )->get_fd();
5b08de2000-01-25Per Hedbor  stdout = 0; call_out( check_pid, 0.1 ); return fd; } remove_call_out( kill_script ); return stdout; } void done() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("CGIScript::done"); PROCESS_DEBUG(sprintf("fastcgi.pike::CGIScript::done close stderr %O for %O\n stdin: %O\n stdout: %O\n", stderr,fcgi,stdin,stdout));
5f7f272010-02-26Eiichiro ITANI  stderr->close();
9da3d62000-01-25Per Hedbor  } CGIScript run() {
425bcc2010-02-23Eiichiro ITANI  DTFUNC("CGIScript::run");
5b08de2000-01-25Per Hedbor  fcgi = do_fcgiscript( this_object() ); fcgi->set_done_callback( done );
edc3242000-01-30Per Hedbor  ready = 1; stdin = fcgi->stdin; stdout= fcgi->stdout;
425bcc2010-02-23Eiichiro ITANI  stderr= fcgi->stderr;
edc3242000-01-30Per Hedbor  pid = fcgi->fake_pid();
5b08de2000-01-25Per Hedbor  return this_object( );
9da3d62000-01-25Per Hedbor  } } // override some variables... void create(Configuration conf) { ::create( conf );
edc3242000-01-30Per Hedbor 
9da3d62000-01-25Per Hedbor  set("location", "/fcgi-bin/" );
edc3242000-01-30Per Hedbor 
9da3d62000-01-25Per Hedbor  defvar("ex", 1, "Handle *.fcgi", TYPE_FLAG, "Also handle all '.fcgi' files as FCGI-scripts, as well " " as files in the fcgi-bin directory."); defvar("ext", ({"fcgi",}), "FCGI-script extensions", TYPE_STRING_LIST, "All files ending with these extensions, will be parsed as "+ "FCGI-scripts.");
edc3242000-01-30Per Hedbor  killvar("cgi_tag");
9da3d62000-01-25Per Hedbor }
dfa9ad2010-01-19Eiichiro ITANI  string status() { string statmessage = "<h3>FastCGI object status</h3>\n"; foreach (fcgis; string cmd; FCGI f) { int count = 0; statmessage +=
d90fce2010-01-19Henrik Grubbström (Grubba)  "<h4>" + Roxen.html_encode_string(cmd) + "</h4>\n"
dfa9ad2010-01-19Eiichiro ITANI  "<pre>Object: " + Roxen.html_encode_string(sprintf("%O", f)) + "</pre>\n" "<h5>pids</h5>\n" "<ul>\n"; foreach (f->show_all_pids(),mixed p) statmessage += sprintf("<li>%d Pid: %d, status: %d</li>", ++count, p->pid(), p->status()); statmessage += "</ul>\n" "<h5>Channels</h5>\n" "<ul>\n"; count = 0; foreach (f->show_channels(),FCGIChannel ch) statmessage += sprintf("<li>%d %O<br /><pre>%O</pre></li>", ++count, ch, ch->request_ids); statmessage += "</ul>\n"; } return statmessage; }
425bcc2010-02-23Eiichiro ITANI  string debug_output (string heading, int line, string body) { array(string) r = body / "\n"; array bt = backtrace(); heading = sprintf(THIS_THREAD_ID+"%s(line:%4d):"+" "*sizeof(bt),heading,line); string sep = " "; string m = map(r,lambda(string t,string h) { string tmp = h + sep + t; sep = " "; return tmp; }, heading) * "\n" + "\n"; return m; }