4 * CGI to FastCGI bridge
7 * Copyright (c) 1996 Open Market, Inc.
9 * See the file "LICENSE.TERMS" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 static const char rcsid[] = "$Id: cgi-fcgi.c,v 1.10 1999/08/27 19:39:17 roberts Exp $";
17 #include "fcgi_config.h"
34 extern char **environ;
37 #ifdef HAVE_SYS_PARAM_H
38 #include <sys/param.h>
41 #ifdef HAVE_SYS_TIME_H
45 #if defined HAVE_UNISTD_H
51 #include "fcgiappmisc.h"
56 static int wsReadPending = 0;
57 static int fcgiReadPending = 0;
58 static int fcgiWritePending = 0;
60 static void ScheduleIo(void);
64 * Simple buffer (not ring buffer) type, used by all event handlers.
74 *----------------------------------------------------------------------
78 * Returns a count of the number of characters available
79 * in the buffer (at most n) and advances past these
80 * characters. Stores a pointer to the first of these
83 *----------------------------------------------------------------------
86 static int GetPtr(char **ptr, int n, Buffer *pBuf)
90 result = min(n, pBuf->stop - pBuf->next);
96 *----------------------------------------------------------------------
100 * Constructs an FCGI_Header struct.
102 *----------------------------------------------------------------------
104 static FCGI_Header MakeHeader(
111 ASSERT(contentLength >= 0 && contentLength <= FCGI_MAX_LENGTH);
112 ASSERT(paddingLength >= 0 && paddingLength <= 0xff);
113 header.version = FCGI_VERSION_1;
115 header.requestIdB1 = (requestId >> 8) & 0xff;
116 header.requestIdB0 = (requestId ) & 0xff;
117 header.contentLengthB1 = (contentLength >> 8) & 0xff;
118 header.contentLengthB0 = (contentLength ) & 0xff;
119 header.paddingLength = paddingLength;
125 *----------------------------------------------------------------------
127 * MakeBeginRequestBody --
129 * Constructs an FCGI_BeginRequestBody record.
131 *----------------------------------------------------------------------
133 static FCGI_BeginRequestBody MakeBeginRequestBody(
137 FCGI_BeginRequestBody body;
138 ASSERT((role >> 16) == 0);
139 body.roleB1 = (role >> 8) & 0xff;
140 body.roleB0 = (role ) & 0xff;
141 body.flags = (keepConnection) ? FCGI_KEEP_CONN : 0;
142 memset(body.reserved, 0, sizeof(body.reserved));
147 static int bytesToRead; /* number of bytes to read from Web Server */
148 static int appServerSock = -1; /* Socket connected to FastCGI application,
149 * used by AppServerReadHandler and
150 * AppServerWriteHandler. */
151 static Buffer fromAS; /* Bytes read from the FCGI application server. */
152 static FCGI_Header header; /* Header of the current record. Is global
153 * since read may return a partial header. */
154 static int headerLen = 0; /* Number of valid bytes contained in header.
155 * If headerLen < sizeof(header),
156 * AppServerReadHandler is reading a record header;
157 * otherwise it is reading bytes of record content
159 static int contentLen; /* If headerLen == sizeof(header), contentLen
160 * is the number of content bytes still to be
162 static int paddingLen; /* If headerLen == sizeof(header), paddingLen
163 * is the number of padding bytes still
165 static int requestId; /* RequestId of the current request.
167 static FCGI_EndRequestBody erBody;
168 static int readingEndRequestBody = FALSE;
169 /* If readingEndRequestBody, erBody contains
170 * partial content: contentLen more bytes need
172 static int exitStatus = 0;
173 static int exitStatusSet = FALSE;
175 static int stdinFds[3];
179 *----------------------------------------------------------------------
183 * FCGIexit provides a single point of exit. It's main use is for
184 * application debug when porting to other operating systems.
186 *----------------------------------------------------------------------
188 static void FCGIexit(int exitCode)
190 if(appServerSock != -1) {
191 OS_Close(appServerSock);
199 #define exit FCGIexit
203 *----------------------------------------------------------------------
205 * AppServerReadHandler --
207 * Reads data from the FCGI application server and (blocking)
208 * writes all of it to the Web server. Exits the program upon
209 * reading EOF from the FCGI application server. Called only when
210 * there's data ready to read from the application server.
212 *----------------------------------------------------------------------
215 static void AppServerReadHandler(ClientData clientData, int bytesRead)
220 assert(fcgiReadPending == TRUE);
221 fcgiReadPending = FALSE;
228 if(headerLen > 0 || paddingLen > 0) {
229 exit(FCGX_PROTOCOL_ERROR);
231 if(appServerSock != -1) {
232 OS_Close(appServerSock);
236 * XXX: Shouldn't be here if exitStatusSet.
238 exit((exitStatusSet) ? exitStatus : FCGX_PROTOCOL_ERROR);
240 fromAS.stop = fromAS.next + count;
241 while(fromAS.next != fromAS.stop) {
243 * fromAS is not empty. What to do with the contents?
245 if(headerLen < sizeof(header)) {
247 * First priority is to complete the header.
249 count = GetPtr(&ptr, sizeof(header) - headerLen, &fromAS);
251 memcpy(&header + headerLen, ptr, count);
253 if(headerLen < sizeof(header)) {
256 if(header.version != FCGI_VERSION_1) {
257 exit(FCGX_UNSUPPORTED_VERSION);
259 if((header.requestIdB1 << 8) + header.requestIdB0 != requestId) {
260 exit(FCGX_PROTOCOL_ERROR);
262 contentLen = (header.contentLengthB1 << 8)
263 + header.contentLengthB0;
264 paddingLen = header.paddingLength;
267 * Header is complete (possibly from previous call). What now?
269 switch(header.type) {
273 * Write the buffered content to stdout or stderr.
274 * Blocking writes are OK here; can't prevent a slow
275 * client from tying up the app server without buffering
276 * output in temporary files.
278 count = GetPtr(&ptr, contentLen, &fromAS);
281 outFD = (header.type == FCGI_STDOUT) ?
282 STDOUT_FILENO : STDERR_FILENO;
283 if(OS_Write(outFD, ptr, count) < 0) {
288 case FCGI_END_REQUEST:
289 if(!readingEndRequestBody) {
290 if(contentLen != sizeof(erBody)) {
291 exit(FCGX_PROTOCOL_ERROR);
293 readingEndRequestBody = TRUE;
295 count = GetPtr(&ptr, contentLen, &fromAS);
297 memcpy(&erBody + sizeof(erBody) - contentLen,
301 if(contentLen == 0) {
302 if(erBody.protocolStatus != FCGI_REQUEST_COMPLETE) {
304 * XXX: What to do with FCGI_OVERLOADED?
306 exit(FCGX_PROTOCOL_ERROR);
308 exitStatus = (erBody.appStatusB3 << 24)
309 + (erBody.appStatusB2 << 16)
310 + (erBody.appStatusB1 << 8)
311 + (erBody.appStatusB0 );
312 exitStatusSet = TRUE;
313 readingEndRequestBody = FALSE;
316 case FCGI_GET_VALUES_RESULT:
318 case FCGI_UNKNOWN_TYPE:
321 exit(FCGX_PROTOCOL_ERROR);
323 if(contentLen == 0) {
325 paddingLen -= GetPtr(&ptr, paddingLen, &fromAS);
328 * If we've processed all the data and skipped all the
329 * padding, discard the header and look for the next one.
331 if(paddingLen == 0) {
335 } /* headerLen >= sizeof(header) */
340 static Buffer fromWS; /* Buffer for data read from Web server
341 * and written to FastCGI application. Used
342 * by WebServerReadHandler and
343 * AppServerWriteHandler. */
344 static int webServerReadHandlerEOF;
345 /* TRUE iff WebServerReadHandler has read EOF from
346 * the Web server. Used in main to prevent
347 * rescheduling WebServerReadHandler. */
349 static void WriteStdinEof(void)
351 static int stdin_eof_sent = 0;
356 *((FCGI_Header *)fromWS.stop) = MakeHeader(FCGI_STDIN, requestId, 0, 0);
357 fromWS.stop += sizeof(FCGI_Header);
362 *----------------------------------------------------------------------
364 * WebServerReadHandler --
366 * Non-blocking reads data from the Web server into the fromWS
367 * buffer. Called only when fromWS is empty, no EOF has been
368 * received from the Web server, and there's data available to read.
370 *----------------------------------------------------------------------
373 static void WebServerReadHandler(ClientData clientData, int bytesRead)
375 assert(fromWS.next == fromWS.stop);
376 assert(fromWS.next == &fromWS.buff[0]);
377 assert(wsReadPending == TRUE);
378 wsReadPending = FALSE;
383 *((FCGI_Header *) &fromWS.buff[0])
384 = MakeHeader(FCGI_STDIN, requestId, bytesRead, 0);
385 bytesToRead -= bytesRead;
386 fromWS.stop = &fromWS.buff[sizeof(FCGI_Header) + bytesRead];
387 webServerReadHandlerEOF = (bytesRead == 0);
389 if (bytesToRead <= 0)
396 *----------------------------------------------------------------------
398 * AppServerWriteHandler --
400 * Non-blocking writes data from the fromWS buffer to the FCGI
401 * application server. Called only when fromWS is non-empty
402 * and the socket is ready to accept some data.
404 *----------------------------------------------------------------------
407 static void AppServerWriteHandler(ClientData clientData, int bytesWritten)
409 int length = fromWS.stop - fromWS.next;
412 assert(fcgiWritePending == TRUE);
414 fcgiWritePending = FALSE;
415 if(bytesWritten < 0) {
418 if((int)bytesWritten < length) {
419 fromWS.next += bytesWritten;
421 fromWS.stop = fromWS.next = &fromWS.buff[0];
431 * This functions is responsible for scheduling all I/O to move
432 * data between a web server and a FastCGI application.
438 * This routine will signal the ioEvent upon completion.
441 static void ScheduleIo(void)
446 * Move data between standard in and the FastCGI connection.
448 if(!fcgiWritePending && appServerSock != -1 &&
449 ((length = fromWS.stop - fromWS.next) != 0)) {
450 if(OS_AsyncWrite(appServerSock, 0, fromWS.next, length,
451 AppServerWriteHandler,
452 (ClientData)appServerSock) == -1) {
455 fcgiWritePending = TRUE;
460 * Schedule a read from the FastCGI application if there's not
461 * one pending and there's room in the buffer.
463 if(!fcgiReadPending && appServerSock != -1) {
464 fromAS.next = &fromAS.buff[0];
466 if(OS_AsyncRead(appServerSock, 0, fromAS.next, BUFFLEN,
467 AppServerReadHandler,
468 (ClientData)appServerSock) == -1) {
471 fcgiReadPending = TRUE;
476 * Schedule a read from standard in if necessary.
478 if((bytesToRead > 0) && !webServerReadHandlerEOF && !wsReadPending &&
480 fromWS.next == &fromWS.buff[0]) {
481 if(OS_AsyncReadStdin(fromWS.next + sizeof(FCGI_Header),
482 BUFFLEN - sizeof(FCGI_Header),
483 WebServerReadHandler, STDIN_FILENO)== -1) {
486 wsReadPending = TRUE;
493 *----------------------------------------------------------------------
497 * Starts nServers copies of FCGI application appPath, all
498 * listening to a Unix Domain socket at bindPath.
500 *----------------------------------------------------------------------
503 static void FCGI_Start(char *bindPath, char *appPath, int nServers)
507 /* @@@ Should be able to pick up the backlog as an arg */
508 if((listenFd = OS_CreateLocalIpcFd(bindPath, 5)) == -1) {
512 if(access(appPath, X_OK) == -1) {
513 fprintf(stderr, "%s is not executable\n", appPath);
518 * Create the server processes
520 for(i = 0; i < nServers; i++) {
521 if(OS_SpawnChild(appPath, listenFd) == -1) {
529 *----------------------------------------------------------------------
531 * FCGIUtil_BuildNameValueHeader --
533 * Builds a name-value pair header from the name length
534 * and the value length. Stores the header into *headerBuffPtr,
535 * and stores the length of the header into *headerLenPtr.
538 * Stores header's length (at most 8) into *headerLenPtr,
539 * and stores the header itself into
540 * headerBuffPtr[0 .. *headerLenPtr - 1].
542 *----------------------------------------------------------------------
544 static void FCGIUtil_BuildNameValueHeader(
547 unsigned char *headerBuffPtr,
549 unsigned char *startHeaderBuffPtr = headerBuffPtr;
551 ASSERT(nameLen >= 0);
552 if (nameLen < 0x80) {
553 *headerBuffPtr++ = nameLen;
555 *headerBuffPtr++ = (nameLen >> 24) | 0x80;
556 *headerBuffPtr++ = (nameLen >> 16);
557 *headerBuffPtr++ = (nameLen >> 8);
558 *headerBuffPtr++ = nameLen;
560 ASSERT(valueLen >= 0);
561 if (valueLen < 0x80) {
562 *headerBuffPtr++ = valueLen;
564 *headerBuffPtr++ = (valueLen >> 24) | 0x80;
565 *headerBuffPtr++ = (valueLen >> 16);
566 *headerBuffPtr++ = (valueLen >> 8);
567 *headerBuffPtr++ = valueLen;
569 *headerLenPtr = headerBuffPtr - startHeaderBuffPtr;
574 static int ParseArgs(int argc, char *argv[],
575 int *doBindPtr, int *doStartPtr,
576 char *connectPathPtr, char *appPathPtr, int *nServersPtr) {
589 *connectPathPtr = '\0';
593 for(i = 0; i < MAXARGS; i++)
595 for(i = 1; i < argc; i++) {
596 if(argv[i][0] == '-') {
597 if(!strcmp(argv[i], "-f")) {
600 "Missing command file name after -f\n");
603 if((fp = fopen(argv[i], "r")) == NULL) {
604 fprintf(stderr, "Cannot open command file %s\n", argv[i]);
608 while(fgets(line, BUFSIZ, fp)) {
612 if((tp1 = (char *) strrchr(line,'\n')) != NULL) {
614 while(*tp1 == ' ' || *tp1 =='\t') {
618 fprintf(stderr, "Line to long\n");
623 if((tp2 = strchr(tp1, ' ')) != NULL) {
628 "To many arguments, "
629 "%d is max from a file\n", MAXARGS);
632 if((av[ac] = (char *)malloc(strlen(tp1)+1)) == NULL) {
633 fprintf(stderr, "Cannot allocate %d bytes\n",
637 strcpy(av[ac++], tp1);
641 err = ParseArgs(ac, av, doBindPtr, doStartPtr,
642 connectPathPtr, appPathPtr, nServersPtr);
643 for(x = 1; x < ac; x++) {
644 ASSERT(av[x] != NULL);
649 } else if (!strcmp(argv[i], "-jitcgi")) {
651 } else if (!strcmp(argv[i], "-dbgfcgi")) {
652 putenv("DEBUG_FCGI=TRUE");
654 } else if(!strcmp(argv[i], "-start")) {
656 } else if(!strcmp(argv[i], "-bind")) {
658 } else if(!strcmp(argv[i], "-connect")) {
661 "Missing connection name after -connect\n");
664 strcpy(connectPathPtr, argv[i]);
667 fprintf(stderr, "Unknown option %s\n", argv[i]);
670 } else if(*appPathPtr == '\0') {
671 strcpy(appPathPtr, argv[i]);
672 } else if(isdigit((int)argv[i][0]) && *nServersPtr == 0) {
673 *nServersPtr = atoi(argv[i]);
674 if(*nServersPtr <= 0) {
675 fprintf(stderr, "Number of servers must be greater than 0\n");
679 fprintf(stderr, "Unknown argument %s\n", argv[i]);
683 if(*doStartPtr && *appPathPtr == 0) {
684 fprintf(stderr, "Missing application pathname\n");
687 if(*connectPathPtr == 0) {
688 fprintf(stderr, "Missing -connect <connName>\n");
690 } else if(strchr(connectPathPtr, ':')) {
692 * XXX: Test to see if we can use IP connect locally...
693 This hack lets me test the ability to create a local process listening
694 to a TCP/IP port for connections and subsequently connect to the app
695 like we do for Unix domain and named pipes.
697 if(*doStartPtr && *doBindPtr) {
699 "<connName> of form hostName:portNumber "
700 "requires -start or -bind\n");
705 if(*nServersPtr == 0) {
711 int main(int argc, char **argv)
713 char **envp = environ;
715 FCGX_Stream *paramsStream;
717 unsigned char headerBuff[8];
718 int headerLen, valueLen;
720 FCGI_BeginRequestRecord beginRecord;
721 int doBind, doStart, nServers;
722 char appPath[MAXPATHLEN], bindPath[MAXPATHLEN];
724 if(ParseArgs(argc, argv, &doBind, &doStart,
725 (char *) &bindPath, (char *) &appPath, &nServers)) {
728 " cgi-fcgi -f <cmdPath> , or\n"
729 " cgi-fcgi -connect <connName> <appPath> [<nServers>] , or\n"
730 " cgi-fcgi -start -connect <connName> <appPath> [<nServers>] , or\n"
731 " cgi-fcgi -bind -connect <connName> ,\n"
732 "where <connName> is either the pathname of a UNIX domain socket\n"
733 "or (if -bind is given) a hostName:portNumber specification\n"
734 "or (if -start is given) a :portNumber specification (uses local host).\n");
738 if(OS_LibInit(stdinFds)) {
739 fprintf(stderr, "Error initializing OS library: %d\n", OS_Errno);
743 equalPtr = getenv("CONTENT_LENGTH");
744 if(equalPtr != NULL) {
745 bytesToRead = atoi(equalPtr);
751 appServerSock = OS_FcgiConnect(bindPath);
753 if(doStart && (!doBind || appServerSock < 0)) {
754 FCGI_Start(bindPath, appPath, nServers);
758 appServerSock = OS_FcgiConnect(bindPath);
761 if(appServerSock < 0) {
762 fprintf(stderr, "Could not connect to %s\n", bindPath);
766 * Set an arbitrary non-null FCGI RequestId
770 * XXX: Send FCGI_GET_VALUES
774 * XXX: Receive FCGI_GET_VALUES_RESULT
778 * Send FCGI_BEGIN_REQUEST (XXX: hack, separate write)
780 beginRecord.header = MakeHeader(FCGI_BEGIN_REQUEST, requestId,
781 sizeof(beginRecord.body), 0);
782 beginRecord.body = MakeBeginRequestBody(FCGI_RESPONDER, FALSE);
783 count = OS_Write(appServerSock, (char *)&beginRecord, sizeof(beginRecord));
784 if(count != sizeof(beginRecord)) {
788 * Send environment to the FCGI application server
790 paramsStream = CreateWriter(appServerSock, requestId, 8192, FCGI_PARAMS);
791 for( ; *envp != NULL; envp++) {
792 equalPtr = strchr(*envp, '=');
793 if(equalPtr == NULL) {
796 valueLen = strlen(equalPtr + 1);
797 FCGIUtil_BuildNameValueHeader(
802 if(FCGX_PutStr((char *) &headerBuff[0], headerLen, paramsStream) < 0
803 || FCGX_PutStr(*envp, equalPtr - *envp, paramsStream) < 0
804 || FCGX_PutStr(equalPtr + 1, valueLen, paramsStream) < 0) {
805 exit(FCGX_GetError(paramsStream));
808 FCGX_FClose(paramsStream);
809 FreeStream(¶msStream);
811 * Perform the event loop until AppServerReadHander sees FCGI_END_REQUEST
813 fromWS.stop = fromWS.next = &fromWS.buff[0];
814 webServerReadHandlerEOF = FALSE;
816 * XXX: might want to use numFDs in the os library.
818 numFDs = max(appServerSock, STDIN_FILENO) + 1;
819 OS_SetFlags(appServerSock, O_NONBLOCK);
821 if (bytesToRead <= 0)
826 while(!exitStatusSet) {
828 * NULL = wait forever (or at least until there's something
834 FCGIexit(exitStatus);