Skip to content

WebServer

主要实现的功能是, 存储处理以及传递网页给客户端

只需要支持HTTP协议, HTML文档格式以及URL就可以和网络浏览器配合使用

STM32 网络通信Web Server中 SSI与CGI的应用解析_cgi ssi-CSDN博客

使用的技术

CGI技术

CGI技术 : 通用网关接口(Common Gateway Interface)是一个Web服务器主机提供信息服务的标准接口。通过CGI接口,Web服务器就能够获取客户端提交的信息,转交给服务器端的CGI程序进行处理,最后返回结果给客户端。

实际是使用这个让服务器执行另一个程序, 起到HTML文本转为C语言的作用, 使用这一个最后获取到的是数据

image-20240712144953692

在网页中,CGI(Common Gateway Interface)起着关键作用。以下是一些常见的 CGI 在网页中的应用:

  1. 动态内容生成:CGI 可以根据用户请求动态地生成网页内容。通过处理用户提交的表单数据或其他输入,CGI 脚本能够生成个性化和实时更新的内容,使每个用户都能够获得适合自己需求的页面。
  2. 用户交互:通过 CGI 技术,网页可以与用户进行交互。例如,在一个在线购物网站上,当用户选择了某个商品并点击“添加到购物车”按钮时,后台 CGI 脚本会接收这些信息,并将商品保存到购物车中。
  3. 数据库连接和操作:CGI 可以连接数据库,并执行查询、插入、更新等操作。这使得网站能够动态地从数据库中读取数据,并将其展示给用户。
  4. 用户认证和权限控制:通过 CGI 技术,可以对访问者进行身份验证,并根据其权限提供不同级别的服务。例如,在一个论坛或社交网络上发布帖子可能需要登录并拥有特定权限才能完成。
  5. 文件上传和下载:使用 CGI 技术可以实现文件上传功能,允许用户向服务器发送文件并存储起来。同时也可以提供文件下载功能,允许用户从服务器获取特定文件。

总之,在网页开发过程中利用 CGI 技术可以实现更多动态、个性化和高度交互性质感觉到功能效果,并提升整体使用体验

SSI技术

Server Side Include,是一种类似于ASP的基于服务器的网页制作技术。大多数的WEB服务器等均支持SSI命令。将内容发送到浏览器之前,可以使用“服务器端包含 (SSI)”指令将文本、图形或应用程序信息包含到网页中。

SSI用在.shtml,.stm,.shtm文件中,以的形式写在网页文件中,在服务端接收到浏览器请求后,就会将网页文件中查找到的替换成服务器中的Tag对应内容,然后连同网页数据一起发送给浏览器。

使用这一个最后获取到的是网页, 如果服务器没有使用这一个技术, 这些标签会被识别为注释

CGI(Common Gateway Interface)和 SSI(Server Side Includes)都是用于在服务器端处理和生成网页的技术,但它们有一些不同之处。

  1. CGI 是一种通用的、功能强大的技术,可以编写以各种编程语言编写的脚本来创建动态内容。CGI 脚本能够接收用户请求,并生成基于这些请求的动态内容。与之相对,SSI 主要是用来包含静态内容或执行简单操作,比如在页面中包含其他文件或当前日期等。
  2. 在使用上,SSI 更适合于简单而静态化的任务。例如,在很多静态网页中可能会使用 SSI 来引入共享导航菜单、页脚等信息;而对于需要更复杂交互性质感觉到计算和处理的任务则更适合采用 CGI 技术。

尽管两者有区别,但在实际应用中它们也可以结合起来使用。比如一个网站可以使用 SSI 来包含共享部分(如导航栏),同时使用 CGI 脚本来处理用户提交的表单数据并返回相应结果。

总体而言,SSI 和 CGI 都是为了增强服务器端处理能力而设计的技术,在特定情况下也可组合使用以达到更好效果。

基本原理就是:SSI在HTML文件中,可以通过注释行调用命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。

  • CGI:调用 Web 服务器外的程序
  • SSI:Web 服务器本身执行直接写在 HTML 中的指令

SSI是对网页里面的数据进行替换, CGI是实际可以生成一个数据进行发送

还有一个特殊的 SSI 命令,“exec”。通过指定外部程序,这可以像 CGI 一样运行,并且可以按原样执行 Linux / Unix 命令。

