ESP32系列四:搭建http的webserver的服务器

打印 上一主题 下一主题

主题 1895|帖子 1895|积分 5685

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
最近在使用ESP32搭建web服务器测试,发现esp32搭建这类开发情况还是比较方便的。具体的http协议这里就不再赘述,我们重要说一下怎样使用ESP32提供的API来搭建我们的http web。
一、web服务器搭建过程
1、配置web服务器
在ESP-IDF中,Web服务器使用httpd组件实现。我们需要先创建httpd_config_t结构体,指定服务器的端口、最大并发连接数、URI匹配处理器等选项。然后,我们通过调用httpd_start函数来启动Web服务器。(使用默认的配置就可以,包括端标语都已经默认配置好了)
  1. httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  2. httpd_handle_t server = NULL;
  3. // 设置服务器端口为80
  4. config.server_port = 80;
  5. // 创建HTTP服务器句柄
  6. if (httpd_start(&server, &config) != ESP_OK) {
  7.     printf("Error starting server!\n");
  8.     return;
  9. }
复制代码
2、 注册 URI处理器
在Web服务器启动后,我们需要为不同的URI注册处理器函数。当Web服务器接收到哀求时,会根据哀求的URI选择相应的处理器函数举行处理。在ESP-IDF中,我们可以使用httpd_register_uri_handler函数注册URI处理器。该函数的原型如下
  1. esp_err_t httpd_register_uri_handler(httpd_handle_t hd, const httpd_uri_t *uri)
复制代码
其中,hd参数为HTTP服务器句柄;uri参数为包含URI路径、HTTP方法、处理函数等信息的结构体指针。例如:
  1. static const httpd_uri_t echo = {
  2.     .uri       = "/",
  3.     .method    = HTTP_POST,
  4.     .handler   = echo_post_handler,
  5.     .user_ctx  = NULL
  6. };
  7. static esp_err_t echo_post_handler(httpd_req_t *req)
  8. {
  9.     char buf[100];
  10.     // char ssid[10];
  11.     // char pswd[10];
  12.     int ret, remaining = req->content_len;
  13.     while (remaining > 0) {
  14.         /* Read the data for the request */
  15.         if ((ret = httpd_req_recv(req, buf,
  16.                         MIN(remaining, sizeof(buf)))) <= 0) {
  17.             if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
  18.                 /* Retry receiving if timeout occurred */
  19.                 continue;
  20.             }
  21.             return ESP_FAIL;
  22.         }
  23.         /* Send back the same data */
  24.         httpd_resp_send_chunk(req, buf, ret);
  25.         remaining -= ret;
  26.         esp_err_t e = httpd_query_key_value(buf,"ssid",wifi_name,sizeof(wifi_name));
  27.         if(e == ESP_OK) {
  28.             printf("ssid = %s\r\n",wifi_name);
  29.         }
  30.         else {
  31.             printf("error = %d\r\n",e);
  32.         }
  33.         e = httpd_query_key_value(buf,"password",wifi_password,sizeof(wifi_password));
  34.         if(e == ESP_OK) {
  35.             printf("pswd = %s\r\n",wifi_password);
  36.         }
  37.         else {
  38.             printf("error = %d\r\n",e);
  39.         }
  40.         /* Log data received */
  41.         ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
  42.         ESP_LOGI(TAG, "%.*s", ret, buf);
  43.         ESP_LOGI(TAG, "====================================");
  44.     }
  45.     // End response
  46.     httpd_resp_send_chunk(req, NULL, 0);
  47.     if(strcmp(wifi_name ,"\0")!=0 && strcmp(wifi_password,"\0")!=0)
  48.     {
  49.         xSemaphoreGive(ap_sem);
  50.         ESP_LOGI(TAG, "set wifi name and password successfully! goto station mode");
  51.     }
  52.     return ESP_OK;
  53. }
复制代码

