【问题标题】:C Webvserver – STDIN, sockets and CGI pipingC Webvserver – STDIN、套接字和 CGI​​ 管道
【发布时间】:2013-12-13 17:48:04
【问题描述】:

我想为GNUNet创建一个远程控制,所以我开始为GNU OS编写一个自制的多线程通用网络服务器,能够验证用户身份(从system user database读取)并能够执行通用 CGI 程序/脚本。我从头开始,现在只是一个草稿。但是,一切似乎都运行良好。

我有一个问题。

如您所知,CGI 程序/脚本从 STDIN 读取 POST 字符串并将其内容发送到 STDOUT。以下是我编写的代码(部分)。而且它似乎有效。

if (pipe(cgiPipe))
{
  perror("pipe");
}

cgiPid = fork();

if (cgiPid == 0)
{

  /* child */

  /* piping the POST content... */

  /* first, send the truncated part of the POST string contained within the request string... */
  if (nPOSTLength && (nSentChrs = write(cgiPipe[1], sPOSTSegment, 
      nReqLen + requestString - sPOSTSegment)) > 0)
  {
    nPOSTLength -= nSentChrs;

    /* after, read and send the rest of the POST string not received yet... */
    while (nPOSTLength > 0 && (nReadChrs = read(nRemote, reservedBuffer, 
        BUFFER_SIZE_PER_USER)) > 0 && (nSentChrs = write(cgiPipe[1], reservedBuffer, 
        nReadChrs)) > 0 && nReadChrs == nSentChrs)
    {
      nPOSTLength -= nReadChrs;
    }

    if (nReadChrs < 0)
    {
      printf("Error reading POST string.\n");
      goto closeThread;
    }
    if (nSentChrs < 0)
    {
      printf("Error sending POST string.\n");
      goto closeThread;
    }
  }
  else
  {
    write(cgiPipe[1], "(null)", 6);
  }

  close(cgiPipe[1]);

  /* redirecting the output of the pipe to the STDIN of the child process */
  dup2(cgiPipe[0], STDIN_FILENO);
  /* redirecting STDOUT of the child process to the remote client */
  dup2(nRemote, STDOUT_FILENO);
  setuid(nUserID);

  if (execve(sLocalPath, NULL, aCGIEnv))
  {
    /* unable to execute CGI... */
    perror("execve");
    sendString(nRemote,
        "HTTP/1.1 200 OK\r\n"
        "Content-length: 97\r\n"
        "Content-Type: text/html\r\n\r\n"
        "<!doctype html><html><head><title>CGI Error</title></head><body><h1>CGI Error.</h1></body></html>\r\n"
    );
  }

  goto closeThread;

}
else if (cgiPid > 0)
{

  /* parent */

  close(cgiPipe[0]);

  /* wait for child process. */
  if (waitpid(cgiPid, NULL, 0) == -1)
  {
    perror("wait");
  }

  goto closeThread;

}
else
{

  /* parent */

  perror("fork");

  /* let's try to send it as normal file, if the user has the right permissions... */

}

如您所见,执行 CGI 程序之前,整个 POST 字符串从客户端接收并通过管道传输(首先是请求中包含的截断部分字符串——通常是几个字节——然后是其余的)。 然后,CGI程序被执行。

现在我的问题...

如果我尝试上传几个 MB 的文件,在调用 CGI 之前 会通过管道传输几个 MB:有没有办法将套接字直接重定向到新的 STDIN进程,为了之前不读取它?但是,可以肯定的是,我必须在之前发送 POST 字符串的读取截断部分。所以,我可以用这种方式来概括我想做的事情:

  • 将一个字符串(几个字节)传送到 STDIN,然后
  • 将套接字(客户端)重定向到 STDIN,然后
  • 执行外部进程(CGI 程序)

有可能吗?你能告诉我怎么做吗?

【问题讨论】:

  • 一些声明会有所帮助。
  • 这个write(..., nReqLen + requestString - sPOSTSegment)) 看起来有问题。第三个参数应该是一个整数。我希望这至少会让编译器发出警告。
  • @alk 没错! requestString 是一个字符数组,sPOSTSegment 是一个指向该数组字符的指针[通过:sPOSTSegment = 4 + strstr(requestString, "\r\n\r\n") – 找到时],所以 sPOSTSegment 减去 requestString 是从requestString 开始计数的POST 段的偏移量。而且,如果我执行 TOTAL_LENGTH_OF_THE_REQUEST - POST_OFFSET,那么我会得到 POST 段的长度。所以 ... nReqLen + requestString - sPOSTSegment ... 是请求字符串中包含的 POST 段的长度(截断或不截断)。是整数! GCC 不会发出任何警告。
  • 为什么需要将字符串传送到标准输入?你不能把字符串写到标准输入吗?
  • @GiuseppePes 我想向 STDIN 发送一个小字符串,然后 然后 通过管道将 a socket 发送到 STDIN!有什么想法吗?