尤其是后一个对OS的命令可以原样执行的问题,根据使用方法的不同,有可能对服务器造成严重的故障。因此,许多租用服务器(共享服务器)禁止使用此“exec”命令或设置一些限制。

实际实现

image-20240712150303758

Middlewares\lwip\src\apps\http\httpd.c和fs.c

fs.c/h是文件的操作

httpd.c/h是网页服务器

lwip-2.1.3自带的httpd网页服务器使用教程(二)使用SSI动态生成网页部分内容_ssi的tag-CSDN博客

image-20240712152438741

在lwipopts.h文件里面, 第三个选项设置为0的时候会从fsdata.c文件获取数据, 1会从fsdata_custom.c文件里面获取数据

image-20240712154834406

使用makefsdata -i, 可以把fs文件夹下面的网页转换为一个fsdata.c文件

image-20240712160125634

获取的这一个文件不需要添加到工程里面, 实际使用的时候会使用#include进行添加

实际是把这些文件转换为一个

c
struct fsdata_file {
  const struct fsdata_file *next;
  const unsigned char *name;
  const unsigned char *data;
  int len;
  u8_t flags;
};

格式的文件, 数据在一个大数组里面

c
/**
 * @brief       lwip_demo 测试
 * @param
 * @retval
 */
void lwip_demo(void)
{
    /* Httpd Init 主要是进行http连接*/
    httpd_init();

    /* 配置SSI处理程序 */
    httpd_ssi_init();

    /* 配置CGI处理器 */
    httpd_cgi_init();
}

第一个函数是httpd.c文件里面的, 会初始化一个tcp_pcb进行管理, 获取连接进来的客户

第二个函数最后调用函数http_set_ssi_handler, 初始化符号链表

c
/**
 * @ingroup httpd
 * Set the SSI handler function.
 *
 * @param ssi_handler the SSI handler function
 * @param tags an array of SSI tag strings to search for in SSI-enabled files
 * @param num_tags number of tags in the 'tags' array
 */
void http_set_ssi_handler(tSSIHandler ssi_handler, const char **tags, int num_tags)

第一个参数是处理函数, 第二个是符号表, 第三个是符号的个数

c
/**
 * @breif       SSI的Handler句柄
 * @param       iIndex      :
 * @param       pcInsert    :
 * @param       iInsertLen  :
 * @retval
 */
static u16_t SSIHandler(int iIndex, char *pcInsert, int iInsertLen)
{
    switch (iIndex)
    {
        case 0:
            ADC_Handler(pcInsert);
            break;
		//...处理其他的的标号
    }

    return strlen(pcInsert);
}

static const char *ppcTAGs[] = /* SSI的Tag */
{
    "t", /* ADC值 */
    "w", /* 温度值 */
    "h", /* 时间 */
    "y"  /* 日期 */
};

http_set_ssi_handler(SSIHandler, ppcTAGs, NUM_CONFIG_SSI_TAGS);   /* 配置SSI句柄 */

第三个函数最后调用http_set_cgi_handlers, 用处是设置cgi的处理函数

c
/**
 * @ingroup httpd
 * Set an array of CGI filenames/handler functions
 *
 * @param cgis an array of CGI filenames/handler functions
 * @param num_handlers number of elements in the 'cgis' array
 */
void
http_set_cgi_handlers(const tCGI *cgis, int num_handlers)
c

/**
 * @breif       CGI LED控制句柄
 * @param       iIndex      : CGI句柄索引号
 * @param       iNumParams  :
 * @param       pcParam     :
 * @param       pcValue     :
 * @retval
 */
const char *LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);

static const tCGI ppcURLs[] = /* cgi程序 */
{
    {"/leds.cgi", LEDS_CGI_Handler},
    {"/beep.cgi", BEEP_CGI_Handler},
};

http_set_cgi_handlers(ppcURLs, NUM_CONFIG_CGI_URIS);      /* 配置CGI句柄 */

处理函数

c
/**
 * @breif       SSIHandler中需要用到的处理RTC时间的函数
 * @param       pcInsert    : 一个待处理的字符串
 * @retval
 */
