当前位置:   article > 正文

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

lwip httpd

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

通过全局数组定义TAG标签列表

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

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

  1. // 配置HTTPD
  2. #define LWIP_HTTPD_SSI 1
  3. #define LWIP_HTTPD_SSI_INCLUDE_TAG 0

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

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

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

  1. #include <lwip/apps/httpd.h>
  2. #include <lwip/def.h>
  3. #include <stm32f1xx.h>
  4. #include <string.h>
  5. #include <time.h>
  6. #include "test.h"
  7. ADC_HandleTypeDef hadc3;
  8. static const char *ssi_tags[] = {"light", "temp", "devname", "devtype1", "devtype2", "devtype3", "datetime"};
  9. static double test_adc_read(uint32_t channel)
  10. {
  11. double voltage;
  12. uint32_t value;
  13. ADC_ChannelConfTypeDef adc_channel;
  14. adc_channel.Channel = channel;
  15. adc_channel.Rank = ADC_REGULAR_RANK_1;
  16. adc_channel.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  17. HAL_ADC_ConfigChannel(&hadc3, &adc_channel);
  18. HAL_ADC_Start(&hadc3);
  19. HAL_ADC_PollForConversion(&hadc3, HAL_MAX_DELAY);
  20. value = HAL_ADC_GetValue(&hadc3);
  21. voltage = value * 3.3 / 4096;
  22. return voltage;
  23. }
  24. static u16_t test_ssi_handler(int iIndex, char *pcInsert, int iInsertLen)
  25. {
  26. struct tm tm;
  27. time_t t;
  28. // 注意: 不要直接返回snprintf函数的返回值
  29. // 当iInsertLen空间不够时snprintf返回的是欲写入的字符个数,不是真正写入的个数
  30. switch (iIndex)
  31. {
  32. case 0:
  33. snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_5));
  34. break;
  35. case 1:
  36. snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_4));
  37. break;
  38. case 2:
  39. snprintf(pcInsert, iInsertLen, "STM32F103ZET6");
  40. break;
  41. case 3:
  42. case 5:
  43. pcInsert[0] = '\0';
  44. break;
  45. case 4:
  46. snprintf(pcInsert, iInsertLen, " selected=\"selected\"");
  47. break;
  48. case 6:
  49. time(&t);
  50. localtime_r(&t, &tm);
  51. strftime(pcInsert, iInsertLen, "%Y-%m-%d %H:%M:%S", &tm);
  52. break;
  53. default:
  54. return HTTPD_SSI_TAG_UNKNOWN;
  55. }
  56. return strlen(pcInsert);
  57. }
  58. static void test_adc_init(void)
  59. {
  60. GPIO_InitTypeDef gpio;
  61. __HAL_RCC_ADC3_CLK_ENABLE();
  62. __HAL_RCC_GPIOF_CLK_ENABLE();
  63. gpio.Mode = GPIO_MODE_ANALOG;
  64. gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7;
  65. HAL_GPIO_Init(GPIOF, &gpio);
  66. hadc3.Instance = ADC3;
  67. hadc3.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  68. HAL_ADC_Init(&hadc3);
  69. }
  70. void test_init(void)
  71. {
  72. test_adc_init();
  73. http_set_ssi_handler(test_ssi_handler, ssi_tags, LWIP_ARRAYSIZE(ssi_tags));
  74. }

