物联网网关Web服务器--CGI开发实例BMI计算

本例子通一个计算体重指数的程序来演示Web服务器CGI开发。

硬件环境:飞腾派开发板(国产E2000处理器)

软件环境:飞腾派OS(Phytium Pi OS)

硬件平台参考另一篇博客:国产化ARM平台-飞腾派开发板硬件与系统

lighttpd服务器部署参考另一篇博客:物联网网关Web服务器--lighttpd服务器部署与应用测试

1、部署与运行效果

//启动服务器
user@phytiumpi:/var/www$ sudo service lighttpd start

//服务器根目录/var/www部署如下目录与文件
user@phytiumpi:/var/www$ tree 
.
`-- html
    |-- bmi.png
    |-- bmi_index.png
    |-- cgi-bin
    |   `-- bmi.cgi
    `-- index.html

2 directories, 4 files

//文件权限如下
user@phytiumpi:/var/www$ ls -lh html/*
-rw-r--r-- 1 root root  36K Jan 16 11:07 html/bmi.png
-rw-r--r-- 1 root root  13K Jan 16 14:08 html/bmi_index.png
-rw-r--r-- 1 root root  688 Jan 16 11:07 html/index.html

html/cgi-bin:
total 16K
-rwxr-xr-x 1 root root 15K Jan 16 13:51 bmi.cgi

//bmi.cgi文件类型
user@phytiumpi:/var/www$ file html/cgi-bin/bmi.cgi 
html/cgi-bin/bmi.cgi: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=f39d7a4a7551ef3e3b4eba59a12959d4bc636032, for GNU/Linux 3.7.0, not stripped
  • 浏览器运行

输入身高与体重信息,点击“计算”按钮,会提交当前网页中的表单数据到Web服务器并返回计算后的BMI数据。

2、Index网页文件说明

index.html是web服务器默认的页面文件,主要作用就是显示一个静态页面,提交当前页面后指定的cgi程序执行处理。

//action 指定了cgi-bin\bmi.cgi为提交后执行的程序文件
<form action="cgi-bin\bmi.cgi" method="get">	

index.html源码:

<html>
<body>
<div align="center">
<form action="cgi-bin\bmi.cgi" method="get">	
	<table>	
		<tr><td rowspan="3"><img src="bmi.png" hight="60" width="120"></td>
				<td align="center" colspan="3"><h2>体重指数(BMI)计算器</h2></td></tr>
		<tr><td >身高 : <input type="number" name="cm" min="1" max="300" size="3"> cm </td>
				<td >体重 : <input type="number" name="kg" min="1" max="500" size="3"> kg </td>
				<td align="center" ><input type=submit value=" 计 算 " size="16">	</td></tr>
		<tr><td align="center" colspan="3">BMI = <input type="text" name="ret" value=""  size="3" readonly></tr> 
	</table>	
</form>
</br><img src=bmi_index.png >
</div>
</body>		
</html>	

3、HTTP 请求处理功能说明

通过getvalue.h头文件实现。

宏定义和全局变量

#define FIELD_LEN 60 
#define NV_PAIRS 15 

typedef struct name_value_st{
    char name[FIELD_LEN + 1];
    char value[FIELD_LEN + 1]; 
} name_value;

name_value name_val_pairs[NV_PAIRS];
int num_pairs = 0;/*pairs number*/
const char *M = NULL;
const char *L = NULL;
const char *S = NULL;
static int iread = 0;
  • FIELD_LEN 宏定义了每个名称或值的最大长度为 60。

  • NV_PAIRS 宏定义了可以处理的名称 - 值对的最大数量为 15。

  • name_value 结构体包含两个字符数组 namevalue,分别用于存储名称和值,长度为 FIELD_LEN + 1

  • name_val_pairsname_value 结构体的数组,用于存储多个名称 - 值对。

  • num_pairs 用于记录实际存储的名称 - 值对的数量。

  • MLS 是指向常量字符的指针,初始化为 NULL,可能用于存储请求方法、内容长度和查询字符串。

  • iread 是静态整型变量,可能用于记录读取值的次数。

函数声明

void unescape_url(char *url);
void set_env(const char *r_mth, const char *c_len,const char *q_str);
char* get_value(const char *name);
int get_input(void);
void send_error(char *error_test);
char x2c(char *what);
void load_nv_pair(char *tmp_buffer, int nv_entry_number_to_load);
  • unescape_url(char *url):对 URL 进行转义处理。

  • set_env(const char *r_mth, const char *c_len,const char *q_str):设置环境变量,将传入的三个参数存储到全局指针 MLS 中。

  • get_value(const char *name):根据传入的名称查找并返回对应的 value

  • get_input(void):获取输入数据,根据请求方法(POST 或 GET)将数据存储在 ip_data 中,并将数据解析为名称 - 值对存储在 name_val_pairs 中。

  • send_error(char *error_text):输出错误信息,以 HTML 格式输出错误信息。

  • x2c(char *what):将十六进制表示的字符转换为对应的 ASCII 字符。

  • load_nv_pair(char *tmp_buffer, int nv_entry_number_to_load):将 tmp_buffer 中的名称 - 值对加载到 name_val_pairs 数组的指定条目中。

set_env(const char *r_mth, const char *c_len,const char *q_str)

void set_env(const char *r_mth, const char *c_len,const char *q_str)
{
    M = r_mth;
    L = c_len;
    S = q_str;
}
  • 功能:将传入的三个参数 r_mthc_lenq_str 分别赋值给全局指针 MLS,用于存储环境信息。

get_value(const char *name)

char* get_value(const char *name)
{
    int nv_entry_number = 0;
    int i = 0;
    char* val = NULL;
    char *tname = NULL;
    
    if(iread == 0)
    {    
        if (!get_input())
        {
            return "error";
            exit(EXIT_FAILURE);
        }
    }
    
    for(i = 0; i < num_pairs; i++ )
    {        
        val = name_val_pairs[nv_entry_number].value;
        tname = name_val_pairs[nv_entry_number].name;
        nv_entry_number++;
        if( strcmp(tname,name) == 0 )
        { break;}
        else
        {  val = NULL; 
           tname = NULL;
        }
    }

    iread++;//read value times
    return val;    

    exit(EXIT_SUCCESS);
}
  • 功能:

    1. 首先,如果 iread 为 0,则调用 get_input() 函数获取输入数据。如果 get_input() 失败,返回 "error" 并终止程序。

    2. 然后遍历 name_val_pairs 数组,比较每个名称 - 值对的名称部分和传入的 name,如果匹配,将对应的 value 存储在 val 中。

    3. 增加 iread 的值,表示读取值的次数。

    4. 最后返回找到的 value,如果未找到,返回 NULL

get_input(void)

int get_input(void)
{
    int nv_entry_number = 0;
    int got_data = 0;
    char *ip_data = NULL;
    int ip_length = 0;
    char tmp_buffer[(FIELD_LEN * 2) + 2];
    int tmp_offset = 0;
    char *tmp_char_ptr = NULL;
    int chars_processed = 0;


    tmp_char_ptr = (char*)M;
    if ( tmp_char_ptr)
    {
        if(strcmp(tmp_char_ptr, "POST") == 0)
        {
            tmp_char_ptr = (char*)L;
            if (tmp_char_ptr)
            {
                ip_length = atoi(tmp_char_ptr);
                ip_data = malloc(ip_length + 1);
                if (fread(ip_data, 1, ip_length, stdin)!= ip_length)
                {
                    send_error("Bad read from stdin");
                    return(0);
                }
                ip_data[ip_length] = '\0';
                got_data = 1;
            }
        }
    }
    tmp_char_ptr = (char*)M;
    if ( tmp_char_ptr)
    {
        if(strcmp(tmp_char_ptr, "GET") == 0)
        {
            tmp_char_ptr = (char*)S;
            if (tmp_char_ptr)
            {
                ip_length = strlen(tmp_char_ptr);
                ip_data = malloc(ip_length + 1);
                strcpy(ip_data, (char*)S);
                ip_data[ip_length] = '\0';
                got_data = 1;
            }
        }
    }


    if (!got_data)
    {
        send_error("No data received");
    }

    if (ip_length <= 0)
    {
        send_error("Input length <= 0");
        return(0);
    }

    memset(name_val_pairs, '\0', sizeof(name_val_pairs));
    tmp_char_ptr = ip_data;

    while (chars_processed <= ip_length && nv_entry_number < NV_PAIRS)
    {
        tmp_offset = 0;
        while (*tmp_char_ptr && *tmp_char_ptr!= '&' && tmp_offset < FIELD_LEN)
        {
            tmp_buffer[tmp_offset] = *tmp_char_ptr;
            tmp_offset++;
            tmp_char_ptr++;
            chars_processed++;
        }
        tmp_buffer[tmp_offset] = '\0';
        load_nv_pair(tmp_buffer, nv_entry_number);
        tmp_char_ptr++;
        nv_entry_number++;
    }

    free(ip_data);
    ip_data = NULL;
    
    return(1);
}
  • 功能:

    1. 首先,通过 M 检查请求方法。

    2. 如果是 POST 方法,通过 L 获取内容长度,分配足够的内存给 ip_data,使用 fread 从标准输入读取数据,处理读取错误。

    3. 如果是 GET 方法,通过 S 获取查询字符串,分配内存给 ip_data,复制查询字符串,添加字符串结束符。

    4. 检查是否有数据,如果没有数据,调用 send_error 函数报错。

    5. 检查输入长度是否小于等于 0,若是则报错。

    6. 清空 name_val_pairs 数组。

    7. 遍历 ip_data,将数据存储在 tmp_buffer 中,遇到 & 符号或达到 FIELD_LEN 长度时,调用 load_nv_pair 函数将数据存储到 name_val_pairs 数组中。

    8. 释放 ip_data 的内存。

send_error(char *error_text)

void send_error(char *error_text)
{
    printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("Woops:- %s\r\n",error_text);
}
  • 功能:输出 HTML 头信息和错误信息,用于向用户反馈错误信息。

load_nv_pair(char *tmp_buffer, int nv_entry)

void load_nv_pair(char *tmp_buffer, int nv_entry)
{
    int chars_processed = 0;
    char *src_char_ptr = NULL;
    char *dest_char_ptr = NULL;
    src_char_ptr = tmp_buffer;
    dest_char_ptr = name_val_pairs[nv_entry].name;
    while (*src_char_ptr && *src_char_ptr!= '=' && chars_processed < FIELD_LEN)
    {
        if (*src_char_ptr == '+')
        {
            *dest_char_ptr = ' ';
        }else{
            *dest_char_ptr = *src_char_ptr;
        }
        dest_char_ptr++;
        src_char_ptr++;
        chars_processed++;
    }

    if (*src_char_ptr == '=')
    {
        num_pairs++;
        src_char_ptr++;
        dest_char_ptr = name_val_pairs[nv_entry].value;
        chars_processed = 0;
        while (*src_char_ptr && *src_char_ptr!= '=' && chars_processed < FIELD_LEN)
        {
            if (*src_char_ptr == '+')
            {
                *dest_char_ptr = ' ';
            }else{
                *dest_char_ptr = *src_char_ptr;
            }
            dest_char_ptr++;
            src_char_ptr++;
            chars_processed++;
        }

    }
    unescape_url(name_val_pairs[nv_entry].name);
    unescape_url(name_val_pairs[nv_entry].value);
}
  • 功能:

    1. tmp_buffer 中的数据解析为名称 - 值对,将名称存储在 name_val_pairs[nv_entry].name 中,将值存储在 name_val_pairs[nv_entry].value 中。

    2. + 替换为空格。

    3. 调用 unescape_url 函数对名称和值进行 URL 转义处理。

unescape_url(char *url)

void unescape_url(char *url)
{
    int x,y;
    for (x=0,y=0; url[y]; ++x,++y )
    {
        if ( (url[x] = url[y]) == '%')
        {
            url[x] = x2c(&url[y+1]);
            y += 2;
        }
    }
    url[x] = '\0';
}
  • 功能:

    1. 遍历 url 字符串。

    2. 当遇到 % 时,调用 x2c 函数将后面的两个字符转换为对应的 ASCII 字符。

x2c(char *what)

char x2c(char *what)
{
    register char digit;
    digit = (what[0] >= 'A'? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
    digit *= 16;
    digit += (what[1] >= 'A'? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));


    return(digit);
}
  • 功能:将十六进制表示的字符(如 %xx)转换为对应的 ASCII 字符。

4、应用功能主程序说明

通过bmi.c程序实现。关键代码分析说明如下:

	set_env(getenv("REQUEST_METHOD"),
			getenv("CONTENT_LENGTH"),
			getenv("QUERY_STRING"));
  • getenv("REQUEST_METHOD"):该函数用于获取名为 REQUEST_METHOD 的环境变量的值。在 HTTP 服务器环境中,REQUEST_METHOD 通常包含请求的方法,例如 GETPOSTPUT 等。

  • getenv("CONTENT_LENGTH"):该函数用于获取名为 CONTENT_LENGTH 的环境变量的值。在 HTTP 请求中,如果是 POST 方法,CONTENT_LENGTH 表示请求体的长度。

  • getenv("QUERY_STRING"):该函数用于获取名为 QUERY_STRING 的环境变量的值。在 HTTP 的 GET 请求中,QUERY_STRING 包含了 URL 中的查询部分(即 ? 后面的部分)。

	val_cm = get_value("cm");		
	val_kg = get_value("kg");	
  • 通过getvalue.h文件中的自定义函数get_value,可根据传入的名称查找并返回对应的字符串值。

bmi.c文件源码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include "getvalue.h"

int main(int argc, char *argv[])
{
	char *val_cm = NULL;
	char *val_kg = NULL;
	int cm,kg,len;
	float cm_f=0.0,kg_f=0.0,bmi=0.0;
	
	set_env(getenv("REQUEST_METHOD"),
			getenv("CONTENT_LENGTH"),
			getenv("QUERY_STRING"));	
	val_cm = get_value("cm");		
	val_kg = get_value("kg");	

	cm = atoi(val_cm);
	kg = atoi(val_kg);
	cm_f = (cm + 0.0) / 100;
	kg_f = kg + 0.0;
	bmi = kg_f/(cm_f*cm_f);//计算bmi数值

    //下面输出信息都是HTML文件输出
	printf( "Content-type:text/html\n\n" );		
	printf("<html><body><div align=\"center\">\n");
    //输出调试信息
	printf("Debug INFO:CM=%f,KG=%f,BMI=%f \n",cm_f,kg_f,bmi);
	printf("<form action=\"bmi.cgi\" method=\"get\"><table>	    \n");
	printf("<tr><td rowspan=\"3\"><img src=\"../bmi.png\" hight=\"60\" width=\"120\"></td>    \n");
	printf("<td align=\"center\" colspan=\"3\"><h2>体重指数(BMI)计算器</h2></td></tr>    \n");
	printf("<tr><td >身高 : <input type=\"number\" name=\"cm\" min=\"1\" max=\"300\" size=\"3\"> cm </td>    \n");
	printf("<td >体重 : <input type=\"number\" name=\"kg\" min=\"1\" max=\"500\" size=\"3\"> kg </td>    \n");
	printf("<td align=\"center\" ><input type=submit value=\" 计 算 \" size=\"16\">	</td></tr>    \n");
	printf("<tr><td align=\"center\" colspan=\"3\">BMI =     \n");
	if(bmi == 0.0)
		printf("<input type=\"text\" name=\"ret\" value=\"  \"  size=\"3\" readonly></tr>     \n");
	else
	  printf("<input type=\"text\" name=\"ret\" value=\" %.1f  \"  size=\"3\" readonly></tr>     \n",bmi);	
	printf("</table></form></br><img src=\"../bmi_index.png\" >    \n");
	printf("</div></body>	</html>	    \n");
	
	return 0;						
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/959170.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【论文+源码】diffuseq使用扩散模型和diffuseq-v2的序列文本生成序列,并且桥接离散和连续的文本空间,用于加速SEQ2SEQ扩散模型。

这篇论文介绍了一种名为DIFFUSEQ的新型扩散模型&#xff0c;专门针对序列到序列&#xff08;SEQ2SEQ&#xff09;文本生成任务进行设计。尽管扩散模型在视觉和音频等连续信号领域取得了成功&#xff0c;但在自然语言处理特别是条件生成方面的适应仍然未被广泛探索。通过广泛的评…

2024年度总结:技术探索与个人成长的交织

文章目录 前言年度创作回顾&#xff1a;技术深耕与分享数据库技术&#xff1a;MySQL 与 MyBatisJava 及相关技术栈计算机网络&#xff1a;构建网络知识体系思维方式的转变&#xff1a;构建技术知识体系的桥梁 项目实践&#xff1a;人工智能与智慧医疗的碰撞生活与博客的融合与平…

如何使用LDAP-Monitoring-Watchdog实时监控 LDAP 目录中记录修改

关于LDAP-Monitoring-Watchdog LDAP-Monitoring-Watchdog是一种用于实时监控 LDAP 目录中记录更改的工具&#xff0c;该工具能够与Linux兼容&#xff0c;用于检测目录变化&#xff0c;为管理员和安全研究人员提供对添加、修改和删除的可见性。 该工具提供了一种机制来跟踪和可…

Cloudflare通过代理服务器绕过 CORS 限制:原理、实现场景解析

第一部分&#xff1a;问题背景 1.1 错误现象复现 // 浏览器控制台报错示例 Access to fetch at https://chat.qwenlm.ai/api/v1/files/ from origin https://ocr.doublefenzhuan.me has been blocked by CORS policy: Response to preflight request doesnt pass access con…

深入理解动态规划(dp)--(提前要对dfs有了解)

前言&#xff1a;对于动态规划&#xff1a;该算法思维是在dfs基础上演化发展来的&#xff0c;所以我不想讲的是看到一个题怎样直接用动态规划来解决&#xff0c;而是说先用dfs搜索&#xff0c;一步步优化&#xff0c;这个过程叫做动态规划。&#xff08;该文章教你怎样一步步的…

速通Docker === Docker Compose

目录 Docker Compose 简介 Docker Compose 常用命令 使用 Docker Compose 启动 WordPress 普通启动方式&#xff08;使用 Docker 命令&#xff09; 使用 Docker Compose 启动 Docker Compose 的特性 Docker Compose 简介 Docker Compose 是一个用于定义和运行多容器 Dock…

知识体系_统计学_03_描述性统计_概括性度量

对数据的概括性度量可从三方面进行测量和描述&#xff1a;集中趋势、离中趋势和分布形态。 集中趋势&#xff0c;反映的是各数据向其中心值靠拢或聚集的程度&#xff1b;离中趋势&#xff0c;反映的是数据的离散程度&#xff0c;远离中心值的趋势&#xff1b;分布形态反映的是…

HackTheBox靶机:Sightless;NodeJS模板注入漏洞,盲XSS跨站脚本攻击漏洞实战

HackTheBox靶机&#xff1a;Sightless 渗透过程1. 信息收集常规探测深入分析 2. 漏洞利用&#xff08;CVE-2022-0944&#xff09;3. 从Docker中提权4. 信息收集&#xff08;michael用户&#xff09;5. 漏洞利用 Froxlor6. 解密Keepass文件 漏洞分析SQLPad CVE-2022-0944 靶机介…

Qt Creator 15.0.0如何更换主题和字体

1.打开Qt Creator 15.0.0 (Community)&#xff0c; 2.点击编辑栏3.点击Preferences... 4.修改主题&#xff0c;点击环境&#xff0c;修改Theme:栏 5.修改字体大小&#xff0c;点击文本编辑器&#xff0c;修改字号栏。&#xff0c;修改Theme:栏

深度强化学习:PPO

深度强化学习算法&#xff1a;PPO 1. Importance Sampling 先说一下什么是采样&#xff1a;对于一个随机变量&#xff0c;我们通常用概率密度函数来描述该变量的概率分布特性。具体来说&#xff0c;给定随机变量的一个取值&#xff0c;可以根据概率密度函数来计算该值对应的概…

亚博microros小车-原生ubuntu支持系列:11手指控制与手势识别

识别框架还是沿用之前的了MediaPipe Hand。 背景知识不摘重复&#xff0c;参见之前的&#xff1a;亚博microros小车-原生ubuntu支持系列&#xff1a;10-画笔-CSDN博客 手指控制 src/yahboom_esp32_mediapipe/yahboom_esp32_mediapipe/目录下新建文件10_HandCtrl.py&#xff…

OpenCV:在图像中添加高斯噪声、胡椒噪声

目录 在图像中添加高斯噪声 高斯噪声的特性 添加高斯噪声的实现 给图像添加胡椒噪声 实现胡椒噪声的步骤 相关阅读 OpenCV&#xff1a;图像处理中的低通滤波-CSDN博客 OpenCV&#xff1a;高通滤波之索贝尔、沙尔和拉普拉斯-CSDN博客 OpenCV&#xff1a;图像滤波、卷积与…

【模拟集成电路】锁相环(phase-locked loops,PLL)设计_环形振荡器相关(简)

0. 前言 未来将会不定时更新PLL相关的文章&#xff0c;主要目的是作为个人的学习笔记&#xff0c;关于锁相环的基础&#xff0c;可以参考《模拟CMOS集成电路设计_Behzad Razavi》后面几章的内容&#xff0c;下面的文章主要参考书籍是的英文书籍《DESIGN OF CMOS PHASE‑LOCKED …

Qt简单迷宫游戏

目录 你将学到你将准备你将改变你将设计你将编程开始界面游玩界面胜利界面其它bug修复 你可扩展下一篇博客要说的东西 你将学到 Qt中QKeySequence对象的基本创建Qt中QShortcut对象的基本应用Qt中QSoundEffect对象的基本应用 你将准备 在开始制作Qt简单迷宫游戏之前&#xff…

SSM电子商城系统

&#x1f345;点赞收藏关注 → 添加文档最下方联系方式咨询本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345; 项目视频 电…

springboot3 集成 knife4j(接口文档)

提示&#xff1a;文章是集成 knife4j&#xff0c;而非 swagger2 或者 swagger3&#xff0c;效果如图 文章目录 前言一、添加依赖二、如何集成1.配置文件2.注解部分1.Tag2.Operation3.Parameter4.Schema 3.使用 总结 前言 提示&#xff1a;&#xff1a;大家在开发阶段&#xff…

亚博microros小车-原生ubuntu支持系列:7-脸部检测

背景知识 官网介绍&#xff1a; Face Mesh - mediapipe mpFaceMesh.FaceMesh() 类的参数有&#xff1a;self.staticMode, self.maxFaces, self.minDetectionCon, self.minTrackCon staticMode:是否将每帧图像作为静态图像处理。如果为 True&#xff0c;每帧都会进行人脸检测…

写作利器:如何用 PicGo + GitHub 图床提高创作效率

你好呀&#xff0c;欢迎来到 Dong雨 的技术小栈 &#x1f331; 在这里&#xff0c;我们一同探索代码的奥秘&#xff0c;感受技术的魅力 ✨。 &#x1f449; 我的小世界&#xff1a;Dong雨 &#x1f4cc; 分享我的学习旅程 &#x1f6e0;️ 提供贴心的实用工具 &#x1f4a1; 记…

thingsboard 动态报警

前言 考虑将报警上下限写入设备属性&#xff0c;设备遥测数据与设备属性实时做报警逻辑。这样做的好处在于&#xff0c;可以动态修改设备属性&#xff0c;进而修改设备报警触发上下限。 1、修改设备属性 基于mq &#xff0c;向设备写入属性。 topic v1/devices/me/attribut…

三、双链表

链表的种类有很多&#xff0c;单链表是不带头不循环单向链表&#xff0c;但双链表是带头循环双向链表&#xff0c;并且双链表还有一个哨兵位&#xff0c;哨兵位不是头节点 typedef int LTDataType;typedef struct ListNode{struct ListNode* next; //指针保存下⼀个结点的地址s…