TinyHttpd源码精读(三)

devtools/2024/11/14 5:58:34/

在上一章中我们一起看了如何实现静态的网页,在这里我们一起看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为0if (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服务感兴趣的也能自己手动开始由简入难慢慢丰富自己的功能。


http://www.ppmy.cn/devtools/47169.html

相关文章

LeetCode刷题之HOT100之合并区间

雨下了一整天&#xff0c;中午早早就回去吃饭拿快递了&#xff0c;今天拿了很多快递。我的书回来啦哈哈&#xff0c;还有好多零食&#xff0c;爽歪歪啊&#xff0c;放在下面了&#xff0c;然后准备开始做题啦&#xff01; 图一&#xff1a;左一是xh送我的&#xff0c;非常精彩…

流批一体计算引擎-10-[Flink]中的常用算子和DataStream转换

pyflink 处理 kafka数据 1 DataStream API 示例代码 从非空集合中读取数据&#xff0c;并将结果写入本地文件系统。 from pyflink.common.serialization import Encoder from pyflink.common.typeinfo import Types from pyflink.datastream import StreamExecutionEnviron…

QT 信号和槽 多对一关联示例,多个信号,一个槽函数响应,多个信号源如何绑定一个槽函数

三个顾客 Anderson、Bruce、Castiel 都要订饭&#xff0c;分别对应三个按钮&#xff0c;点击一个按钮&#xff0c;就会弹出给该顾客送饭的消息。注意这个例子只使用一个槽函数&#xff0c;而三个顾客名称是不一样的&#xff0c;弹窗时显示的消息不一样&#xff0c;这需要一些 技…

golang协程(go)与信道(chan)使用示例

函数定义 // 普通函数 func f(from string) {//输出三次传入的字符串for i : 0; i < 50; i {fmt.Println(from, ":", i)} } 协程调用 //使用go协程调用函数go f("go routines > Hello World") 局部函数go协程使用 //使用协和调用临时函数go fun…

内存池(Memory Pool)

内存池&#xff08;Memory Pool&#xff09; 内存池&#xff08;Memory Pool&#xff09;是一种内存管理技术&#xff0c;主要用于优化程序中动态内存分配和释放的效率&#xff0c;减少内存碎片&#xff0c;提高程序运行速度。以下是内存池的一些关键概念和工作原理介绍&#…

【AI大模型】Transformers大模型库(七):单机多卡推理之device_map

目录​​​​​​​ 一、引言 二、单机多卡推理之device_map 2.1 概述 2.2 自动配置&#xff0c;如device_map"auto" 2.3 手动配置&#xff0c;如device_map"cuda:1" 三、总结 一、引言 这里的Transformers指的是huggingface开发的大模型库&#x…

Unity 实现WebSocket 简单通信——客户端

创建连接 ClientWebSocket socket new ClientWebSocket(); string url $"ws://{ip}:{port}"; bool createUri Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out Uri uri); if (createUri) {var task socket.ConnectAsync(uri, CancellationToken.None);task…

远程医疗平台如何连接医生和患者?

远程医疗平台&#xff0c;以其创新的信息技术手段&#xff0c;构筑了一个无视地理界限的医疗服务新体系&#xff0c;实现了医患之间的实时互动和诊疗服务。例如欣九康诊疗系统&#xff0c;通过一系列功能模块&#xff0c;有效连接了医生与患者&#xff0c;为两者提供了一个全面…