void RTCTime_Handler(char *pcInsert)
{
    RTC_TimeTypeDef RTC_TimeStruct;
    uint8_t hour, min, sec;

    HAL_RTC_GetTime(&g_rtc_handle, &RTC_TimeStruct, RTC_FORMAT_BIN);
    hour = RTC_TimeStruct.Hours;
    min = RTC_TimeStruct.Minutes;
    sec = RTC_TimeStruct.Seconds;

    *pcInsert = (char)((hour / 10) + 0x30);
    *(pcInsert + 1) = (char)((hour % 10) + 0x30);
    *(pcInsert + 2) = ':';
    *(pcInsert + 3) = (char)((min / 10) + 0x30);
    *(pcInsert + 4) = (char)((min % 10) + 0x30);
    *(pcInsert + 5) = ':';
    *(pcInsert + 6) = (char)((sec / 10) + 0x30);
    *(pcInsert + 7) = (char)((sec % 10) + 0x30);
}
html
<tr>
<td width="200">ADC1_CH5电压值</td>
<td width="200"><!--#t-->mv</td>
</tr>

这一个在发送到服务器以后, 会找到t对应的标签, 进行替换

c
/**
 * @breif       CGI LED控制句柄
 * @param       iIndex      : CGI句柄索引号
 * @param       iNumParams  :
 * @param       pcParam     :
 * @param       pcValue     :
 * @retval
 */
const char *LEDS_CGI_Handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
    uint8_t i = 0; /* 注意根据自己的GET的参数的多少来选择i值范围 */

    iIndex = FindCGIParameter("LED1", pcParam, iNumParams);   /* 找到led的索引号 */

    /* 只有一个CGI句柄 iIndex=0 */
    if (iIndex != -1)
    {
        LED1(1);    /* 关闭所有的LED1灯 */

        for (i = 0; i < iNumParams; i++)                /* 检查CGI参数: example GET /leds.cgi?led=2&led=4 */
        {
            if (strcmp(pcParam[i], "LED1") == 0)        /* 检查参数"led" */
            {
                if (strcmp(pcValue[i], "LED1ON") == 0)  /* 改变LED1状态 */
                {
                    LED1(0);    /* 打开LED1 */
                }
                else if (strcmp(pcValue[i], "LED1OFF") == 0)
                {
                    LED1(1);    /* 关闭LED1 */
                }
            }
        }
    }

    if (HAL_GPIO_ReadPin(LED1_GPIO_PORT,LED1_GPIO_PIN) == 0 && pcf8574_read_bit(BEEP_IO) == 0)
    {
        return "/STM32F407LED_ON_BEEP_OFF.shtml";   /* LED1开,BEEP关 */
    }
    else if (HAL_GPIO_ReadPin(LED1_GPIO_PORT,LED1_GPIO_PIN) == 0 && pcf8574_read_bit(BEEP_IO) == 1)
    {
        return "/STM32F407LED_ON_BEEP_ON.shtml";    /* LED1开,BEEP开 */
    }
    else if (HAL_GPIO_ReadPin(LED1_GPIO_PORT,LED1_GPIO_PIN) == 1 && pcf8574_read_bit(BEEP_IO) == 1)
    {
        return "/STM32F407LED_OFF_BEEP_ON.shtml";   /* LED1关,BEEP开 */
    }
    else
    {
        return "/STM32F407LED_OFF_BEEP_OFF.shtml";  /*  LED1关,BEEP关 */
    }
}

image-20240712163502079

返回的是这些页面的值

http
<div style="margin-top:30px; text-align:center;">
<form method="get" action="/leds.cgi">
   LED1:
      <input type="radio" name="LED1" value="LED1ON" id="LED1_0"   checked>ON
      <input name="LED1" type="radio" id="LED1_1" value="LED1OFF" >OFF<BR>
<br>
  <input type="submit" name="button2" id="button2" value="SEND">
</form>
</div>

这是一个HTML表单,用于控制LED灯的开关。用户可以选择将LED1打开或关闭,并通过点击“SEND”按钮发送请求。

在表单中,有两个选项供用户选择:

  • ON:表示将LED1打开;
  • OFF:表示将LED1关闭。

默认情况下,选中的是"ON",即将LED1打开。

当用户点击“SEND”按钮时,表单会向服务器发送一个GET请求,并在URL参数中包含所选择的状态。服务器收到请求后可以执行相应操作来控制LED灯的状态。

实际调用这不同的处理函数是在http_find_file函数里面