CGI程序超时时reading/writing,分别为from/tostdin/stdout

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

我已经从 shell 测试了这个程序,当从文件重定向 stdin 时它工作正常。但是,当作为 CGI 程序运行时,它会超时(TimeForCGI hiawatha web 服务器设置为 30 秒)。该程序仅包含在一个文件中。应该注意的是,编写这个程序只是为了物理验证我读到的关于 C.G.I 的内容,我选择了 C(或任何其他生成二进制可执行文件的东西)所以我可以确定这些东西已经没有被任何解释器触及,可能是为了促进他们的抽象。

#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;
}

这里是对应的html表格;

<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>

您的程序尝试从其标准输入中读取数据,直到它结束。当您重定向来自文件的输入时,这很好,但它不适用于 CGI 程序。当到达请求主体的末尾时,运行 CGI 的 Web 服务器没有义务在输入上发出文件结束信号。如果没有,那么您的程序将在 fread().

中无限期阻塞

在请求正文末尾未发出 EOF 信号的原因有多种。 RFC 明确假定扩展数据的存在,但服务器将 CGI 的标准输入直接连接到请求进入的网络套接字也是合理的。通常不会在那里检测到 EOF,直到并且除非客户端关闭其连接结束,许多客户端不会在请求之间这样做,而其他许多客户端在收到响应之前不会这样做。

因此,RFC 3875 中的 CGI 规范说“脚本不得尝试读取超过 CONTENT_LENGTH 字节,即使有更多数据可用”(第 4.2 节)。 CONTENT_LENGTH 通过同名的环境变量传递给脚本,前提是请求指定了一个。您的 CGI 读取的字节数不得超过变量指定的字节数,并且如果根本未指定内容长度,则它不得读取 any 字节。另一方面,在 CGI 中不需要读取整个请求体,或者根本不需要读取其中的任何一个。

同时我也这样做了;

#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;
}

表现出所需的行为。完全缓冲大大减少了一次处理一个字符的开销,并且一旦 getchar 和 putchar 被展开(假设 libc 已被动态链接),它只是一个函数调用。由于这只是使用我信任的 Hiawatha 数据的实验代码,因此我没有费心检查 getchar 和 putchar 的 return 值是否为错误条件。我也懒得去检查 CONTENT_LENGTH 是 NULL 还是 ""。在实践中,对于流量较小的小型项目,我会使用特定领域的解释性语言,例如 PHP。对于要求苛刻的工作负载,我可能会使用 C/C++,尽管 FastCGI 可以通过打开和关闭与 unix 域套接字的连接的较轻操作来代替以创建子进程为代价的较重操作来提高性能页表和所有其他进程管理簿记。

您上次 post 中的以下代码也应该这样做:

#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;
}

并且没有最后的 fflush(stdout);,因为你将在最后的 putchar(3); 之后立即 exit(2),这将使 stdio 刷新所有剩余的缓冲区。