文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-09-08 10:23

关注

上一篇:lwip-2.1.3自带的httpd网页服务器使用教程(一)从SD卡读取网页文件并显示

(本节例程名称:ssi_test)
电脑上用的Web服务器采用ASP、PHP或JSP动态网页技术后,可以根据HTTP模板(asp、php或jsp文件),动态替换掉网页中的<% %>或标签,生成动态网页。lwip自带的httpd也有类似的功能,动态网页的文件扩展名为.ssi,定界符为,其中TAG是不超过LWIP_HTTPD_MAX_TAG_NAME_LEN长度的自定义名称,替换后的文本长度不超过LWIP_HTTPD_MAX_TAG_INSERT_LEN个字符。因为lwip主要在嵌入式系统中运行,所以httpd的ssi功能实现得比较简单。

SSI功能默认是不开启的。开启SSI的方法是在lwipopts.h中定义下面的宏:

// 配置HTTPD#define LWIP_HTTPD_SSI 1#define LWIP_HTTPD_SSI_INCLUDE_TAG 0

LWIP_HTTPD_SSI=1是开启SSI功能的意思,LWIP_HTTPD_SSI_INCLUDE_TAG=0意思是不在最终生成的HTML网页中保留标签。
接下来我们要在C语言程序中定义一下TAG标签列表,和TAG标签替换的内容。
HTTP服务器是在main函数中初始化的,我们在httpd_init()之后新增一个test_init()函数调用:

httpd_init(); // 启动网页服务器test_init();

test_init函数在新建的test.c中实现:

#include #include #include #include #include #include "test.h"ADC_HandleTypeDef hadc3;static const char *ssi_tags[] = {"light", "temp", "devname", "devtype1", "devtype2", "devtype3", "datetime"};static double test_adc_read(uint32_t channel){  double voltage;  uint32_t value;  ADC_ChannelConfTypeDef adc_channel;    adc_channel.Channel = channel;  adc_channel.Rank = ADC_REGULAR_RANK_1;  adc_channel.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;  HAL_ADC_ConfigChannel(&hadc3, &adc_channel);    HAL_ADC_Start(&hadc3);  HAL_ADC_PollForConversion(&hadc3, HAL_MAX_DELAY);  value = HAL_ADC_GetValue(&hadc3);  voltage = value * 3.3 / 4096;  return voltage;}static u16_t test_ssi_handler(int iIndex, char *pcInsert, int iInsertLen){  struct tm tm;  time_t t;    // 注意: 不要直接返回snprintf函数的返回值  // 当iInsertLen空间不够时snprintf返回的是欲写入的字符个数,不是真正写入的个数  switch (iIndex)  {    case 0:      snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_5));      break;    case 1:      snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_4));      break;    case 2:      snprintf(pcInsert, iInsertLen, "STM32F103ZET6");      break;    case 3:    case 5:      pcInsert[0] = '\0';      break;    case 4:      snprintf(pcInsert, iInsertLen, " selected=\"selected\"");      break;    case 6:      time(&t);      localtime_r(&t, &tm);      strftime(pcInsert, iInsertLen, "%Y-%m-%d %H:%M:%S", &tm);      break;    default:      return HTTPD_SSI_TAG_UNKNOWN;  }  return strlen(pcInsert);}static void test_adc_init(void){  GPIO_InitTypeDef gpio;    __HAL_RCC_ADC3_CLK_ENABLE();  __HAL_RCC_GPIOF_CLK_ENABLE();    gpio.Mode = GPIO_MODE_ANALOG;  gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7;  HAL_GPIO_Init(GPIOF, &gpio);    hadc3.Instance = ADC3;  hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START;  HAL_ADC_Init(&hadc3);}void test_init(void){  test_adc_init();  http_set_ssi_handler(test_ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));}

在test_init函数中,我们调用了lwip httpd提供的http_set_ssi_handler函数设置TAG标签的列表和TAG标签处理函数。
TAG标签处理函数的名称是test_ssi_handler。TAG标签列表由全局数组ssi_tags定义(lwip规定此变量必须为全局变量,不能为局部变量),数组的大小为LWIP_ARRAYSIZE(ssi_tags)。LWIP_ARRAYSIZE是lwip提供的取数组元素个数的函数,定义在头文件中。
ssi_tags数组一共定义了7个TAG标签,下标为0~6。
static const char *ssi_tags[] = {"light", "temp", "devname", "devtype1", "devtype2", "devtype3", "datetime"};
这些标签的替换内容由test_ssi_handler函数定义。函数的原型是:
static u16_t test_ssi_handler(int iIndex, char *pcInsert, int iInsertLen);
其中参数iIndex是当前要处理的TAG标签在ssi_tags数组中的下标号,pcInsert是存放替换后文本的缓冲区,iInsertLen是缓冲区的大小。函数的返回值是替换后文本的实际长度。如果当前不想替换该标签,可以返回HTTPD_SSI_TAG_UNKNOWN。

最后,我们把HTML网页模板info.ssi放入lwip-2.1.3/apps/http/fs文件夹中,并运行lwip-2.1.3/apps/http/makefsdata.exe程序,将网页打包成fsdata.c文件。info.ssi的内容如下。