在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提供的取数组元素个数的函数,定义在<lwip/def.h>头文件中。
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的内容如下。

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  2. "http://www.w3.org/TR/html4/loose.dtd">
  3. <html>
  4. <head>
  5. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  6. <title>STM32F103ZE ENC28J60</title>
  7. <style type="text/css">
  8. <!--
  9. body {
  10. font: 100% Verdana, Arial, Helvetica, sans-serif;
  11. background: #666666;
  12. margin: 0;
  13. padding: 0;
  14. text-align: center;
  15. color: #000000;
  16. }
  17. #container {
  18. width: 80%;
  19. background: #FFFFFF;
  20. margin: 0 auto;
  21. border: 1px solid #000000;
  22. text-align: left;
  23. }
  24. #header {
  25. background: #DDDDDD;
  26. padding: 0 10px;
  27. }
  28. #header h1 {
  29. margin: 0;
  30. padding: 10px 0;
  31. }
  32. #sidebar1 {
  33. float: left;
  34. width: 22%;
  35. background: #EBEBEB;
  36. padding: 15px 0;
  37. }
  38. #sidebar2 {
  39. float: right;
  40. width: 23%;
  41. background: #EBEBEB;
  42. padding: 15px 0;
  43. }
  44. #sidebar1 p, #sidebar1 h3, #sidebar2 p, #sidebar2 h3 {
  45. margin-left: 10px;
  46. margin-right: 10px;
  47. }
  48. #mainContent {
  49. margin: 0 24% 0 23.5%;
  50. }
  51. #mainContent form {
  52. line-height: 2em;
  53. }
  54. #footer {
  55. padding: 0 10px;
  56. background: #DDDDDD;
  57. }
  58. #footer p {
  59. margin: 0;
  60. padding: 10px 0;
  61. }
  62. .fltrt {
  63. float: right;
  64. margin-left: 8px;
  65. }
  66. .fltlft {
  67. float: left;
  68. margin-right: 8px;
  69. }
  70. .clearfloat {
  71. clear: both;
  72. height: 0;
  73. font-size: 1px;
  74. line-height: 0px;
  75. }
  76. -->
  77. </style>
  78. </head>
  79. <body>
  80. <div id="container">
  81. <div id="header">
  82. <h1>STM32F103ZE ENC28J60</h1>
  83. </div>
  84. <div id="sidebar1">
  85. <h3>传感器信息</h3>
  86. <p>光敏电阻: <!--#light--></p>
  87. <p>热敏电阻: <!--#temp--></p>
  88. </div>
  89. <div id="sidebar2">
  90. <h3>其他信息</h3>
  91. <p>stm32有自带的以太网模块,为什么还要用ENC28J60?</p>
  92. <p>首先,很多STM32的型号都是不带内置网卡的。其次,ENC28J60相对成熟,很多人因为有现成的ENC28J60方案,所以直接使用。</p>
  93. </div>
  94. <div id="mainContent">
  95. <h2>器件搜索</h2>
  96. <form name="form1" method="get">
  97. <label>
  98. 器件名称:
  99. <input name="devname" type="text" id="devname" value="<!--#devname-->">
  100. </label><br>
  101. <label>
  102. 器件类型:
  103. <select name="devtype" id="devtype">
  104. <option value="1"<!--#devtype1-->>单片机芯片</option>
  105. <option value="2"<!--#devtype2-->>网络芯片</option>
  106. <option value="3"<!--#devtype3-->>音频芯片</option>
  107. </select>
  108. </label><br>
  109. <input type="submit" value="搜索">
  110. </form>
  111. <h1>搜索结果</h1>
  112. <p>暂无任何器件</p>
  113. </div>
  114. <br class="clearfloat" />
  115. <div id="footer">
  116. <p><b>当前时间: </b><!--#datetime--></p>
  117. </div>
  118. </div>
  119. </body>
  120. </html>

程序运行结果:

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

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

通过回调函数直接处理所有标签

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

  1. // 配置HTTPD
  2. #define LWIP_HTTPD_SSI 1
  3. #define LWIP_HTTPD_SSI_INCLUDE_TAG 0
  4. #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);

  1. static u16_t test_ssi_handler(const char *ssi_tag_name, char *pcInsert, int iInsertLen)
  2. {
  3. struct tm tm;
  4. time_t t;
  5. // 注意: 不要直接返回snprintf函数的返回值
  6. // 当iInsertLen空间不够时snprintf返回的是欲写入的字符个数,不是真正写入的个数
  7. if (strcmp(ssi_tag_name, "light") == 0)
  8. snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_5));
  9. else if (strcmp(ssi_tag_name, "temp") == 0)
  10. snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_4));
  11. else if (strcmp(ssi_tag_name, "devname") == 0)
  12. snprintf(pcInsert, iInsertLen, "STM32F103ZET6");
  13. else if (strcmp(ssi_tag_name, "devtype1") == 0 || strcmp(ssi_tag_name, "devtype3") == 0)
  14. snprintf(pcInsert, iInsertLen, "");
  15. else if (strcmp(ssi_tag_name, "devtype2") == 0)
  16. snprintf(pcInsert, iInsertLen, " selected=\"selected\"");
  17. else if (strcmp(ssi_tag_name, "datetime") == 0)
  18. {
  19. time(&t);
  20. localtime_r(&t, &tm);
  21. strftime(pcInsert, iInsertLen, "%Y-%m-%d %H:%M:%S", &tm);
  22. }
  23. else
  24. return HTTPD_SSI_TAG_UNKNOWN;
  25. return strlen(pcInsert);
  26. }
  27. void test_init(void)
  28. {
  29. test_adc_init();
  30. http_set_ssi_handler(test_ssi_handler, NULL, 0);
  31. }

分多次替换同一个HTTP连接的同一个标签的内容