标签: c sockets webserver pipe dup2


【解决方案1】:

解决了!!

我只需要将发送过程放在父进程而不是子进程中。这样CGI就立即执行了:

if (pipe(cgiPipe))
{
  perror("pipe");
}

cgiPid = fork();

if (cgiPid == 0)
{

  /* child */

  /* piping the POST content... */

  close(cgiPipe[1]);

  /* redirecting the output of the pipe to the STDIN of the child process */
  dup2(cgiPipe[0], STDIN_FILENO);
  /* redirecting STDOUT of the child process to the remote client */
  dup2(nRemote, STDOUT_FILENO);
  setuid(nUserID);

  if (execve(sLocalPath, NULL, aCGIEnv))
  {
    /* unable to execute CGI... */
    perror("execve");
    sendString(nRemote,
        "HTTP/1.1 200 OK\r\n"
        "Content-length: 97\r\n"
        "Content-Type: text/html\r\n\r\n"
        "<!doctype html><html><head><title>CGI Error</title></head><body><h1>CGI Error.</h1></body></html>\r\n"
    );
  }

  goto closeThread;

}
else if (cgiPid > 0)
{

  /* parent */

  close(cgiPipe[0]);

  /* first, send the truncated part of the POST string contained within the request string... */
  if (nPOSTLength && (nSentChrs = write(cgiPipe[1], sPOSTSegment, 
      nReqLen + requestString - sPOSTSegment)) > 0)
  {
    nPOSTLength -= nSentChrs;

    /* after, read and send the rest of the POST string not received yet... */
    while (nPOSTLength > 0 && (nReadChrs = read(nRemote, reservedBuffer, 
        BUFFER_SIZE_PER_USER)) > 0 && (nSentChrs = write(cgiPipe[1], reservedBuffer, 
        nReadChrs)) > 0 && nReadChrs == nSentChrs)
    {
      nPOSTLength -= nReadChrs;
    }

    if (nReadChrs < 0)
    {
      printf("Error reading POST string.\n");
      goto closeThread;
    }
    if (nSentChrs < 0)
    {
      printf("Error sending POST string.\n");
      goto closeThread;
    }
  }
  else
  {
    write(cgiPipe[1], "(null)", 6);
  }

  /* wait for child process. */
  if (waitpid(cgiPid, NULL, 0) == -1)
  {
    perror("wait");
  }

  goto closeThread;

}
else
{

  /* parent */

  perror("fork");

  /* let's try to send it as normal file, if the user has the right permissions... */

}

感谢您的帮助!

还有……希望尽早看到 GNUNet 的遥控器! :)

【讨论】:

    【解决方案2】:

    这可以通过使用dup2() 将文件描述符STDIN_FILENO 替换为打开的套接字来实现。然后,您还应该关闭子进程中的原始套接字:

    dup2(socket_fd, STDIN_FILENO);
    close(socket_fd); ; 
    execve("cgi_process", args, env);
    

    execve 执行另一个进程,其STDIN 绑定到socket_fd

    【讨论】:

    • 在这个例子中,小字符串丢失了。我需要连接一个小字符串,然后是套接字发送的内容,一起发送到 CGI 的 STDIN_FILENO。因此,简而言之,当 CGI 程序执行 read(STDIN_FILENO, ...) 时,它必须接收小字符串,然后开始从套接字接收(就像我用管道所做的那样)。之前不发送全部内容是否有可能?或者,你有什么替代我做的“预加载”的可能吗?
    • 基本上我希望 STDIN 由两部分组成:[ MY_LITTLE_STRING ---- WHAT_THE_SOCKET_SENDS_BUT_NOT_YET_KNOWN ] 并且 CGI 应该从中读取,开始与只有在读取了包含在 STDIN 中的字符串之后,才能使用套接字。相反,我用我的实际代码制作的 STDIN 如下: [ MY_LITTLE_STRING ---- WHAT_THE_SOCKET_SENT_ALREADY_PRELOADED_AND_PIPED ] 现在清楚了吗?我想避免任何形式的“预加载”。
    猜你喜欢
    • 2012-06-08
    • 1970-01-01
    • 2013-09-14
    • 1970-01-01
    • 1970-01-01
    • 2017-01-17
    • 2013-09-05
    • 1970-01-01
    相关资源
    最近更新 更多