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

news2025/1/22 13:39:27

本例子通一个计算体重指数的程序来演示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;						
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML新春烟花

系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色动态爱心9HTML跳动的爱心&#xff08;双心版&#xff09;1…

从结构嵌套的幻梦里:递归与数据构建的精巧和鸣

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 这一节我们来学习递归的相关知识 函数递归 一、什么是递归1.1 递归的思想 二、递归的限制条件三、递归的…

【Linux系统】—— 编译器 gcc/g++ 的使用

【Linux系统】—— 编译器 gcc/g 的使用 1 用 gcc 直接编译2 翻译环境2.1 预处理&#xff08;进行宏替换&#xff09;2.2 编译&#xff08;生成汇编&#xff09;2.3 汇编&#xff08;生成机器可识别代码&#xff09;2.4 链接2.5 记忆小技巧2.6 编译方式2.7 几个问题2.7.1 如何理…

【Unity3D】3D物体摆放、场景优化案例Demo

目录 PlaceManager.cs(放置管理类) Ground.cs(地板类) 和 GroundData.cs(地板数据类) 额外知识点说明 1、MeshFilter和MeshRenderer的Bounds区别 2、Gizmos 绘制一个平行于斜面的立方体 通过网盘分享的文件&#xff1a;PlaceGameDemo2.unitypackage 链接: https://pan.baid…

智能系统的感知和决策

智能系统在感知和决策过程中具备的关键能力表现在智能感知/自主判定上&#xff0c;下面可以从感知的本质、自主判断的含义及其在智能系统中的作用进行深入分析。 1、智能感知&#xff1a;信息获取与理解 智能感知是指智能系统通过传感器或其他数据采集手段获取环境中的信息&…

Spring 中的事件驱动模型

事件驱动的基本了解 事件模式也就是观察者模式&#xff0c;当一个对象改变的时候&#xff0c;所有依赖的对象都会收到一个通知。 Subject&#xff1a;抽象主题 Observer&#xff1a;具体主题 Concrete Subject&#xff1a;抽象观察者&#xff0c;在得到更新通知之后去更新自…

linux-FTP服务配置与应用

也许你对FTP不陌生&#xff0c;但是你是否了解FTP到底是个什么玩意&#xff1f; FTP 是File Transfer Protocol&#xff08;文件传输协议&#xff09;的英文简称&#xff0c;而中文简称为 “文传协议” 用于Internet上的控制文件的双向传输。同时&#xff0c;它也是一个应用程序…

linux-NFS网络共享存储服务配置

1.NFS服务原理 NFS会经常用到&#xff0c;用于在网络上共享存储&#xff0c;这样讲&#xff0c;你对NFS可能不太了解&#xff0c;举一个例子&#xff0c; 加入有三台机器A,B,C&#xff0c;它们需要访问同一个目录&#xff0c;目录中都是图片&#xff0c;传统的做法是把这些 图…

Jenkins 启动

废话 这一阵子感觉空虚&#xff0c;心里空捞捞的&#xff0c;总想找点事情做&#xff0c;即使这是一件微小的事情&#xff0c;空余时间除了骑车、打球&#xff0c;偶尔朋友聚会 … 还能干什么呢&#xff1f; 当独自一人时&#xff0c;究竟可以做点什么&#xff0c;填补这空虚…

消息队列篇--原理篇--Pulsar(Namespace,BookKeeper,类似Kafka甚至更好的消息队列)

Apache Pulusar是一个分布式、多租户、高性能的发布/订阅&#xff08;Pub/Sub&#xff09;消息系统&#xff0c;最初由Yahoo开发并开源。它结合了Kafka和传统消息队列的优点&#xff0c;提供高吞吐量、低延迟、强一致性和可扩展的消息传递能力&#xff0c;适用于大规模分布式系…

Python配置MITMPROXY中间人监听配置

1、安装python 环境&#xff0c;此处可以使用conda安装:conda create --name my_new_env python3.12 2、pip安装mitmproxy&#xff1a;pip install mitmproxy&#xff0c;安装后如果使用mitmproxy --version 成功返回结果&#xff0c;说明已经在环境变量路径中&#xff0c;如果…

Java-数据结构-二叉树习题(2)

第一题、平衡二叉树 ① 暴力求解法 &#x1f4da; 思路提示&#xff1a; 该题要求我们判断给定的二叉树是否为"平衡二叉树"。 平衡二叉树指&#xff1a;该树所有节点的左右子树的高度相差不超过 1。 也就是说需要我们会求二叉树的高&#xff0c;并且要对节点内所…

【网络原理】万字详解 HTTP 协议

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. HTTP 前置知识1.1 HTTP 是什么1.2 HTPP 协议应用场景1.3 HTTP 协议工作过程 2. HTTP 协议格式2.1 fiddler…

基于STM32的智能寝室控制系统设计(论文+源码)

1 .系统整体设计 通过需求分析&#xff0c;本设计基于STM32的智能寝室控制系统整体架构如图2.1所示&#xff0c;整系统利用DHT11温湿度传感器获取室内环境数据&#xff0c;并通过OLED显示&#xff0c;提供用户实时信息&#xff0c;火焰传感器和烟雾传感器用于监测火灾情况&…

日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件

日历热力图&#xff0c;月度数据可视化图表&#xff0c;vue组件 先看效果&#x1f447; 在线体验https://www.guetzjb.cn/calanderViewGraph/ 日历图简单划分为近一年时间&#xff0c;开始时间是 上一年的今天&#xff0c;例如2024/01/01 —— 2025/01/01&#xff0c;跨度刚…

铁电存储器FM25CL64B简介及其驱动编写(基于STM32 hal库)

铁电存储器FM25CL64B简介及其驱动编写&#xff08;基于STM32 hal库&#xff09; 文章目录 铁电存储器FM25CL64B简介及其驱动编写&#xff08;基于STM32 hal库&#xff09;前言一、FM25CL64B简介二、驱动代码1.头文件2.c文件 总结 前言 FM25CL64B是赛普拉斯cypress出品的一款铁…

基于微信小程序的科创微应用平台设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

HarmonyOS Next 最强AI智能辅助编程工具 CodeGenie介绍

随着大模型的兴起&#xff0c;在智能编码领域首先获得了应用。 市面上从Microsoft Copilot到国内阿里通义&#xff0c;字节marscode等&#xff0c;都提供了copilot方式的智能编码工具。HarmonyOS Next作为诞生一年的新事物&#xff0c;由于代码量和文档迭代原因&#xff0c;在智…

WPF2-1在xaml为对象的属性赋值.md

1. AttributeValue方式 1.1. 简单属性赋值1.2. 对象属性赋值 2. 属性标签的方式给属性赋值3. 标签扩展 (Markup Extensions) 3.1. StaticResource3.2. Binding 3.2.1. 普通 Binding3.2.2. ElementName Binding3.2.3. RelativeSource Binding3.2.4. StaticResource Binding (带参…

Appium(四)

一、app页面元素定位 1、通过id定位元素: resrouce-id2、通过ClassName定位&#xff1a;classname3、通过AccessibilityId定位&#xff1a;content-desc4、通过AndroidUiAutomator定位5、通过xpath定位xpath、id、class、accessibility id、android uiautomatorUI AutomatorUI自…