简体   繁体   中英

CGI program timeout when reading/writing, respectively, from/to stdin/stdout

I have tested this program from the shell, and it works correctly when redircting stdin from a file. However, when operating as a CGI program, it times out (TimeForCGI hiawatha webserver setting is set to 30 seconds). This program is contained in just one file. It should be noted that this program was written only to physically verify what I have been reading about CGI, and I chose C (or anything else that generates a binary executable) so I can be sure that these things have been untouched by any interpreter, as might be done to facilitate their abstractions.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>

int main (void);

int
main
(void)
{
  static char buf[BUFSIZ];
  size_t size;

  if (setvbuf (stdout, NULL, _IOFBF, BUFSIZ) != 0)
    error (EXIT_FAILURE, errno, "setvbuf(), stdout");
  if (setvbuf (stdin, NULL, _IOFBF, BUFSIZ) != 0)
    error (EXIT_FAILURE, errno, "setvbuf(), stdin");
  if (setvbuf (stderr, NULL, _IOLBF, BUFSIZ) != 0)
    error (EXIT_FAILURE, errno, "setvbuf(), stderr");
  printf ("Content-Type: text/plain\n\n");
  if (fflush (stdout) == EOF)
    error (EXIT_FAILURE, errno, "fflush()");
  for (;;)
    {
      size = fread (buf,1, BUFSIZ, stdin);
      if (size == 0)
        {
          if (feof (stdin) != 0)
            goto quit;
          else
            error (EXIT_FAILURE, errno, "fread(), stdin");
        }
      size = fwrite (buf, 1, size, stdout);
      if (size == 0)
        error (EXIT_FAILURE, errno, "write(), stdout");
    }
 quit:
  fflush (stdout);
  return EXIT_SUCCESS;
}

Here is the corresponding html form;

<html>
  <head>
    <title>Form</title>
  </head>
  <body>
    <form action="form-process.cgi" method="post">
      input_a: <input name="input_a" type="text"><br>
      input_b: <input name="input_b" type="text"><br>
      <input type="submit" value="Submit">
    </form>
  </body>
</html>

Your program attempts to read from its standard input until it reaches its end. That's fine when you redirect the input from a file, but it is inappropriate for a CGI program. The web server in which the CGI runs is not obligated to signal end-of-file on the input when the end of the request body is reached. If it does not, then your program will block indefinitely in fread() .

There are several reasons why EOF might not be signaled at the end of the request body. The RFC explicitly postulates the presence of extension data, but it is also plausible that the server connects the CGI's standard input directly to the network socket on which the request is coming in. EOF will not normally be detected there until and unless the client closes its end of the connection, which many clients do not do between requests, and which many of the rest do not do until after they have received the response.

Accordingly, the CGI specifications in RFC 3875 say "the script MUST NOT attempt to read more than CONTENT_LENGTH bytes, even if more data is available" (section 4.2). The CONTENT_LENGTH is conveyed to the script via an environment variable of that name, provided that the request specifies one. Your CGI must not read more bytes than the variable specifies, and it must not read any bytes if the content length is not specified at all. On the other hand, it is at the CGI's not required to read the whole request body, or any of it at all.

In the meantime I have done this;

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <dstralg.h>

int main (void);

int
main
(void)
{
  int l;
  int i;

  if (setvbuf (stdin, NULL, _IOFBF, BUFSIZ)!= 0)
    error (EXIT_FAILURE, errno, "sevbuf(), stdin");
  if (setvbuf (stdout, NULL, _IOFBF, BUFSIZ)!= 0)
    error (EXIT_FAILURE, errno, "sevbuf(), stdout");
  printf ("Content-Type: text/plain\n\n");
  l = atoi (getenv ("CONTENT_LENGTH"));
  for (i = 0; i < l; ++i)
    putchar (getchar ());
  return EXIT_SUCCESS;
}

which exhibits the desired behavior. The full buffering greatly reduces the overhead of processing one character at a time, and is only a function call once getchar and putchar have been unwound (assuming libc has been dynamically linked). As this is only experimental code using data from Hiawatha, which I trust, I didn't bother checking the return values of getchar and putchar being error conditions. Nor did I bother to check if CONTENT_LENGTH was NULL or "". In practice, I would use a domain specific interpretted language, such as PHP, for small projects with light traffic. I would probably use C/C++ for demanding workloads, although FastCGI can improve performance by the lighter operation of opening and closing connection to a unix domain socket in place of the heavier operation of forking a child process with the expense of creating page tables and all the other process management book keeping.

The following code, from your last post, should do also:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <dstralg.h>

int main (void);

int
main
(void)
{
    int l;
    int i;

    printf ("Content-Type: text/plain\n\n");
    l = atoi (getenv ("CONTENT_LENGTH"));
    for (i = 0; i < l; ++i)
        putchar (getchar ());
    fflush(stdout);
    return EXIT_SUCCESS;
}

and without the final fflush(stdout); also, because you are going to exit(2) inmediately, after last putchar(3); , and that will make stdio to flush all buffers remaining.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM