如何搭建http的webserver服務(wù)器
最近在使用ESP32搭建web服務(wù)器測(cè)試,發(fā)現(xiàn)esp32搭建這類開(kāi)發(fā)環(huán)境還是比較方便的。具體的http協(xié)議這里就不再贅述,我們主要說(shuō)一下如何使用ESP32提供的API來(lái)搭建我們的http web。
一、web服務(wù)器搭建過(guò)程
1、配置web服務(wù)器
在ESP-IDF中,Web服務(wù)器使用httpd組件實(shí)現(xiàn)。我們需要先創(chuàng)建httpd_config_t結(jié)構(gòu)體,指定服務(wù)器的端口、最大并發(fā)連接數(shù)、URI匹配處理器等選項(xiàng)。然后,我們通過(guò)調(diào)用httpd_start函數(shù)來(lái)啟動(dòng)Web服務(wù)器。(使用默認(rèn)的配置就可以,包括端口號(hào)都已經(jīng)默認(rèn)配置好了)
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
// 設(shè)置服務(wù)器端口為80
config.server_port = 80;
// 創(chuàng)建HTTP服務(wù)器句柄
if (httpd_start(&server, &config) != ESP_OK) {
printf("Error starting server!\n");
return;
}
2、 注冊(cè) URI處理器
在Web服務(wù)器啟動(dòng)后,我們需要為不同的URI注冊(cè)處理器函數(shù)。當(dāng)Web服務(wù)器接收到請(qǐng)求時(shí),會(huì)根據(jù)請(qǐng)求的URI選擇相應(yīng)的處理器函數(shù)進(jìn)行處理。在ESP-IDF中,我們可以使用httpd_register_uri_handler函數(shù)注冊(cè)URI處理器。該函數(shù)的原型如下
esp_err_t httpd_register_uri_handler(httpd_handle_t hd, const httpd_uri_t *uri)
其中,hd參數(shù)為HTTP服務(wù)器句柄;uri參數(shù)為包含URI路徑、HTTP方法、處理函數(shù)等信息的結(jié)構(gòu)體指針。例如:
static const httpd_uri_t echo = {
.uri = "/",
.method = HTTP_POST,
.handler = echo_post_handler,
.user_ctx = NULL
};
static esp_err_t echo_post_handler(httpd_req_t *req)
{
char buf[100];
// char ssid[10];
// char pswd[10];
int ret, remaining = req->content_len;
while (remaining > 0) {
/* Read the data for the request */
if ((ret = httpd_req_recv(req, buf,
MIN(remaining, sizeof(buf)))) <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
/* Retry receiving if timeout occurred */
continue;
}
return ESP_FAIL;
}
/* Send back the same data */
httpd_resp_send_chunk(req, buf, ret);
remaining -= ret;
esp_err_t e = httpd_query_key_value(buf,"ssid",wifi_name,sizeof(wifi_name));
if(e == ESP_OK) {
printf("ssid = %s\r\n",wifi_name);
}
else {
printf("error = %d\r\n",e);
}
e = httpd_query_key_value(buf,"password",wifi_password,sizeof(wifi_password));
if(e == ESP_OK) {
printf("pswd = %s\r\n",wifi_password);
}
else {
printf("error = %d\r\n",e);
}
/* Log data received */
ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
ESP_LOGI(TAG, "%.*s", ret, buf);
ESP_LOGI(TAG, "====================================");
}
// End response
httpd_resp_send_chunk(req, NULL, 0);
if(strcmp(wifi_name ,"\0")!=0 && strcmp(wifi_password,"\0")!=0)
{
xSemaphoreGive(ap_sem);
ESP_LOGI(TAG, "set wifi name and password successfully! goto station mode");
}
return ESP_OK;
}html的網(wǎng)頁(yè)如下:這個(gè)網(wǎng)頁(yè)包含了按鈕的定義,以及發(fā)送json格式的數(shù)據(jù)。
最后送json數(shù)據(jù)的時(shí)候,要用JSON.stringify方法格式化data,否則esp32解析json會(huì)報(bào)錯(cuò),此處一定要注意?。?!
整體html界面非常簡(jiǎn)單,沒(méi)有基礎(chǔ)的也很容易讀懂,里面寫了一個(gè)js函數(shù),該函數(shù)是在點(diǎn)擊按鈕的時(shí)候觸發(fā),功能主要是讀取文本框輸入的數(shù)據(jù),將數(shù)據(jù)封裝為json格式,然后post發(fā)送數(shù)據(jù),xhttp.open(“POST”, “/wifi_data”, true);中的url “/wifi_data”和esp32服務(wù)端中的定義要一致,否則是無(wú)法成功的。
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Web server system</title>
</head>
<table class="fixed" border="5">
<col width="1000px" /><col width="500px" />
<tr><td>
<h2 style=" text-align:center;"> **** Web Server ***</h2>
<h3>wifi 密碼配置</h3>
<div>
<label for="name">wifi名稱</label>
<input type="text" id="wifi" name="car_name" placeholder="ssid">
<br>
<label for="type">密碼</label>
<input type="text" id="code" name="car_type" placeholder="password">
<br>
<button id ="send_WIFI" type="button" onclick="send_wifi()">提交</button>
</div>
</td><td>
<table border="10">
<tr>
<td>
<label for="newfile">Upload a file</label>
</td>
<td colspan="2">
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
</td>
</tr>
<tr>
<td>
<label for="filepath">Set path on server</label>
</td>
<td>
<input id="filepath" type="text" style="width:100%;">
</td>
<td>
<button id="upload" type="button" onclick="upload()">Upload</button>
</td>
</tr>
</table>
</td></tr>
</table>
<script>
function setpath() {
var default_path = document.getElementById("newfile").files[0].name;
document.getElementById("filepath").value = default_path;
}
function upload() {
var filePath = document.getElementById("filepath").value;
var upload_path = "/upload/" + filePath;
var fileInput = document.getElementById("newfile").files;
/* Max size of an individual file. Make sure this
* value is same as that set in file_server.c */
var MAX_FILE_SIZE = 200*1024;
var MAX_FILE_SIZE_STR = "200KB";
if (fileInput.length == 0) {
alert("No file selected!");
} else if (filePath.length == 0) {
alert("File path on server is not set!");
} else if (filePath.indexOf(' ') >= 0) {
alert("File path on server cannot have spaces!");
} else if (filePath[filePath.length-1] == '/') {
alert("File name not specified after path!");
} else if (fileInput[0].size > 200*1024) {
alert("File size must be less than 200KB!");
} else {
document.getElementById("newfile").disabled = true;
document.getElementById("filepath").disabled = true;
document.getElementById("upload").disabled = true;
var file = fileInput[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
document.open();
document.write(xhttp.responseText);
document.close();
} else if (xhttp.status == 0) {
alert("Server closed the connection abruptly!");
location.reload()
} else {
alert(xhttp.status + " Error!\n" + xhttp.responseText);
location.reload()
}
}
};
xhttp.open("POST", upload_path, true);
xhttp.send(file);
}
}
function send_wifi() {
var input_ssid = document.getElementById("wifi").value;
var input_code = document.getElementById("code").value;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "/wifi_data", true);
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
console.log(xhttp.responseText);
} else if (xhttp.status == 0) {
alert("Server closed the connection abruptly!");
location.reload()
} else {
alert(xhttp.status + " Error!\n" + xhttp.responseText);
location.reload()
}
}
};
var data = {
"wifi_name":input_ssid,
"wifi_code":input_code
}
xhttp.send(JSON.stringify(data));
}
</script>static esp_err_t html_default_get_handler(httpd_req_t *req)
{
// /* Send HTML file header */
// httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><body>");
/* Get handle to embedded file upload script */
extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end");
const size_t upload_script_size = (upload_script_end - upload_script_start);
/* Add file upload form and script which on execution sends a POST request to /upload */
httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size);
/* Send remaining chunk of HTML file to complete it */
httpd_resp_sendstr_chunk(req, "</body></html>");
/* Send empty chunk to signal HTTP response completion */
httpd_resp_sendstr_chunk(req, NULL);
return ESP_OK;
}
httpd_uri_t html_default = {
.uri = "/", // Match all URIs of type /path/to/file
.method = HTTP_GET,
.handler = html_default_get_handler,
.user_ctx = "html" // Pass server data as context
};
httpd_register_uri_handler(server, &html_default);這里要特別注意:
1)、.uri = "/",這個(gè)表示當(dāng)你打開(kāi)網(wǎng)頁(yè)的IP地址后第一個(gè)要顯示的頁(yè)面,也就是要放到根目錄下("/"也就是根目錄)。
2)、ESP32可以直接將html的網(wǎng)頁(yè)編譯進(jìn)來(lái)不需要轉(zhuǎn)為數(shù)組,但在CMakeList.txt文件中需要EMBED_FILES upload_script.html這樣的形式引入網(wǎng)頁(yè)文件。這個(gè)html編譯出出來(lái)的文本文件怎么使用呢。wifi.html編譯出來(lái)一般名稱是默認(rèn)的_binary_名稱_類型_start。這個(gè)指針代編譯出來(lái)文件的起始地址。_binary_名稱_類型_end,代表結(jié)束地址。wifi.html的引用方式如下。通過(guò)以下的方式就可以獲得html這個(gè)大數(shù)組。
extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end");
const size_t upload_script_size = (upload_script_end - upload_script_start);
/* Add file upload form and script which on execution sends a POST request to /upload */
httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size);3、實(shí)現(xiàn)URI處理函數(shù)
在注冊(cè)URI處理器后,我們需要實(shí)現(xiàn)對(duì)應(yīng)的處理器函數(shù)。URI處理器函數(shù)的原型為:
typedef esp_err_t (*httpd_uri_func_t)(httpd_req_t *req);
如上面示例中的:
static esp_err_t html_default_get_handler(httpd_req_t *req)
4、處理HTTP請(qǐng)求
在URI處理器函數(shù)中,我們可以通過(guò)HTTP請(qǐng)求信息結(jié)構(gòu)體指針httpd_req_t獲取HTTP請(qǐng)求的各種參數(shù)和數(shù)據(jù)。以下是一些常用的HTTP請(qǐng)求處理函數(shù):
httpd_req_get_hdr_value_str:獲取HTTP請(qǐng)求頭中指定字段的值(字符串格式)
httpd_req_get_url_query_str:獲取HTTP請(qǐng)求URL中的查詢參數(shù)(字符串格式)
httpd_query_key_value:解析HTTP請(qǐng)求URL中的查詢參數(shù),獲取指定參數(shù)名的值(字符串格式)
httpd_req_recv:從HTTP請(qǐng)求接收數(shù)據(jù)
httpd_req_send:發(fā)送HTTP響應(yīng)數(shù)據(jù)
httpd_resp_set_type:設(shè)置HTTP響應(yīng)內(nèi)容的MIME類型
httpd_resp_send_chunk:分塊發(fā)送HTTP響應(yīng)數(shù)據(jù)。
例如,以下是一個(gè)URI處理器函數(shù)的示例,用于處理/echo路徑的POST請(qǐng)求:
static esp_err_t echo_post_handler(httpd_req_t *req)
{
char buf[1024];
int ret, remaining = req->content_len;
// 從HTTP請(qǐng)求中接收數(shù)據(jù)
while (remaining > 0) {
ret = httpd_req_recv(req, buf, MIN(remaining, sizeof(buf)));
if (ret <= 0) {
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
// 處理超時(shí)
httpd_resp_send_408(req);
}
return ESP_FAIL;
}
// 處理接收到的數(shù)據(jù)
// ...
remaining -= ret;
}
// 發(fā)送HTTP響應(yīng)
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
httpd_resp_send(req, "Received data: ", -1);
httpd_resp_send_chunk(req, buf, req->content_len);
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
5、 發(fā)送響應(yīng)
在URI處理函數(shù)中,可以使用httpd_resp_send()函數(shù)將響應(yīng)發(fā)送回客戶端。該函數(shù)需要傳入一個(gè)httpd_req_t結(jié)構(gòu)體作為參數(shù),該結(jié)構(gòu)體表示HTTP請(qǐng)求和響應(yīng)。
例如,在上面的hello_get_handler處理函數(shù)中,可以使用httpd_resp_send()函數(shù)將“Hello, World!”字符串作為響應(yīng)發(fā)送回客戶端:
static esp_err_t hello_get_handler(httpd_req_t *req)
{
const char* resp_str = "Hello, World!";
httpd_resp_send(req, resp_str, strlen(resp_str));
return ESP_OK;
}二、主要使用API的說(shuō)明
1. httpd_register_uri_handler
用于將HTTP請(qǐng)求的URI路由到處理程序。這個(gè)函數(shù)接收兩個(gè)參數(shù):httpd_handle_t類型的HTTP服務(wù)器句柄和httpd_uri_t類型的URI配置。
2. httpd_handle_t
httpd_handle_t是HTTP服務(wù)器的一個(gè)句柄,它是通過(guò)httpd_start函數(shù)創(chuàng)建的。而httpd_uri_t則定義了HTTP請(qǐng)求的URI信息,包括URI路徑、HTTP請(qǐng)求方法和處理函數(shù)等。
3. httpd_query_key_value獲取變量值
httpd_query_key_value 用于從查詢字符串中獲取指定鍵的值。查詢字符串是指URL中?后面的部分,包含多個(gè)鍵值對(duì),每個(gè)鍵值對(duì)之間使用&分隔。例如,對(duì)于以下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);
這是一個(gè)使用示例
char query_str[] = "key1=value1&key2=value2";
char key[] = "key1";
char value[16];
if (httpd_query_key_value(query_str, key, value, sizeof(value)) == ESP_OK) {
printf("value=%s\n", value);
} else {
printf("key not found\n");
}
4. 獲取get參數(shù)示例
下面定義的 handler 演示了如何從請(qǐng)求參數(shù)里解析 字符串param1和整型變量param2:
esp_err_t index_handler(httpd_req_t *req)
{
char* query_str = NULL;
char param1_value[10] = {0};
int param2_value=0;
query_str = strstr(req->uri, "?");
if(query_str!=NULL){
query_str ++;
httpd_query_key_value(query_str, "param1", param1_value, sizeof(param1_value));
char param2_str[10] = {0};
httpd_query_key_value(query_str, "param2", param2_str, sizeof(param2_str));
param2_value = atoi(param2_str);
}
char resp_str[50] = {0};
snprintf(resp_str, sizeof(resp_str), "param1=%s, param2=%d", param1_value, param2_value);
httpd_resp_send(req, resp_str, strlen(resp_str));
return ESP_OK;
}5. 獲取post參數(shù)示例
下面的示例代碼中根據(jù)httpd_req_t的content_len來(lái)分配一個(gè)緩沖區(qū),并解析請(qǐng)求中的POST參數(shù):
esp_err_t post_demo_handler(httpd_req_t *req)
{
char post_string[64];
int post_int=0;
if (req->content_len > 0)
{
// 從請(qǐng)求體中讀取POST參數(shù)
char *buf = malloc(req->content_len + 1);
int ret = httpd_req_recv(req, buf, req->content_len);
if (ret <= 0)
{
// 接收數(shù)據(jù)出錯(cuò)
free(buf);
return ESP_FAIL;
}
buf[req->content_len] = '\0';
// 解析POST參數(shù)
char *param_str;
param_str = strtok(buf, "&");
while (param_str != NULL)
{
char *value_str = strchr(param_str, '=');
if (value_str != NULL)
{
*value_str++ = '\0';
if (strcmp(param_str, "post_string") == 0)
{
strncpy(post_string, value_str, sizeof(post_string));
}
else if (strcmp(param_str, "post_int") == 0)
{
post_int = atoi(value_str);
}
}
param_str = strtok(NULL, "&");
}
free(buf);
}
// 將結(jié)果打印輸出
printf("post_string=%s, post_int=%d\n", post_string, post_int);
// 返回成功
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
httpd_uri_t post_uri = {
.uri = "/post",
.method = HTTP_POST,
.handler = post_demo_handler,
.user_ctx = NULL
};到此這篇關(guān)于搭建http的webserver的服務(wù)器的文章就介紹到這了,更多相關(guān)http的webserver服務(wù)器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
理解web服務(wù)器和數(shù)據(jù)庫(kù)的負(fù)載均衡以及反向代理
這里的“負(fù)載均衡”是指在網(wǎng)站建設(shè)中應(yīng)該考慮的“負(fù)載均衡”。假設(shè)我們要搭建一個(gè)網(wǎng)站:aaa.me,我們使用的web服務(wù)器每秒能處理100條請(qǐng)求,而aaa.me這個(gè)網(wǎng)站最火的時(shí)候也只是每秒99條請(qǐng)求,那么我們使用一個(gè)服務(wù)器是完全可以的2014-04-04
iptables如何配置NAT實(shí)現(xiàn)端口轉(zhuǎn)發(fā)
這篇文章主要介紹了iptables如何配置NAT實(shí)現(xiàn)端口轉(zhuǎn)發(fā),本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-06-06
游戲服務(wù)器中的Netty應(yīng)用以及源碼剖析
這篇文章主要為大家介紹了游戲服務(wù)器中的Netty應(yīng)用以及源碼剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
解決Navicat?連接服務(wù)器不成功的問(wèn)題(Access?denied?for?user?'root
這篇文章主要介紹了Navicat?連接服務(wù)器不成功(Access?denied?for?user?'root'@?'*.*.*.*'?(using?password:?YES)),出現(xiàn)這種原因一般是服務(wù)器的root用戶沒(méi)有開(kāi)啟訪問(wèn)權(quán)限,本文給大家分享解決方法,需要的朋友可以參考下2022-09-09
jenkins插件pipeline集成持續(xù)交付管道全面介紹
這篇文章主要就jenkins插件pipeline集成持續(xù)交付管道相關(guān)內(nèi)容做一個(gè)全面介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
如何將pem證書轉(zhuǎn)換為.crt和.key與證書格式介紹
這篇文章主要介紹了如何將pem證書轉(zhuǎn)換為.crt和.key與證書格式介紹,需要的朋友可以參考下2024-01-01
云服務(wù)器(Linux)安裝部署Kafka的詳細(xì)過(guò)程
這篇文章主要介紹了云服務(wù)器(Linux)安裝部署Kafka的詳細(xì)過(guò)程,kafka的安裝需要依賴于jdk,需要在服務(wù)器上提前安裝好該環(huán)境,這里使用用jdk1.8,本文給大家詳細(xì)介紹感興趣的朋友跟隨小編一起看看吧2022-11-11
OnZoom基于Apache Hudi的一體架構(gòu)實(shí)踐解析
這篇文章主要介紹了OnZoom基于Apache Hudi的一體架構(gòu)實(shí)踐, 有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03
Win2003 Server DHCP服務(wù)器安裝圖解教程
為了節(jié)省IP地址資源,IP地址采用了DHCP自動(dòng)分配方式2012-10-10