STM32F103ZE ENC28J60

传感器信息

光敏电阻:

热敏电阻:

其他信息

stm32有自带的以太网模块,为什么还要用ENC28J60?

首先,很多STM32的型号都是不带内置网卡的。其次,ENC28J60相对成熟,很多人因为有现成的ENC28J60方案,所以直接使用。

器件搜索



暂无任何器件


程序运行结果:

可以看到,我们成功在网页中显示了光敏电阻和热敏电阻的电压值,以及当前时间。程序还指定了表单里面的文本框的显示文本和下拉菜单框的选中项。

扩展阅读:小梅哥AC620开发板NIOS II LWIP实现HTTP网页控制数码管的显示内容

(本节例程名称:ssi_test2)
如果网页比较多的话,把所有网页用到的标签名都放到ssi_tags全局数组中也不太现实。lwip允许我们开启LWIP_HTTPD_SSI_RAW选项,不用定义ssi_tags全局数组,直接在test_ssi_handler回调函数里面判断标签名就行。

// 配置HTTPD#define LWIP_HTTPD_SSI 1#define LWIP_HTTPD_SSI_INCLUDE_TAG 0#define LWIP_HTTPD_SSI_RAW 1

开启LWIP_HTTPD_SSI_RAW选项后,test_ssi_handler的第一个参数就变成字符串了,ssi_tags全局数组就可以删了。
static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen);

static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen){  struct tm tm;  time_t t;    // 注意: 不要直接返回snprintf函数的返回值  // 当iInsertLen空间不够时snprintf返回的是欲写入的字符个数,不是真正写入的个数  if (strcmp(ssi_tag_name, "light") == 0)    snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_5));  else if (strcmp(ssi_tag_name, "temp") == 0)    snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_4));  else if (strcmp(ssi_tag_name, "devname") == 0)    snprintf(pcInsert, iInsertLen, "STM32F103ZET6");  else if (strcmp(ssi_tag_name, "devtype1") == 0 || strcmp(ssi_tag_name, "devtype3") == 0)    snprintf(pcInsert, iInsertLen, "");  else if (strcmp(ssi_tag_name, "devtype2") == 0)    snprintf(pcInsert, iInsertLen, " selected=\"selected\"");  else if (strcmp(ssi_tag_name, "datetime") == 0)  {    time(&t);    localtime_r(&t, &tm);    strftime(pcInsert, iInsertLen, "%Y-%m-%d %H:%M:%S", &tm);  }  else    return HTTPD_SSI_TAG_UNKNOWN;  return strlen(pcInsert);}void test_init(void){  test_adc_init();  http_set_ssi_handler(test_ssi_handler, NULL, 0);}

(本节例程名称:ssi_test3)
在实际应用中,有的时候某个标签替换的内容很长,默认的LWIP_HTTPD_MAX_TAG_INSERT_LEN=192字节的空间根本装不下。虽然可以将LWIP_HTTPD_MAX_TAG_INSERT_LEN的值改大,但是这样会增大内存消耗。我们可以开启LWIP_HTTPD_SSI_MULTIPART选项,把一段长文本拆成很多段,多次替换。
由于需要执行多次替换,如果每次刷新网页,替换的内容都不相同的话,那么两个人同时访问这张网页就会出问题,会发生相互干扰。为了防止相互干扰,我们可以开启LWIP_HTTPD_FILE_STATE选项,每一次新打开一个连接的时候,就分配一段内存,生成好要替换的内容。替换的时候直接发送已生成的内容就行了。
通常情况下打开了LWIP_HTTPD_SSI_MULTIPART选项,也要同时打开LWIP_HTTPD_FILE_STATE选项。不过,两者也可以单独使用。
开启LWIP_HTTPD_FILE_STATE选项后需要实现下面两个函数。
void *fs_state_init(struct fs_file *file, const char *name);
void fs_state_free(struct fs_file *file, void *state);
fs_state_init函数根据网页名称name创建并填充自定义结构体并返回。
fs_state_free函数用于释放fs_state_init函数创建的结构体所占用的内存。

开启LWIP_HTTPD_FILE_STATE或LWIP_HTTPD_SSI_MULTIPART选项后,test_ssi_handler函数的参数也会发生改变。
当LWIP_HTTPD_FILE_STATE=0且LWIP_HTTPD_SSI_MULTIPART=0时:
static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen);

当LWIP_HTTPD_FILE_STATE=0且LWIP_HTTPD_SSI_MULTIPART=1时:
static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen, u16_t current_tag_part, u16_t *next_tag_part);
第一次调用回调函数时,current_tag_part的值为0。后续调用回调函数时,current_tag_part的值由前一次调用时函数内设置的*next_tag_part的值决定。
在回调函数内,如果没有给*next_tag_part赋值,那么*next_tag_part的值为HTTPD_LAST_TAG_PART,表明当前输出的是标签内容的最后一段文本,后续不再为此标签调用此回调函数。如果给*next_tag_part赋值了,且不等于HTTPD_LAST_TAG_PART,那么还会有下一次函数调用。