(本节例程名称: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);

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

  1. // 配置HTTPD
  2. #define LWIP_HTTPD_FILE_STATE 1
  3. #define LWIP_HTTPD_SSI 1
  4. #define LWIP_HTTPD_SSI_INCLUDE_TAG 0
  5. #define LWIP_HTTPD_SSI_MULTIPART 1
  6. #define LWIP_HTTPD_SSI_RAW 1

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

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

  1. <div id="sidebar2">
  2. <h3>其他信息</h3>
  3. <p><!--#othermsg--></p>
  4. </div>

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

  1. #include <lwip/apps/fs.h>
  2. #include <lwip/apps/httpd.h>
  3. #include <lwip/mem.h>
  4. #include <stm32f1xx.h>
  5. #include <string.h>
  6. #include <time.h>
  7. #include "test.h"
  8. struct page_state
  9. {
  10. char datetime[50];
  11. char othermsg[1500];
  12. u16_t othermsg_len;
  13. };
  14. void *fs_state_init(struct fs_file *file, const char *name)
  15. {
  16. char part[50];
  17. int i, value;
  18. struct page_state *state;
  19. struct tm tm;
  20. time_t t;
  21. if (strcmp(name, "/info.ssi") == 0)
  22. {
  23. state = mem_malloc(sizeof(struct page_state));
  24. if (state == NULL)
  25. return NULL;
  26. printf("%s: new state(0x%p)\n", __func__, state);
  27. time(&t);
  28. localtime_r(&t, &tm);
  29. strftime(state->datetime, sizeof(state->datetime), "%Y-%m-%d %H:%M:%S", &tm);
  30. i = 1;
  31. state->othermsg[0] = '\0';
  32. while (i != -1)
  33. {
  34. value = rand();
  35. snprintf(part, sizeof(part), "第%d个随机数的值是%d。", i, value);
  36. if (strlen(state->othermsg) + strlen(part) + 1 <= sizeof(state->othermsg))
  37. {
  38. strcat(state->othermsg, part);
  39. i++;
  40. }
  41. else
  42. i = -1;
  43. }
  44. state->othermsg_len = strlen(state->othermsg);
  45. return state;
  46. }
  47. else
  48. return NULL;
  49. }
  50. void fs_state_free(struct fs_file *file, void *state)
  51. {
  52. if (state != NULL)
  53. {
  54. printf("%s: delete state(0x%p)\n", __func__, state);
  55. mem_free(state);
  56. }
  57. }
  58. 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)
  59. {
  60. struct page_state *state = connection_state;
  61. uint16_t len;
  62. if (state == NULL)
  63. return HTTPD_SSI_TAG_UNKNOWN;
  64. // 注意: 不要直接返回snprintf函数的返回值
  65. // 当iInsertLen空间不够时snprintf返回的是欲写入的字符个数,不是真正写入的个数
  66. if (strcmp(ssi_tag_name, "light") == 0)
  67. snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_5));
  68. else if (strcmp(ssi_tag_name, "temp") == 0)
  69. snprintf(pcInsert, iInsertLen, "%.2fV", test_adc_read(ADC_CHANNEL_4));
  70. else if (strcmp(ssi_tag_name, "devname") == 0)
  71. snprintf(pcInsert, iInsertLen, "STM32F103ZET6");
  72. else if (strcmp(ssi_tag_name, "devtype1") == 0 || strcmp(ssi_tag_name, "devtype3") == 0)
  73. snprintf(pcInsert, iInsertLen, "");
  74. else if (strcmp(ssi_tag_name, "devtype2") == 0)
  75. snprintf(pcInsert, iInsertLen, " selected=\"selected\"");
  76. else if (strcmp(ssi_tag_name, "datetime") == 0)
  77. snprintf(pcInsert, iInsertLen, "%s", state->datetime);
  78. else if (strcmp(ssi_tag_name, "othermsg") == 0)
  79. {
  80. len = state->othermsg_len - current_tag_part;
  81. if (len > iInsertLen - 1)
  82. {
  83. // 本次如果发不完, 下次还得接着发, *next_tag_part需要赋值
  84. len = iInsertLen - 1;
  85. *next_tag_part = current_tag_part + len;
  86. }
  87. else
  88. {
  89. // 本次发得完, 就没有下次了, *next_tag_part就不用赋值
  90. }
  91. memcpy(pcInsert, state->othermsg + current_tag_part, len);
  92. pcInsert[len] = '\0';
  93. 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);
  94. return len;
  95. }
  96. else
  97. return HTTPD_SSI_TAG_UNKNOWN;
  98. return strlen(pcInsert);
  99. }

程序运行结果:

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/246049
推荐阅读
相关标签
  

闽ICP备14008679号