自学内容网 自学内容网

物联网网关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;
}

 


原文地址:https://blog.csdn.net/Tony_Shen/article/details/145285395

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!