【问题标题】:CGI program timeout when reading/writing, respectively, from/to stdin/stdout分别从/向标准输入/标准输出读取/写入时的 CGI 程序超时
【发布时间】:2018-08-07 14:37:57
【问题描述】:

我已经从 shell 测试了这个程序,当从文件重定向标准输入时它可以正常工作。但是,当作为 CGI 程序运行时,它会超时(TimeForCGI hiawatha 网络服务器设置设置为 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>

【问题讨论】:

  • 您不会检查来自freadfwrite 的错误。您的环境不同,正如@johnbollinger 的回答所述,服务器不会强制发出 eof 条件信号(关闭进程的输入)您需要检查 Content-length
  • 为什么禁用缓冲区然后使用fread/fwrite 调用而不是read/writesystem 调用?你到底想要缓冲吗?

标签: c cgi


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您的出色解释。与此同时,我尝试了一种算法,该算法使用 getchar 和 putchar 的次数由 atoi(getenv ("CONTENT_LENGTH")) 的值指定。为了该线程的未来读者,正确的程序将在后续帖子中提供。
  • 您的回答还解释了为什么在我的 webroot 中使用复制到 cat.cgi 的 unix 实用程序 cat 也不起作用。
  • @RobinMiyagi,如果您使用getchar/putchar,请不要停用缓冲。只需使用fflush 函数并在必要时刷新缓冲区,但不要停用它们,否则会严重影响性能。
  • 我没有停用缓冲,我启用了完全缓冲。正如我之前发布的,exit 刷新并关闭所有 stdio 缓冲区,并且在从 main 返回后由编译器托管的启动代码调用 exit。此外,完全缓冲大大提高了性能。
  • 当 CONTENT_LENGTH 本身格式错误时会发生什么?您如何清理 CONTENT_LENGTH?
【解决方案2】:

与此同时,我已经这样做了;

#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 的返回值是否是错误条件。我也没有费心去检查 CONTENT_LENGTH 是 NULL 还是“”。在实践中,对于流量较小的小型项目,我会使用特定领域的解释语言,例如 PHP。我可能会使用 C/C++ 来处理要求苛刻的工作负载,尽管 FastCGI 可以通过打开和关闭与 unix 域套接字的连接的较轻操作来提高性能,而不是通过创建页表和所有费用来分叉子进程的繁重操作其他流程管理簿记。

【讨论】:

  • 全缓冲是什么意思。您已停用 stdio 包完全缓冲以使用您的,因此您不能使用 fflush(3) 调用,因此可以从使用 getchar(3)/putchar(3) 调用中受益。您的 full buffering 远未完成(至少对于完整的 stdio 缓冲:))
  • 根据 `man 3 exit':所有打开的 stdio(3) 流都被刷新和关闭。由 tmpfile(3) 创建的文件被删除,至少对于 GNU C 库来说是这样。作为 C.G.I.程序往往是内部软件,可移植性不是这个项目的优先事项。
【解决方案3】:

您上一篇文章中的以下代码也应该这样做:

#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 刷新所有剩余的缓冲区。

【讨论】:

  • 根据 `man 3 exit':所有打开的 stdio(3) 流都被刷新和关闭。由 tmpfile(3) 创建的文件被删除,至少对于 GNU C 库来说是这样。作为 C.G.I.程序往往是内部软件,可移植性不是这个项目的优先事项。
  • @RobinMiyagi,正如你在我的帖子中所读到的,...并且没有最后的fflush(stdout); 也*因为你马上要去exit(2)*...但是,如果您不打算使用exit(),那么最好将fflush() 与stdio 一起使用,这样您就可以保证当您的最后一个输出停留在输出缓冲区时您不会等待某些东西,CGI 输出不保证是一个 tty,所以你不需要在 \n 之后刷新,就像在交互式程序中一样。
  • _exit(2) 是底层系统调用。但是,至少对于 G.N.U.,实际调用的是该系统调用的库包装函数 exit(3),并执行库函数清理。缓冲实际上是库提供的更高级别的构造,当您直接使用读/写系统调用时,如果您希望有缓冲,则必须自己提供此功能。根据系统文档,exit 使用 atexit(3) 执行所有功能,刷新所有 stdio 缓冲区,然后关闭所有打开的文件。
  • @如果有人向您传递了格式错误的“CONTENT_LENGTH”并且知道 atoi 无法正确处理非空终止字符串会发生什么?
  • @RobinMiyagi,是的,我打错了....抱歉 :) 但我认为这不是真正的问题,对吧?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-31
  • 2012-04-25
  • 1970-01-01
  • 2013-11-26
  • 2011-11-15
  • 2019-09-01
相关资源
最近更新 更多