html的网页如下:这个网页包含了按钮的定义,以及发送json格式的数据。
最后送json数据的时候,要用JSON.stringify方法格式化data,否则esp32解析json会报错,此处一定要注意!!!
整体html界面非常简单,没有根本的也很容易读懂,里面写了一个js函数,该函数是在点击按钮的时候触发,功能重要是读取文本框输入的数据,将数据封装为json格式,然后post发送数据,xhttp.open(“POST”, “/wifi_data”, true);中的url “/wifi_data”和esp32服务端中的定义要一致,否则是无法成功的。
  1. <!DOCTYPE html>
  2. <head>
  3.     <meta charset="utf-8">
  4.     <title>Web server system</title>
  5. </head>
  6. <table class="fixed" border="5">
  7.     <col width="1000px" /><col width="500px" />
  8.     <tr><td>
  9.         <h2 style=" text-align:center;"> **** Web Server ***</h2>
  10.         <h3>wifi 密码配置</h3>
  11.     <div>
  12.         <label for="name">wifi名称</label>
  13.         <input type="text" id="wifi" name="car_name" placeholder="ssid">
  14.         <br>
  15.         <label for="type">密码</label>
  16.         <input type="text" id="code" name="car_type" placeholder="password">
  17.         <br>
  18.         <button id ="send_WIFI" type="button" onclick="send_wifi()">提交</button>
  19.     </div>
  20.     </td><td>
  21.         <table border="10">
  22.             <tr>
  23.                 <td>
  24.                     <label for="newfile">Upload a file</label>
  25.                 </td>
  26.                 <td colspan="2">
  27.                     <input id="newfile" type="file" onchange="setpath()" style="width:100%;">
  28.                 </td>
  29.             </tr>
  30.             <tr>
  31.                 <td>
  32.                     <label for="filepath">Set path on server</label>
  33.                 </td>
  34.                 <td>
  35.                     <input id="filepath" type="text" style="width:100%;">
  36.                 </td>
  37.                 <td>
  38.                     <button id="upload" type="button" onclick="upload()">Upload</button>
  39.                 </td>
  40.             </tr>
  41.         </table>
  42.     </td></tr>
  43. </table>
  44. <script>
  45. function setpath() {
  46.     var default_path = document.getElementById("newfile").files[0].name;
  47.     document.getElementById("filepath").value = default_path;
  48. }
  49. function upload() {
  50.     var filePath = document.getElementById("filepath").value;
  51.     var upload_path = "/upload/" + filePath;
  52.     var fileInput = document.getElementById("newfile").files;
  53.     /* Max size of an individual file. Make sure this
  54.      * value is same as that set in file_server.c */
  55.     var MAX_FILE_SIZE = 200*1024;
  56.     var MAX_FILE_SIZE_STR = "200KB";
  57.     if (fileInput.length == 0) {
  58.         alert("No file selected!");
  59.     } else if (filePath.length == 0) {
  60.         alert("File path on server is not set!");
  61.     } else if (filePath.indexOf(' ') >= 0) {
  62.         alert("File path on server cannot have spaces!");
  63.     } else if (filePath[filePath.length-1] == '/') {
  64.         alert("File name not specified after path!");
  65.     } else if (fileInput[0].size > 200*1024) {
  66.         alert("File size must be less than 200KB!");
  67.     } else {
  68.         document.getElementById("newfile").disabled = true;
  69.         document.getElementById("filepath").disabled = true;
  70.         document.getElementById("upload").disabled = true;
  71.         var file = fileInput[0];
  72.         var xhttp = new XMLHttpRequest();
  73.         xhttp.onreadystatechange = function() {
  74.             if (xhttp.readyState == 4) {
  75.                 if (xhttp.status == 200) {
  76.                     document.open();
  77.                     document.write(xhttp.responseText);
  78.                     document.close();
  79.                 } else if (xhttp.status == 0) {
  80.                     alert("Server closed the connection abruptly!");
  81.                     location.reload()
  82.                 } else {
  83.                     alert(xhttp.status + " Error!\n" + xhttp.responseText);
  84.                     location.reload()
  85.                 }
  86.             }
  87.         };
  88.         xhttp.open("POST", upload_path, true);
  89.         xhttp.send(file);
  90.     }
  91. }
  92. function send_wifi() {
  93.     var input_ssid = document.getElementById("wifi").value;
  94.     var input_code = document.getElementById("code").value;
  95.     var xhttp = new XMLHttpRequest();
  96.         xhttp.open("POST", "/wifi_data", true);
  97.         xhttp.onreadystatechange = function() {
  98.             if (xhttp.readyState == 4) {
  99.                 if (xhttp.status == 200) {
  100.                     console.log(xhttp.responseText);
  101.                 } else if (xhttp.status == 0) {
  102.                     alert("Server closed the connection abruptly!");
  103.                     location.reload()
  104.                 } else {
  105.                     alert(xhttp.status + " Error!\n" + xhttp.responseText);
  106.                     location.reload()
  107.                 }
  108.             }
  109.         };
  110.     var data = {
  111.         "wifi_name":input_ssid,
  112.         "wifi_code":input_code
  113.     }
  114.         xhttp.send(JSON.stringify(data));
  115. }
  116. </script>
