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.
15 static const char rcsid[] = "$Id: cgi-fcgi.c,v 1.1 1997/09/16 15:36:25 stanleyg Exp $";
31 #ifdef HAVE_SYS_TIME_H
34 #ifdef HAVE_SYS_PARAM_H
35 #include <sys/param.h>
39 #include "fcgiappmisc.h"
41 #include "fcgi_config.h"
44 static int wsReadPending = 0;
45 static int wsWritePending = 0;
46 static int fcgiReadPending = 0;
47 static int fcgiWritePending = 0;
49 static void ScheduleIo(void);
53 * Simple buffer (not ring buffer) type, used by all event handlers.
63 *----------------------------------------------------------------------
67 * Returns a count of the number of characters available
68 * in the buffer (at most n) and advances past these
69 * characters. Stores a pointer to the first of these
72 *----------------------------------------------------------------------
75 static int GetPtr(char **ptr, int n, Buffer *pBuf)
79 result = min(n, pBuf->stop - pBuf->next);
85 *----------------------------------------------------------------------
89 * Constructs an FCGI_Header struct.
91 *----------------------------------------------------------------------
93 static FCGI_Header MakeHeader(
100 ASSERT(contentLength >= 0 && contentLength <= FCGI_MAX_LENGTH);
101 ASSERT(paddingLength >= 0 && paddingLength <= 0xff);
102 header.version = FCGI_VERSION_1;
104 header.requestIdB1 = (requestId >> 8) & 0xff;
105 header.requestIdB0 = (requestId ) & 0xff;
106 header.contentLengthB1 = (contentLength >> 8) & 0xff;
107 header.contentLengthB0 = (contentLength ) & 0xff;
108 header.paddingLength = paddingLength;
114 *----------------------------------------------------------------------
116 * MakeBeginRequestBody --
118 * Constructs an FCGI_BeginRequestBody record.
120 *----------------------------------------------------------------------
122 static FCGI_BeginRequestBody MakeBeginRequestBody(
126 FCGI_BeginRequestBody body;
127 ASSERT((role >> 16) == 0);
128 body.roleB1 = (role >> 8) & 0xff;
129 body.roleB0 = (role ) & 0xff;
130 body.flags = (keepConnection) ? FCGI_KEEP_CONN : 0;
131 memset(body.reserved, 0, sizeof(body.reserved));
136 static int bytesToRead; /* number of bytes to read from Web Server */
137 static int appServerSock = -1; /* Socket connected to FastCGI application,
138 * used by AppServerReadHandler and
139 * AppServerWriteHandler. */
140 static Buffer fromAS; /* Bytes read from the FCGI application server. */
141 static FCGI_Header header; /* Header of the current record. Is global
142 * since read may return a partial header. */
143 static int headerLen = 0; /* Number of valid bytes contained in header.
144 * If headerLen < sizeof(header),
145 * AppServerReadHandler is reading a record header;
146 * otherwise it is reading bytes of record content
148 static int contentLen; /* If headerLen == sizeof(header), contentLen
149 * is the number of content bytes still to be
151 static int paddingLen; /* If headerLen == sizeof(header), paddingLen
152 * is the number of padding bytes still
154 static int requestId; /* RequestId of the current request.
156 static FCGI_EndRequestBody erBody;
157 static int readingEndRequestBody = FALSE;
158 /* If readingEndRequestBody, erBody contains
159 * partial content: contentLen more bytes need
161 static int exitStatus = 0;
162 static int exitStatusSet = FALSE;
164 static int stdinFds[3];
168 *----------------------------------------------------------------------
172 * FCGIexit provides a single point of exit. It's main use is for
173 * application debug when porting to other operating systems.
175 *----------------------------------------------------------------------
177 static void FCGIexit(int exitCode)
179 if(appServerSock != -1) {
180 OS_Close(appServerSock);
188 #define exit FCGIexit
192 *----------------------------------------------------------------------
194 * AppServerReadHandler --
196 * Reads data from the FCGI application server and (blocking)
197 * writes all of it to the Web server. Exits the program upon
198 * reading EOF from the FCGI application server. Called only when
199 * there's data ready to read from the application server.
201 *----------------------------------------------------------------------
204 static void AppServerReadHandler(ClientData clientData, int bytesRead)
209 assert(fcgiReadPending == TRUE);
210 fcgiReadPending = FALSE;
217 if(headerLen > 0 || paddingLen > 0) {
218 exit(FCGX_PROTOCOL_ERROR);
220 if(appServerSock != -1) {
221 OS_Close(appServerSock);
225 * XXX: Shouldn't be here if exitStatusSet.
227 exit((exitStatusSet) ? exitStatus : FCGX_PROTOCOL_ERROR);
229 fromAS.stop = fromAS.next + count;
230 while(fromAS.next != fromAS.stop) {
232 * fromAS is not empty. What to do with the contents?
234 if(headerLen < sizeof(header)) {
236 * First priority is to complete the header.
238 count = GetPtr(&ptr, sizeof(header) - headerLen, &fromAS);
240 memcpy(&header + headerLen, ptr, count);
242 if(headerLen < sizeof(header)) {
245 if(header.version != FCGI_VERSION_1) {
246 exit(FCGX_UNSUPPORTED_VERSION);
248 if((header.requestIdB1 << 8) + header.requestIdB0 != requestId) {
249 exit(FCGX_PROTOCOL_ERROR);
251 contentLen = (header.contentLengthB1 << 8)
252 + header.contentLengthB0;
253 paddingLen = header.paddingLength;
256 * Header is complete (possibly from previous call). What now?
258 switch(header.type) {
262 * Write the buffered content to stdout or stderr.
263 * Blocking writes are OK here; can't prevent a slow
264 * client from tying up the app server without buffering
265 * output in temporary files.
267 count = GetPtr(&ptr, contentLen, &fromAS);
270 outFD = (header.type == FCGI_STDOUT) ?
271 STDOUT_FILENO : STDERR_FILENO;
272 if(OS_Write(outFD, ptr, count) < 0) {
277 case FCGI_END_REQUEST:
278 if(!readingEndRequestBody) {
279 if(contentLen != sizeof(erBody)) {
280 exit(FCGX_PROTOCOL_ERROR);
282 readingEndRequestBody = TRUE;
284 count = GetPtr(&ptr, contentLen, &fromAS);
286 memcpy(&erBody + sizeof(erBody) - contentLen,
290 if(contentLen == 0) {
291 if(erBody.protocolStatus != FCGI_REQUEST_COMPLETE) {
293 * XXX: What to do with FCGI_OVERLOADED?
295 exit(FCGX_PROTOCOL_ERROR);
297 exitStatus = (erBody.appStatusB3 << 24)
298 + (erBody.appStatusB2 << 16)
299 + (erBody.appStatusB1 << 8)
300 + (erBody.appStatusB0 );
301 exitStatusSet = TRUE;
302 readingEndRequestBody = FALSE;
305 case FCGI_GET_VALUES_RESULT:
307 case FCGI_UNKNOWN_TYPE:
310 exit(FCGX_PROTOCOL_ERROR);
312 if(contentLen == 0) {
314 paddingLen -= GetPtr(&ptr, paddingLen, &fromAS);
317 * If we've processed all the data and skipped all the
318 * padding, discard the header and look for the next one.
320 if(paddingLen == 0) {
324 } /* headerLen >= sizeof(header) */
329 static Buffer fromWS; /* Buffer for data read from Web server
330 * and written to FastCGI application. Used
331 * by WebServerReadHandler and
332 * AppServerWriteHandler. */
333 static int webServerReadHandlerEOF;
334 /* TRUE iff WebServerReadHandler has read EOF from
335 * the Web server. Used in main to prevent
336 * rescheduling WebServerReadHandler. */
339 *----------------------------------------------------------------------
341 * WebServerReadHandler --
343 * Non-blocking reads data from the Web server into the fromWS
344 * buffer. Called only when fromWS is empty, no EOF has been
345 * received from the Web server, and there's data available to read.
347 *----------------------------------------------------------------------
350 static void WebServerReadHandler(ClientData clientData, int bytesRead)
352 assert(fromWS.next == fromWS.stop);
353 assert(fromWS.next == &fromWS.buff[0]);
354 assert(wsReadPending == TRUE);
355 wsReadPending = FALSE;
360 *((FCGI_Header *) &fromWS.buff[0])
361 = MakeHeader(FCGI_STDIN, requestId, bytesRead, 0);
362 bytesToRead -= bytesRead;
363 fromWS.stop = &fromWS.buff[sizeof(FCGI_Header) + bytesRead];
364 webServerReadHandlerEOF = (bytesRead == 0);
369 *----------------------------------------------------------------------
371 * AppServerWriteHandler --
373 * Non-blocking writes data from the fromWS buffer to the FCGI
374 * application server. Called only when fromWS is non-empty
375 * and the socket is ready to accept some data.
377 *----------------------------------------------------------------------
380 static void AppServerWriteHandler(ClientData clientData, int bytesWritten)
382 int length = fromWS.stop - fromWS.next;
385 assert(fcgiWritePending == TRUE);
387 fcgiWritePending = FALSE;
388 if(bytesWritten < 0) {
391 if((int)bytesWritten < length) {
392 fromWS.next += bytesWritten;
394 fromWS.stop = fromWS.next = &fromWS.buff[0];
404 * This functions is responsible for scheduling all I/O to move
405 * data between a web server and a FastCGI application.
411 * This routine will signal the ioEvent upon completion.
414 static void ScheduleIo(void)
419 * Move data between standard in and the FastCGI connection.
421 if(!fcgiWritePending && appServerSock != -1 &&
422 ((length = fromWS.stop - fromWS.next) != 0)) {
423 if(OS_AsyncWrite(appServerSock, 0, fromWS.next, length,
424 AppServerWriteHandler,
425 (ClientData)appServerSock) == -1) {
428 fcgiWritePending = TRUE;
433 * Schedule a read from the FastCGI application if there's not
434 * one pending and there's room in the buffer.
436 if(!fcgiReadPending && appServerSock != -1) {
437 fromAS.next = &fromAS.buff[0];
439 if(OS_AsyncRead(appServerSock, 0, fromAS.next, BUFFLEN,
440 AppServerReadHandler,
441 (ClientData)appServerSock) == -1) {
444 fcgiReadPending = TRUE;
449 * Schedule a read from standard in if necessary.
451 if((bytesToRead > 0) && !webServerReadHandlerEOF && !wsReadPending &&
453 fromWS.next == &fromWS.buff[0]) {
454 if(OS_AsyncReadStdin(fromWS.next + sizeof(FCGI_Header),
455 BUFFLEN - sizeof(FCGI_Header),
456 WebServerReadHandler, STDIN_FILENO)== -1) {
459 wsReadPending = TRUE;
466 *----------------------------------------------------------------------
470 * Starts nServers copies of FCGI application appPath, all
471 * listening to a Unix Domain socket at bindPath.
473 *----------------------------------------------------------------------
476 static void FCGI_Start(char *bindPath, char *appPath, int nServers)
481 if((listenFd = OS_CreateLocalIpcFd(bindPath)) == -1) {
485 if(access(appPath, X_OK) == -1) {
486 fprintf(stderr, "%s is not executable\n", appPath);
491 * Create the server processes
493 for(i = 0; i < nServers; i++) {
494 if(OS_SpawnChild(appPath, listenFd) == -1) {
502 *----------------------------------------------------------------------
504 * FCGIUtil_BuildNameValueHeader --
506 * Builds a name-value pair header from the name length
507 * and the value length. Stores the header into *headerBuffPtr,
508 * and stores the length of the header into *headerLenPtr.
511 * Stores header's length (at most 8) into *headerLenPtr,
512 * and stores the header itself into
513 * headerBuffPtr[0 .. *headerLenPtr - 1].
515 *----------------------------------------------------------------------
517 static buildNameValueHeaderCalls = 0; /* XXX: for testing */
519 static void FCGIUtil_BuildNameValueHeader(
522 unsigned char *headerBuffPtr,
524 unsigned char *startHeaderBuffPtr = headerBuffPtr;
526 ASSERT(nameLen >= 0);
527 if(nameLen < 0x80 && (buildNameValueHeaderCalls & 1) == 0) {
528 *headerBuffPtr++ = nameLen;
530 *headerBuffPtr++ = (nameLen >> 24) | 0x80;
531 *headerBuffPtr++ = (nameLen >> 16);
532 *headerBuffPtr++ = (nameLen >> 8);
533 *headerBuffPtr++ = nameLen;
535 ASSERT(valueLen >= 0);
536 if(valueLen < 0x80 && (buildNameValueHeaderCalls & 2) == 0) {
537 *headerBuffPtr++ = valueLen;
539 *headerBuffPtr++ = (valueLen >> 24) | 0x80;
540 *headerBuffPtr++ = (valueLen >> 16);
541 *headerBuffPtr++ = (valueLen >> 8);
542 *headerBuffPtr++ = valueLen;
544 *headerLenPtr = headerBuffPtr - startHeaderBuffPtr;
545 buildNameValueHeaderCalls++;
550 static int ParseArgs(int argc, char *argv[],
551 int *doBindPtr, int *doStartPtr,
552 char *connectPathPtr, char *appPathPtr, int *nServersPtr) {
565 *connectPathPtr = '\0';
569 for(i = 0; i < MAXARGS; i++)
571 for(i = 1; i < argc; i++) {
572 if(argv[i][0] == '-') {
573 if(!strcmp(argv[i], "-f")) {
576 "Missing command file name after -f\n");
579 if((fp = fopen(argv[i], "r")) == NULL) {
580 fprintf(stderr, "Cannot open command file %s\n", argv[i]);
584 while(fgets(line, BUFSIZ, fp)) {
588 if((tp1 = (char *) strrchr(line,'\n')) != NULL) {
590 while(*tp1 == ' ' || *tp1 =='\t') {
594 fprintf(stderr, "Line to long\n");
599 if((tp2 = strchr(tp1, ' ')) != NULL) {
604 "To many arguments, "
605 "%d is max from a file\n", MAXARGS);
608 if((av[ac] = malloc(strlen(tp1)+1)) == NULL) {
609 fprintf(stderr, "Cannot allocate %d bytes\n",
613 strcpy(av[ac++], tp1);
617 err = ParseArgs(ac, av, doBindPtr, doStartPtr,
618 connectPathPtr, appPathPtr, nServersPtr);
619 for(x = 1; x < ac; x++) {
620 ASSERT(av[x] != NULL);
625 } else if (!strcmp(argv[i], "-jitcgi")) {
627 } else if (!strcmp(argv[i], "-dbgfcgi")) {
628 putenv("DEBUG_FCGI=TRUE");
630 } else if(!strcmp(argv[i], "-start")) {
632 } else if(!strcmp(argv[i], "-bind")) {
634 } else if(!strcmp(argv[i], "-connect")) {
637 "Missing connection name after -connect\n");
640 strcpy(connectPathPtr, argv[i]);
643 fprintf(stderr, "Unknown option %s\n", argv[i]);
646 } else if(*appPathPtr == '\0') {
647 strcpy(appPathPtr, argv[i]);
648 } else if(isdigit(argv[i][0]) && *nServersPtr == 0) {
649 *nServersPtr = atoi(argv[i]);
650 if(*nServersPtr <= 0) {
651 fprintf(stderr, "Number of servers must be greater than 0\n");
655 fprintf(stderr, "Unknown argument %s\n", argv[i]);
659 if(*doStartPtr && *appPathPtr == 0) {
660 fprintf(stderr, "Missing application pathname\n");
663 if(*connectPathPtr == 0) {
664 fprintf(stderr, "Missing -connect <connName>\n");
666 } else if(strchr(connectPathPtr, ':')) {
668 * XXX: Test to see if we can use IP connect locally...
669 This hack lets me test the ability to create a local process listening
670 to a TCP/IP port for connections and subsequently connect to the app
671 like we do for Unix domain and named pipes.
673 if(*doStartPtr && *doBindPtr) {
675 "<connName> of form hostName:portNumber "
676 "requires -start or -bind\n");
681 if(*nServersPtr == 0) {
687 void main(int argc, char **argv, char **envp)
690 FCGX_Stream *paramsStream;
692 unsigned char headerBuff[8];
693 int headerLen, valueLen;
695 FCGI_BeginRequestRecord beginRecord;
696 int doBind, doStart, nServers;
697 char appPath[MAXPATHLEN], bindPath[MAXPATHLEN];
699 if(ParseArgs(argc, argv, &doBind, &doStart,
700 (char *) &bindPath, (char *) &appPath, &nServers)) {
703 " cgi-fcgi -f <cmdPath> , or\n"
704 " cgi-fcgi -connect <connName> <appPath> [<nServers>] , or\n"
705 " cgi-fcgi -start -connect <connName> <appPath> [<nServers>] , or\n"
706 " cgi-fcgi -bind -connect <connName> ,\n"
707 "where <connName> is either the pathname of a UNIX domain socket\n"
708 "or (if -bind is given) a hostName:portNumber specification\n"
709 "or (if -start is given) a :portNumber specification (uses local host).\n");
713 if(OS_LibInit(stdinFds)) {
714 fprintf(stderr, "Error initializing OS library: %d\n", OS_Errno);
718 equalPtr = getenv("CONTENT_LENGTH");
719 if(equalPtr != NULL) {
720 bytesToRead = atoi(equalPtr);
726 appServerSock = OS_FcgiConnect(bindPath);
728 if(doStart && (!doBind || appServerSock < 0)) {
729 FCGI_Start(bindPath, appPath, nServers);
733 appServerSock = OS_FcgiConnect(bindPath);
736 if(appServerSock < 0) {
737 fprintf(stderr, "Could not connect to %s\n", bindPath);
741 * Set an arbitrary non-null FCGI RequestId
745 * XXX: Send FCGI_GET_VALUES
749 * XXX: Receive FCGI_GET_VALUES_RESULT
753 * Send FCGI_BEGIN_REQUEST (XXX: hack, separate write)
755 beginRecord.header = MakeHeader(FCGI_BEGIN_REQUEST, requestId,
756 sizeof(beginRecord.body), 0);
757 beginRecord.body = MakeBeginRequestBody(FCGI_RESPONDER, FALSE);
758 count = OS_Write(appServerSock, (char *)&beginRecord, sizeof(beginRecord));
759 if(count != sizeof(beginRecord)) {
763 * Send environment to the FCGI application server
765 paramsStream = CreateWriter(appServerSock, requestId, 8192, FCGI_PARAMS);
766 for( ; *envp != NULL; envp++) {
767 equalPtr = strchr(*envp, '=');
768 if(equalPtr == NULL) {
771 valueLen = strlen(equalPtr + 1);
772 FCGIUtil_BuildNameValueHeader(
777 if(FCGX_PutStr((char *) &headerBuff[0], headerLen, paramsStream) < 0
778 || FCGX_PutStr(*envp, equalPtr - *envp, paramsStream) < 0
779 || FCGX_PutStr(equalPtr + 1, valueLen, paramsStream) < 0) {
780 exit(FCGX_GetError(paramsStream));
783 FCGX_FClose(paramsStream);
784 FreeStream(¶msStream);
786 * Perform the event loop until AppServerReadHander sees FCGI_END_REQUEST
788 fromWS.stop = fromWS.next = &fromWS.buff[0];
789 webServerReadHandlerEOF = FALSE;
791 * XXX: might want to use numFDs in the os library.
793 numFDs = max(appServerSock, STDIN_FILENO) + 1;
794 OS_SetFlags(appServerSock, O_NONBLOCK);
797 while(!exitStatusSet) {
799 * NULL = wait forever (or at least until there's something
805 FCGIexit(exitStatus);