TinyHttpd源码精读(三)

在上一章中我们一起看了如何实现静态的网页,在这里我们一起看Tinyhttpd最后的一部分,动态网页的实现:在这里首先声明下因为cgi脚本的支持问题,所以我会新建一个简单的cgi脚本然后将路径导向到这个脚本:

0.perl的配置:

sudo apt update
sudo apt install build-essential libssl-dev zlib1g-dev
sudo apt install perl

1.新增cgi脚本内容:

        在color.cgi同级目录下新增一个temp.cgi的文件,然后内容如下:

#!/usr/bin/perl
use strict;
use warnings;

print "Content-Type: text/html\n\n";
print "<html>\n";
print "<head><title>Simple CGI Script</title></head>\n";
print "<body>\n";
print "<h1>Hello, World!</h1>\n";
print "</body>\n";
print "</html>\n";

  2.修改响应路径:

        在index.html中将<FORM ACTION="color.cgi" METHOD="POST">替换成:

        <FORM ACTION="temp.cgi" METHOD="POST">

3.运行httpd

        然后再对话框内随便输入,点击submit即可看到替换后的效果:

        

4.execute_cgi函数:

        这段代码是带有注释的execute_cgi函数,已经在实现cgi的重要逻辑中都加上了log。诸位可以自行选择直接看还是先看后面的简单解析。

void execute_cgi(int client, const char *path,
        const char *method, const char *query_string)
{
    char buf[1024];
    int cgi_output[2];
    int cgi_input[2];
    pid_t pid;
    int status;
    int i;
    char c;
    int numchars = 1;
    int content_length = -1;
    //确认请求方法
    buf[0] = 'A'; buf[1] = '\0';
    if (strcasecmp(method, "GET") == 0)
        while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
            numchars = get_line(client, buf, sizeof(buf));
    else if (strcasecmp(method, "POST") == 0) /*POST*/
    {
        numchars = get_line(client, buf, sizeof(buf));
        while ((numchars > 0) && strcmp("\n", buf))
        {
            buf[15] = '\0';
            if (strcasecmp(buf, "Content-Length:") == 0)
                content_length = atoi(&(buf[16]));
            numchars = get_line(client, buf, sizeof(buf));
        }
        if (content_length == -1) {
            bad_request(client);
            return;
        }
    }
    else/*HEAD or other*/
    {
    }

    //pipe是创建一个匿名的双向管道。允许数据在父子进程或者相关联进程之前单向流动
    //cgi_output一般是一个整形数组,用来存放管道的两个文件描述符。如果调用成功,cgi_output[0]会存放管道的读段描述符,cgi_output[1]会存放管道的写段描述符
    //如果返回值小于0则表示pipe调用失败.
    if (pipe(cgi_output) < 0) {
        cannot_execute(client);
        return;
    }
    if (pipe(cgi_input) < 0) {
        cannot_execute(client);
        return;
    }
    //fork用于创建一个与调用进程几乎完全相同的子进程,如果调用成功,则返回子进程的进程ID,否则返回-1.
    if ( (pid = fork()) < 0 ) {
        cannot_execute(client);
        return;
    }
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    send(client, buf, strlen(buf), 0);
    //如果当前在子进程中则pid为0
    if (pid == 0)  /* child: CGI script */
    {
        char meth_env[255];
        char query_env[255];
        char length_env[255];

        //dup2的作用是复制文件描述符。在这里他将将cgi_output[1]复制到标准输出,将cgi_input[0]复制到标准输入。
        //这意味着任何原本要输出到终端的输出现在都会重定向到管道中。这样就能在父子进程直接进行数据传递了
        dup2(cgi_output[1], STDOUT);
        dup2(cgi_input[0], STDIN);

        close(cgi_output[0]);
        close(cgi_input[1]);
        sprintf(meth_env, "REQUEST_METHOD=%s", method);
        putenv(meth_env);
        if (strcasecmp(method, "GET") == 0) {
            sprintf(query_env, "QUERY_STRING=%s", query_string);
            putenv(query_env);
        }
        else {   /* POST */
            sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
            putenv(length_env);
        }
        //根据path执行脚本
        execl(path, NULL);
        exit(0);
    } else {    /* parent */
        //在父进程显示cgi脚本
        close(cgi_output[1]);
        close(cgi_input[0]);
        //在post的情况下根据读取的客户端的输出将内容传递到子管道中
        if (strcasecmp(method, "POST") == 0)
            for (i = 0; i < content_length; i++) {
                recv(client, &c, 1, 0);
                write(cgi_input[1], &c, 1);
            }
        //将子进程的输出传递到客户端
        while (read(cgi_output[0], &c, 1) > 0)
            send(client, &c, 1, 0);

        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid, &status, 0);
    }
}

5.execute_cgi函数简单解析:

        主体的逻辑就是从pipe函数那里开始,创建一个子进程,父子进程的环境啥的都完全一样,然后再子进程里面打开cgi脚本,并且将脚本的输出通过dup2函数和cgi_output,cgi_input传递到父进程里面,然后由父进程传递到client(网页)中。

6.总结:

        从精读1,2,3我们基本搞清楚了TinyHttpd这个项目的基本运行逻辑,以及静态网页动态网页显示的思路和逻辑。当然后面的cgi这个算是一个精简版讲解,受限于cgi的布置,我们也不太容易一睹原项目中cgi的风采,但是思路是一样的。其中对于异常情况的处理和思路也是值得我们去学习的,不过我并不是主修web服务这块的,所以目前这块我的理解不够深入,暂时也不打算这样深入。后面我将根据我阅读的TinyHttpd的心得,自己写一个简易版本的内容出来。各位也可以自己动手试试,相信各位自己动手写完后肯定是受益匪浅,收获满满。对于socket的理解和使用的逻辑也会更上一层楼,对于C++web服务感兴趣的也能自己手动开始由简入难慢慢丰富自己的功能。

相关推荐

  1. celery/schedules.py精读

    2024-06-11 23:26:02       47 阅读
  2. Redis精读:哈希表

    2024-06-11 23:26:02       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-11 23:26:02       51 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-11 23:26:02       54 阅读
  3. 在Django里面运行非项目文件

    2024-06-11 23:26:02       44 阅读
  4. Python语言-面向对象

    2024-06-11 23:26:02       55 阅读

热门阅读

  1. C语言学习第四天

    2024-06-11 23:26:02       28 阅读
  2. shell脚本

    2024-06-11 23:26:02       24 阅读
  3. c++_0基础_讲解2 头文件 基本框架

    2024-06-11 23:26:02       27 阅读
  4. C++习题精选(4)—— 栈

    2024-06-11 23:26:02       29 阅读
  5. C++ Compound types overview

    2024-06-11 23:26:02       21 阅读
  6. Spring Boot 事务传播机制详解

    2024-06-11 23:26:02       27 阅读