复制代码



  1. static esp_err_t html_default_get_handler(httpd_req_t *req)
  2. {
  3.     // /* Send HTML file header */
  4.     // httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><body>");
  5.     /* Get handle to embedded file upload script */
  6.     extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
  7.     extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end");
  8.     const size_t upload_script_size = (upload_script_end - upload_script_start);
  9.     /* Add file upload form and script which on execution sends a POST request to /upload */
  10.     httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size);
  11.     /* Send remaining chunk of HTML file to complete it */
  12.     httpd_resp_sendstr_chunk(req, "</body></html>");
  13.     /* Send empty chunk to signal HTTP response completion */
  14.     httpd_resp_sendstr_chunk(req, NULL);
  15.     return ESP_OK;
  16. }
  17.     httpd_uri_t html_default = {
  18.         .uri = "/", // Match all URIs of type /path/to/file
  19.         .method = HTTP_GET,
  20.         .handler = html_default_get_handler,
  21.         .user_ctx = "html" // Pass server data as context
  22.     };
  23.     httpd_register_uri_handler(server, &html_default);
复制代码
这里要特殊注意:
1)、.uri = "/",这个表示当你打开网页的IP地点后第一个要显示的页面,也就是要放到根目录下("/"也就是根目录)。
2)、ESP32可以直接将html的网页编译进来不需要转为数组,但在CMakeList.txt文件中需要EMBED_FILES upload_script.html如许的情势引入网页文件。这个html编译出出来的文本文件怎么使用呢。wifi.html编译出来一样寻常名称是默认的_binary_名称_类型_start。这个指针代编译出来文件的起始地点。_binary_名称_类型_end,代表结束地点。wifi.html的引用方式如下。通过以下的方式就可以获得html这个大数组。
  1.     extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
  2.     extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end");
  3.     const size_t upload_script_size = (upload_script_end - upload_script_start);
  4.     /* Add file upload form and script which on execution sends a POST request to /upload */
  5.     httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size);
复制代码

3、实现URI处理函数
在注册URI处理器后,我们需要实现对应的处理器函数。URI处理器函数的原型为:
  1. typedef esp_err_t (*httpd_uri_func_t)(httpd_req_t *req);
复制代码
如上面示例中的:
  1. static esp_err_t html_default_get_handler(httpd_req_t *req)
复制代码
4、处理HTTP哀求
在URI处理器函数中,我们可以通过HTTP哀求信息结构体指针httpd_req_t获取HTTP哀求的各种参数和数据。以下是一些常用的HTTP哀求处理函数:
httpd_req_get_hdr_value_str:获取HTTP哀求头中指定字段的值(字符串格式)
httpd_req_get_url_query_str:获取HTTP哀求URL中的查询参数(字符串格式)
httpd_query_key_value:解析HTTP哀求URL中的查询参数,获取指定参数名的值(字符串格式)
httpd_req_recv:从HTTP哀求接收数据
httpd_req_send:发送HTTP相应数据
httpd_resp_set_type:设置HTTP相应内容的MIME类型
httpd_resp_send_chunk:分块发送HTTP相应数据。
例如,以下是一个URI处理器函数的示例,用于处理/echo路径的POST哀求:
  1. static esp_err_t echo_post_handler(httpd_req_t *req)
  2. {
  3.     char buf[1024];
  4.     int ret, remaining = req->content_len;
  5.     // 从HTTP请求中接收数据
  6.     while (remaining > 0) {
  7.         ret = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
  8.         if (ret <= 0) {
  9.             if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
  10.                 // 处理超时
  11.                 httpd_resp_send_408(req);
  12.             }
  13.             return ESP_FAIL;
  14.         }
  15.         // 处理接收到的数据
  16.         // ...
  17.         remaining -= ret;
  18.     }
  19.     // 发送HTTP响应
  20.     httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
  21.     httpd_resp_send(req, "Received data: ", -1);
  22.     httpd_resp_send_chunk(req, buf, req->content_len);
  23.     httpd_resp_send_chunk(req, NULL, 0);
  24.     return ESP_OK;
  25. }
复制代码
5、 发送相应
在URI处理函数中,可以使用httpd_resp_send()函数将相应发送回客户端。该函数需要传入一个httpd_req_t结构体作为参数,该结构体表示HTTP哀求和相应。
例如,在上面的hello_get_handler处理函数中,可以使用httpd_resp_send()函数将“Hello, World!”字符串作为相应发送回客户端:
  1. static esp_err_t hello_get_handler(httpd_req_t *req)
  2. {
  3.     const char* resp_str = "Hello, World!";
  4.     httpd_resp_send(req, resp_str, strlen(resp_str));
  5.     return ESP_OK;
  6. }