当LWIP_HTTPD_FILE_STATE=1且LWIP_HTTPD_SSI_MULTIPART=0时:
static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen, void *connection_state);
connection_state是之前fs_state_init函数创建的自定义结构体。

当LWIP_HTTPD_FILE_STATE=1且LWIP_HTTPD_SSI_MULTIPART=1时:
static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen, u16_t current_tag_part, u16_t *next_tag_part, void *connection_state);

我们来修改一下刚才的工程。

// 配置HTTPD#define LWIP_HTTPD_FILE_STATE 1#define LWIP_HTTPD_SSI 1#define LWIP_HTTPD_SSI_INCLUDE_TAG 0#define LWIP_HTTPD_SSI_MULTIPART 1#define LWIP_HTTPD_SSI_RAW 1

因为我们的C语言源文件test.c用的是GB2312编码,为了防止C语言里面的汉字输出到网页上后乱码,我们也要把网页文件的编码改成GB2312。
在Dreamweaver CS3里面,在“修改”菜单下选择“页面属性”命令,在“/编码”选项下将编码修改为“简体中文(GB2312)”就行了。
Dreamweaver会自动将网页里面meta标签的charset属性值修改为gb2312。

将网页里面“其他信息”栏目下方的内容替换成othermsg标签:

其他信息

将网页另存为info.ssi,放入lwip-2.1.3/apps/http/fs文件夹中,再次运行lwip-2.1.3/apps/http/makefsdata.exe程序,更新fsdata.c。
在Keil中修改test.c文件:

#include #include #include #include #include #include #include "test.h"struct page_state{  char datetime[50];  char othermsg[1500];  u16_t othermsg_len;};void *fs_state_init(struct fs_file *file, const char *name){  char part[50];  int i, value;  struct page_state *state;  struct tm tm;  time_t t;    if (strcmp(name, "/info.ssi") == 0)  {    state = mem_malloc(sizeof(struct page_state));    if (state == NULL)      return NULL;    printf("%s: new state(0x%p)\n", __func__, state);        time(&t);    localtime_r(&t, &tm);    strftime(state->datetime, sizeof(state->datetime), "%Y-%m-%d %H:%M:%S", &tm);        i = 1;    state->othermsg[0] = '\0';    while (i != -1)    {      value = rand();      snprintf(part, sizeof(part), "第%d个随机数的值是%d。", i, value);      if (strlen(state->othermsg) + strlen(part) + 1 <= sizeof(state->othermsg))      {        strcat(state->othermsg, part);        i++;      }      else        i = -1;    }    state->othermsg_len = strlen(state->othermsg);    return state;  }  else    return NULL;}void fs_state_free(struct fs_file *file, void *state){  if (state != NULL)  {    printf("%s: delete state(0x%p)\n", __func__, state);    mem_free(state);  }}static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen, u16_t current_tag_part, u16_t *next_tag_part, void *connection_state){  struct page_state *state = connection_state;  uint16_t len;    if (state == NULL)    return HTTPD_SSI_TAG_UNKNOWN;    // 注意: 不要直接返回snprintf函数的返回值  // 当iInsertLen空间不够时snprintf返回的是欲写入的字符个数,不是真正写入的个数  if (strcmp(ssi_tag_name, "light") == 0)    snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_5));  else if (strcmp(ssi_tag_name, "temp") == 0)    snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_4));  else if (strcmp(ssi_tag_name, "devname") == 0)    snprintf(pcInsert, iInsertLen, "STM32F103ZET6");  else if (strcmp(ssi_tag_name, "devtype1") == 0 || strcmp(ssi_tag_name, "devtype3") == 0)    snprintf(pcInsert, iInsertLen, "");  else if (strcmp(ssi_tag_name, "devtype2") == 0)    snprintf(pcInsert, iInsertLen, " selected=\"selected\"");  else if (strcmp(ssi_tag_name, "datetime") == 0)    snprintf(pcInsert, iInsertLen, "%s", state->datetime);  else if (strcmp(ssi_tag_name, "othermsg") == 0)  {    len = state->othermsg_len - current_tag_part;    if (len > iInsertLen - 1)    {      // 本次如果发不完, 下次还得接着发, *next_tag_part需要赋值      len = iInsertLen - 1;      *next_tag_part = current_tag_part + len;    }    else    {      // 本次发得完, 就没有下次了, *next_tag_part就不用赋值    }    memcpy(pcInsert, state->othermsg + current_tag_part, len);    pcInsert[len] = '\0';    printf("%s(0x%p, %s): pos=%u~%u, len=%u, tot_len=%u\n", __func__, state, ssi_tag_name, current_tag_part, current_tag_part + len - 1, len, state->othermsg_len);    return len;  }  else    return HTTPD_SSI_TAG_UNKNOWN;  return strlen(pcInsert);}

程序运行结果:

下一篇:lwip-2.1.3自带的httpd网页服务器使用教程(三)使用CGI获取URL参数(GET类型表单)

来源地址:https://blog.csdn.net/ZLK1214/article/details/131738736

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-服务器
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