复制代码
二、重要使用API的说明
1. httpd_register_uri_handler
用于将HTTP哀求的URI路由随处理程序。这个函数接收两个参数:httpd_handle_t类型的HTTP服务器句柄和httpd_uri_t类型的URI配置。
2. httpd_handle_t
httpd_handle_t是HTTP服务器的一个句柄,它是通过httpd_start函数创建的。而httpd_uri_t则定义了HTTP哀求的URI信息,包括URI路径、HTTP哀求方法和处理函数等。
3. httpd_query_key_value获取变量值
httpd_query_key_value 用于从查询字符串中获取指定键的值。查询字符串是指URL中?后面的部分,包含多个键值对,每个键值对之间使用&分隔。例如,对于以下URL:
http://192.168.1.1/path/to/handler?key1=value1&key2=value2
获取其中的:
esp_err_t httpd_query_key_value(const char *query, const char *key, char *buf, size_t buf_len);
这是一个使用示例
  1. char query_str[] = "key1=value1&key2=value2";
  2. char key[] = "key1";
  3. char value[16];
  4. if (httpd_query_key_value(query_str, key, value, sizeof(value)) == ESP_OK) {
  5.     printf("value=%s\n", value);
  6. } else {
  7.     printf("key not found\n");
  8. }
复制代码
4. 获取get参数示例

下面定义的 handler 演示了怎样从哀求参数里解析 字符串param1和整型变量param2:
  1. esp_err_t index_handler(httpd_req_t *req)
  2. {
  3.      char* query_str = NULL;
  4.      char param1_value[10] = {0};
  5.      int param2_value=0;
  6.      query_str = strstr(req->uri, "?");
  7.      if(query_str!=NULL){
  8.          query_str ++;
  9.          httpd_query_key_value(query_str, "param1", param1_value, sizeof(param1_value));
  10.          char param2_str[10] = {0};
  11.          httpd_query_key_value(query_str, "param2", param2_str, sizeof(param2_str));
  12.          param2_value = atoi(param2_str);
  13.      }
  14.      char resp_str[50] = {0};
  15.      snprintf(resp_str, sizeof(resp_str), "param1=%s, param2=%d", param1_value, param2_value);
  16.     httpd_resp_send(req, resp_str, strlen(resp_str));
  17.     return ESP_OK;
  18. }
复制代码
5. 获取post参数示例

下面的示例代码中根据httpd_req_t的content_len来分配一个缓冲区,并解析哀求中的POST参数:
  1. esp_err_t post_demo_handler(httpd_req_t *req)
  2. {
  3.     char post_string[64];
  4.     int post_int=0;
  5.     if (req->content_len > 0)
  6.     {
  7.         // 从请求体中读取POST参数
  8.         char *buf = malloc(req->content_len + 1);
  9.         int ret = httpd_req_recv(req, buf, req->content_len);
  10.         if (ret <= 0)
  11.         {
  12.             // 接收数据出错
  13.             free(buf);
  14.             return ESP_FAIL;
  15.         }
  16.         buf[req->content_len] = '\0';
  17.         // 解析POST参数
  18.         char *param_str;
  19.         param_str = strtok(buf, "&");
  20.         while (param_str != NULL)
  21.         {
  22.             char *value_str = strchr(param_str, '=');
  23.             if (value_str != NULL)
  24.             {
  25.                 *value_str++ = '\0';
  26.                 if (strcmp(param_str, "post_string") == 0)
  27.                 {
  28.                     strncpy(post_string, value_str, sizeof(post_string));
  29.                 }
  30.                 else if (strcmp(param_str, "post_int") == 0)
  31.                 {
  32.                     post_int = atoi(value_str);
  33.                 }
  34.             }
  35.             param_str = strtok(NULL, "&");
  36.         }
  37.         free(buf);
  38.     }
  39.     // 将结果打印输出
  40.     printf("post_string=%s, post_int=%d\n", post_string, post_int);
  41.     // 返回成功
  42.     httpd_resp_send(req, NULL, 0);
  43.     return ESP_OK;
  44. }
  45. httpd_uri_t post_uri = {
  46.          .uri = "/post",
  47.          .method = HTTP_POST,
  48.          .handler = post_demo_handler,
  49.          .user_ctx = NULL
  50. };
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

花瓣小跑

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表