一、背景
![6793005-c169baaf737e0c82.png](https://upload-images.jianshu.io/upload_images/6793005-c169baaf737e0c82.png)
WebServer作为HTTP服务器,接收手机APP请求,配置网关接入网络。
Post请求和Get请求:
![6793005-955abae7579d122c.png](https://upload-images.jianshu.io/upload_images/6793005-955abae7579d122c.png)
二、流程
① 初始化WebServer,绑定本地端口
② 开启监听
③ 解析请求数据包
④ 回发响应数据包
三、初始化WebServer
文件所在 ESP8266_NONOS_SDK-2.1.0\IoT_Demo\user/user_webserver.c。
创建TCP服务端,参考ESP8266学习笔记(5)——TCP/UDP接口使用。
传入参数,本地端口80。
void ICACHE_FLASH_ATTR
user_webserver_init(uint32 port)
{
tcp_websvr_espconn.type = ESPCONN_TCP;
tcp_websvr_espconn.state = ESPCONN_NONE;
tcp_websvr_espconn.proto.tcp = &esptcp;
tcp_websvr_espconn.proto.tcp->local_port = port; // 设置本地端口
espconn_regist_connectcb(&tcp_websvr_espconn, webserver_listen); // 注册监听成功回调函数
espconn_accept(&tcp_websvr_espconn); // 创建 TCP server,建立监听
}
四、开启监听
4.1 监听成功回调函数
LOCAL void ICACHE_FLASH_ATTR
webserver_listen(void *arg)
{
struct espconn *pesp_conn = arg;
espconn_regist_recvcb(pesp_conn, webserver_recv); // 注册接收回调函数
espconn_regist_reconcb(pesp_conn, webserver_recon); // 注册发送回调函数
espconn_regist_disconcb(pesp_conn, webserver_discon); // 注册断连回调函数
}
4.2 接收回调函数
里面POST请求中解析JSON数据参考ESP8266学习笔记(7)——JSON接口使用
/******************************************************************************
* FunctionName : webserver_recv
* Description : Processing the received data from the server
* Parameters : arg -- Additional argument to pass to the callback function
* pusrdata -- The received data (or NULL when the connection has been closed!)
* length -- The length of received data
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
webserver_recv(void *arg, char *pusrdata, unsigned short length)
{
URL_Frame *pURL_Frame = NULL;
char *pParseBuffer = NULL;
bool parse_flag = false; // 解析标志
struct espconn *ptrespconn = arg;
/* 如果设备没有在升级 */
if (upgrade_lock == 0)
{
os_printf("len:%u\n",length);
/* 校验数据是否缺失,如果失败跳转做处理 */
if (check_data(pusrdata, length) == false) // ------------------下述 5.1
{
os_printf("goto\n");
goto _temp_exit;
}
/* 校验数据长度后存储起来,开启解析标志 */
parse_flag = save_data(pusrdata, length); // ------------------下述 5.2
if (parse_flag == false)
{
response_send(ptrespconn, false); // ------------------下述 6.1
}
os_printf("pusrdata:%s\n", pusrdata);
/* 为URL框架申请空间 */
pURL_Frame = (URL_Frame *)os_zalloc(sizeof(URL_Frame)); // ------------------下述 5.3
/* 解析数据,加入URL框架 */
parse_url(precvbuffer, pURL_Frame); // ------------------下述 5.4
os_printf("Type:%d, Select:%s, Command:%s, Filename:%s ",
pURL_Frame->Type,pURL_Frame->pSelect,pURL_Frame->pCommand,pURL_Frame->pFilename);
switch (pURL_Frame->Type)
{
case GET:
os_printf("We have a GET request.\n");
if (os_strcmp(pURL_Frame->pSelect, "client") == 0 &&
os_strcmp(pURL_Frame->pCommand, "command") == 0)
{
if (os_strcmp(pURL_Frame->pFilename, "info") == 0)
{
json_send(ptrespconn, INFOMATION);
}
if (os_strcmp(pURL_Frame->pFilename, "status") == 0)
{
json_send(ptrespconn, CONNECT_STATUS);
}
else if (os_strcmp(pURL_Frame->pFilename, "scan") == 0)
{
char *strstr = NULL;
strstr = (char *)os_strstr(pusrdata, "&");
if (strstr == NULL)
{
if (pscaninfo == NULL)
{
pscaninfo = (scaninfo *)os_zalloc(sizeof(scaninfo));
}
pscaninfo->pespconn = ptrespconn;
pscaninfo->pagenum = 0;
pscaninfo->page_sn = 0;
pscaninfo->data_cnt = 0;
wifi_station_scan(NULL, json_scan_cb);
} else {
strstr ++;
if (os_strncmp(strstr, "page", 4) == 0) {
if (pscaninfo != NULL) {
pscaninfo->pagenum = *(strstr + 5);
pscaninfo->pagenum -= 0x30;
if (pscaninfo->pagenum > pscaninfo->totalpage || pscaninfo->pagenum == 0) {
response_send(ptrespconn, false);
} else {
json_send(ptrespconn, SCAN);
}
} else {
response_send(ptrespconn, false);
}
} else if(os_strncmp(strstr, "finish", 6) == 0){
bss_temp = bss_head;
while(bss_temp != NULL) {
bss_head = bss_temp->next.stqe_next;
os_free(bss_temp);
bss_temp = bss_head;
}
bss_head = NULL;
bss_temp = NULL;
response_send(ptrespconn, true);
} else {
response_send(ptrespconn, false);
}
}
} else {
response_send(ptrespconn, false);
}
} else if (os_strcmp(pURL_Frame->pSelect, "config") == 0 &&
os_strcmp(pURL_Frame->pCommand, "command") == 0) {
if (os_strcmp(pURL_Frame->pFilename, "wifi") == 0) {
ap_conf = (struct softap_config *)os_zalloc(sizeof(struct softap_config));
sta_conf = (struct station_config *)os_zalloc(sizeof(struct station_config));
json_send(ptrespconn, WIFI);
os_free(sta_conf);
os_free(ap_conf);
sta_conf = NULL;
ap_conf = NULL;
}
#if PLUG_DEVICE
else if (os_strcmp(pURL_Frame->pFilename, "switch") == 0) {
json_send(ptrespconn, SWITCH_STATUS);
}
#endif
else if (os_strcmp(pURL_Frame->pFilename, "reboot") == 0) {
json_send(ptrespconn, REBOOT);
} else {
response_send(ptrespconn, false);
}
} else if (os_strcmp(pURL_Frame->pSelect, "upgrade") == 0 &&
os_strcmp(pURL_Frame->pCommand, "command") == 0) {
if (os_strcmp(pURL_Frame->pFilename, "getuser") == 0) {
json_send(ptrespconn , USER_BIN);
}
} else {
response_send(ptrespconn, false);
}
break;
case POST:
os_printf("We have a POST request.\n");
pParseBuffer = (char *)os_strstr(precvbuffer, "\r\n\r\n");
if (pParseBuffer == NULL) {
break;
}
pParseBuffer += 4;
if (os_strcmp(pURL_Frame->pSelect, "config") == 0 &&
os_strcmp(pURL_Frame->pCommand, "command") == 0) {
if (os_strcmp(pURL_Frame->pFilename, "reboot") == 0) {
if (pParseBuffer != NULL) {
if (restart_10ms != NULL) {
os_timer_disarm(restart_10ms);
}
if (rstparm == NULL) {
rstparm = (rst_parm *)os_zalloc(sizeof(rst_parm));
}
rstparm->pespconn = ptrespconn;
rstparm->parmtype = REBOOT;
if (restart_10ms == NULL) {
restart_10ms = (os_timer_t *)os_malloc(sizeof(os_timer_t));
}
os_timer_setfn(restart_10ms, (os_timer_func_t *)restart_10ms_cb, NULL);
os_timer_arm(restart_10ms, 10, 0); // delay 10ms, then do
response_send(ptrespconn, true);
} else {
response_send(ptrespconn, false);
}
} else if (os_strcmp(pURL_Frame->pFilename, "wifi") == 0) {
if (pParseBuffer != NULL) {
struct jsontree_context js;
user_esp_platform_set_connect_status(DEVICE_CONNECTING);
if (restart_10ms != NULL) {
os_timer_disarm(restart_10ms);
}
if (ap_conf == NULL) {
ap_conf = (struct softap_config *)os_zalloc(sizeof(struct softap_config));
}
if (sta_conf == NULL) {
sta_conf = (struct station_config *)os_zalloc(sizeof(struct station_config));
}
jsontree_setup(&js, (struct jsontree_value *)&wifi_req_tree, json_putchar);
json_parse(&js, pParseBuffer);
if (rstparm == NULL) {
rstparm = (rst_parm *)os_zalloc(sizeof(rst_parm));
}
rstparm->pespconn = ptrespconn;
rstparm->parmtype = WIFI;
if (sta_conf->ssid[0] != 0x00 || ap_conf->ssid[0] != 0x00) {
ap_conf->ssid_hidden = 0;
ap_conf->max_connection = 4;
if (restart_10ms == NULL) {
restart_10ms = (os_timer_t *)os_malloc(sizeof(os_timer_t));
}
os_timer_disarm(restart_10ms);
os_timer_setfn(restart_10ms, (os_timer_func_t *)restart_10ms_cb, NULL);
os_timer_arm(restart_10ms, 10, 0); // delay 10ms, then do
} else {
os_free(ap_conf);
os_free(sta_conf);
os_free(rstparm);
sta_conf = NULL;
ap_conf = NULL;
rstparm =NULL;
}
response_send(ptrespconn, true);
} else {
response_send(ptrespconn, false);
}
}
else if (os_strcmp(pURL_Frame->pFilename, "switch") == 0) {
if (pParseBuffer != NULL) {
struct jsontree_context js;
jsontree_setup(&js, (struct jsontree_value *)&StatusTree, json_putchar);
json_parse(&js, pParseBuffer);
// ----------Altered by Leung 2018.11.12-------START
if (user_plug_get_status() == 1) // 获取继电器状态
{
data_send(ptrespconn, true, "{\"status\": 1}"); // 返回APP继电器状态——打开
}
else
{
data_send(ptrespconn, true, "{\"status\": 0}"); // 返回APP继电器状态——关闭
}
// ----------Altered by Leung 2018.11.12-------END
} else {
response_send(ptrespconn, false);
}
}
else {
response_send(ptrespconn, false);
}
}
else if(os_strcmp(pURL_Frame->pSelect, "upgrade") == 0 &&
os_strcmp(pURL_Frame->pCommand, "command") == 0){
if (os_strcmp(pURL_Frame->pFilename, "start") == 0){
response_send(ptrespconn, true);
os_printf("local upgrade start\n");
upgrade_lock = 1;
system_upgrade_init();
system_upgrade_flag_set(UPGRADE_FLAG_START);
os_timer_disarm(&upgrade_check_timer);
os_timer_setfn(&upgrade_check_timer, (os_timer_func_t *)upgrade_check_func, NULL);
os_timer_arm(&upgrade_check_timer, 120000, 0);
} else if (os_strcmp(pURL_Frame->pFilename, "reset") == 0) {
response_send(ptrespconn, true);
os_printf("local upgrade restart\n");
system_upgrade_reboot();
} else {
response_send(ptrespconn, false);
}
}else {
response_send(ptrespconn, false);
}
break;
}
if (precvbuffer != NULL){
os_free(precvbuffer);
precvbuffer = NULL;
}
os_free(pURL_Frame);
pURL_Frame = NULL;
_temp_exit:
;
}
else if(upgrade_lock == 1){
local_upgrade_download(ptrespconn,pusrdata, length);
if (precvbuffer != NULL){
os_free(precvbuffer);
precvbuffer = NULL;
}
os_free(pURL_Frame);
pURL_Frame = NULL;
}
}
五、解析请求数据包
5.1 check_data()
校验收到数据是否正确,判断收到数据的总长度是否等于“Content-Length:”里所描述的长度。
LOCAL bool ICACHE_FLASH_ATTR
check_data(char *precv, uint16 length)
{
char length_buf[10] = {0};
char *ptemp = NULL;
char *pdata = NULL;
char *tmp_precvbuffer;
uint16 tmp_length = length;
uint32 tmp_totallength = 0;
ptemp = (char *)os_strstr(precv, "\r\n\r\n");
if (ptemp != NULL) {
tmp_length -= ptemp - precv;
tmp_length -= 4;
tmp_totallength += tmp_length;
pdata = (char *)os_strstr(precv, "Content-Length: ");
if (pdata != NULL){
pdata += 16;
tmp_precvbuffer = (char *)os_strstr(pdata, "\r\n");
if (tmp_precvbuffer != NULL){
os_memcpy(length_buf, pdata, tmp_precvbuffer - pdata);
dat_sumlength = atoi(length_buf);
os_printf("A_dat:%u,tot:%u,lenght:%u\n",dat_sumlength,tmp_totallength,tmp_length);
if(dat_sumlength != tmp_totallength){
return false;
}
}
}
}
return true;
}
5.2 save_data()
将收到的数据校验长度后,存储起来。
LOCAL char *precvbuffer;
static uint32 dat_sumlength = 0;
LOCAL bool ICACHE_FLASH_ATTR
save_data(char *precv, uint16 length)
{
bool flag = false;
char length_buf[10] = {0};
char *ptemp = NULL;
char *pdata = NULL;
uint16 headlength = 0;
static uint32 totallength = 0;
ptemp = (char *)os_strstr(precv, "\r\n\r\n");
if (ptemp != NULL) {
length -= ptemp - precv;
length -= 4;
totallength += length;
headlength = ptemp - precv + 4;
pdata = (char *)os_strstr(precv, "Content-Length: ");
if (pdata != NULL) {
pdata += 16;
precvbuffer = (char *)os_strstr(pdata, "\r\n");
if (precvbuffer != NULL) {
os_memcpy(length_buf, pdata, precvbuffer - pdata);
dat_sumlength = atoi(length_buf);
}
} else {
if (totallength != 0x00){
totallength = 0;
dat_sumlength = 0;
return false;
}
}
if ((dat_sumlength + headlength) >= 1024) {
precvbuffer = (char *)os_zalloc(headlength + 1);
os_memcpy(precvbuffer, precv, headlength + 1);
} else {
precvbuffer = (char *)os_zalloc(dat_sumlength + headlength + 1);
os_memcpy(precvbuffer, precv, os_strlen(precv));
}
} else {
if (precvbuffer != NULL) {
totallength += length;
os_memcpy(precvbuffer + os_strlen(precvbuffer), precv, length);
} else {
totallength = 0;
dat_sumlength = 0;
return false;
}
}
if (totallength == dat_sumlength) {
totallength = 0;
dat_sumlength = 0;
return true;
} else {
return false;
}
}
5.3 URL_Frame
typedef struct URL_Frame {
enum ProtocolType Type;
char pSelect[URLSize];
char pCommand[URLSize];
char pFilename[URLSize];
} URL_Frame;
5.4 parse_url()
/******************************************************************************
* FunctionName : parse_url
* Description : parse the received data from the server
* Parameters : precv -- the received data
* purl_frame -- the result of parsing the url
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
parse_url(char *precv, URL_Frame *purl_frame)
{
char *str = NULL;
uint8 length = 0;
char *pbuffer = NULL;
char *pbufer = NULL;
if (purl_frame == NULL || precv == NULL) {
return;
}
pbuffer = (char *)os_strstr(precv, "Host:");
if (pbuffer != NULL) {
length = pbuffer - precv;
pbufer = (char *)os_zalloc(length + 1);
pbuffer = pbufer;
os_memcpy(pbuffer, precv, length);
os_memset(purl_frame->pSelect, 0, URLSize);
os_memset(purl_frame->pCommand, 0, URLSize);
os_memset(purl_frame->pFilename, 0, URLSize);
if (os_strncmp(pbuffer, "GET ", 4) == 0) {
purl_frame->Type = GET;
pbuffer += 4;
} else if (os_strncmp(pbuffer, "POST ", 5) == 0) {
purl_frame->Type = POST;
pbuffer += 5;
}
pbuffer ++;
str = (char *)os_strstr(pbuffer, "?");
if (str != NULL) {
length = str - pbuffer;
os_memcpy(purl_frame->pSelect, pbuffer, length);
str ++;
pbuffer = (char *)os_strstr(str, "=");
if (pbuffer != NULL) {
length = pbuffer - str;
os_memcpy(purl_frame->pCommand, str, length);
pbuffer ++;
str = (char *)os_strstr(pbuffer, "&");
if (str != NULL) {
length = str - pbuffer;
os_memcpy(purl_frame->pFilename, pbuffer, length);
} else {
str = (char *)os_strstr(pbuffer, " HTTP");
if (str != NULL) {
length = str - pbuffer;
os_memcpy(purl_frame->pFilename, pbuffer, length);
}
}
}
}
os_free(pbufer);
} else {
return;
}
}
六、回发响应数据包
6.1 response_send()
回复简单格式,里面调用data_send()
/******************************************************************************
* FunctionName : response_send
* Description : processing the send result
* Parameters : arg -- argument to set for client or server
* responseOK -- true or false
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
response_send(void *arg, bool responseOK)
{
struct espconn *ptrespconn = arg;
data_send(ptrespconn, responseOK, NULL);
}
6.2 json_send()
里面生成JSON数据json_ws_send()参考ESP8266学习笔记(7)——JSON接口使用
/******************************************************************************
* FunctionName : json_send
* Description : processing the data as json format and send to the client or server
* Parameters : arg -- argument to set for client or server
* ParmType -- json format type
* Returns : none
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
json_send(void *arg, ParmType ParmType)
{
char *pbuf = NULL;
pbuf = (char *)os_zalloc(jsonSize);
struct espconn *ptrespconn = arg;
switch (ParmType) {
#if PLUG_DEVICE
case SWITCH_STATUS:
json_ws_send((struct jsontree_value *)&StatusTree, "switch", pbuf);
os_printf("switch:%s", pbuf);
break;
#endif
case INFOMATION:
json_ws_send((struct jsontree_value *)&INFOTree, "info", pbuf);
break;
case WIFI:
json_ws_send((struct jsontree_value *)&wifi_info_tree, "wifi", pbuf);
break;
case CONNECT_STATUS:
json_ws_send((struct jsontree_value *)&con_status_tree, "info", pbuf);
break;
case USER_BIN:
json_ws_send((struct jsontree_value *)&userinfo_tree, "user_info", pbuf);
break;
case SCAN: {
u8 i = 0;
u8 scancount = 0;
struct bss_info *bss = NULL;
bss = bss_head;
if (bss == NULL) {
os_free(pscaninfo);
pscaninfo = NULL;
os_sprintf(pbuf, "{\n\"successful\": false,\n\"data\": null\n}");
} else {
do {
if (pscaninfo->page_sn == pscaninfo->pagenum) {
pscaninfo->page_sn = 0;
os_sprintf(pbuf, "{\n\"successful\": false,\n\"meessage\": \"repeated page\"\n}");
break;
}
scancount = scannum - (pscaninfo->pagenum - 1) * 8;
if (scancount >= 8) {
pscaninfo->data_cnt += 8;
pscaninfo->page_sn = pscaninfo->pagenum;
if (pscaninfo->data_cnt > scannum) {
pscaninfo->data_cnt -= 8;
os_sprintf(pbuf, "{\n\"successful\": false,\n\"meessage\": \"error page\"\n}");
break;
}
json_ws_send((struct jsontree_value *)&scan_tree, "scan", pbuf);
} else {
pscaninfo->data_cnt += scancount;
pscaninfo->page_sn = pscaninfo->pagenum;
if (pscaninfo->data_cnt > scannum) {
pscaninfo->data_cnt -= scancount;
os_sprintf(pbuf, "{\n\"successful\": false,\n\"meessage\": \"error page\"\n}");
break;
}
char *ptrscanbuf = (char *)os_zalloc(jsonSize);
char *pscanbuf = ptrscanbuf;
os_sprintf(pscanbuf, ",\n\"ScanResult\": [\n");
pscanbuf += os_strlen(pscanbuf);
for (i = 0; i < scancount; i ++) {
JSONTREE_OBJECT(page_tree,
JSONTREE_PAIR("page", &scaninfo_tree));
json_ws_send((struct jsontree_value *)&page_tree, "page", pscanbuf);
os_sprintf(pscanbuf + os_strlen(pscanbuf), ",\n");
pscanbuf += os_strlen(pscanbuf);
}
os_sprintf(pscanbuf - 2, "]\n");
JSONTREE_OBJECT(scantree,
JSONTREE_PAIR("TotalPage", &scan_callback),
JSONTREE_PAIR("PageNum", &scan_callback));
JSONTREE_OBJECT(scanres_tree,
JSONTREE_PAIR("Response", &scantree));
JSONTREE_OBJECT(scan_tree,
JSONTREE_PAIR("scan", &scanres_tree));
json_ws_send((struct jsontree_value *)&scan_tree, "scan", pbuf);
os_memcpy(pbuf + os_strlen(pbuf) - 4, ptrscanbuf, os_strlen(ptrscanbuf));
os_sprintf(pbuf + os_strlen(pbuf), "}\n}");
os_free(ptrscanbuf);
}
} while (0);
}
break;
}
default :
break;
}
data_send(ptrespconn, true, pbuf);
os_free(pbuf);
pbuf = NULL;
}
6.3 data_send()
/******************************************************************************
* FunctionName : data_send
* Description : processing the data as http format and send to the client or server
* Parameters : arg -- argument to set for client or server
* responseOK -- true or false
* psend -- The send data
* Returns :
*******************************************************************************/
LOCAL void ICACHE_FLASH_ATTR
data_send(void *arg, bool responseOK, char *psend)
{
uint16 length = 0;
char *pbuf = NULL;
char httphead[256];
struct espconn *ptrespconn = arg;
os_memset(httphead, 0, 256);
if (responseOK) {
os_sprintf(httphead,
"HTTP/1.0 200 OK\r\nContent-Length: %d\r\nServer: lwIP/1.4.0\r\n",
psend ? os_strlen(psend) : 0);
if (psend) {
os_sprintf(httphead + os_strlen(httphead),
"Content-type: application/json\r\nExpires: Fri, 10 Apr 2008 14:00:00 GMT\r\nPragma: no-cache\r\n\r\n");
length = os_strlen(httphead) + os_strlen(psend);
pbuf = (char *)os_zalloc(length + 1);
os_memcpy(pbuf, httphead, os_strlen(httphead));
os_memcpy(pbuf + os_strlen(httphead), psend, os_strlen(psend));
} else {
os_sprintf(httphead + os_strlen(httphead), "\n");
length = os_strlen(httphead);
}
} else {
os_sprintf(httphead, "HTTP/1.0 400 BadRequest\r\n\
Content-Length: 0\r\nServer: lwIP/1.4.0\r\n\n");
length = os_strlen(httphead);
}
if (psend) {
espconn_sent(ptrespconn, pbuf, length);
}
else {
espconn_sent(ptrespconn, httphead, length);
}
if (pbuf) {
os_free(pbuf);
pbuf = NULL;
}
}
七、printf打印内容
len:491
A_dat:25,tot:25,lenght:25
pusrdata:POST /config?command=switch HTTP/1.1
Accept: application/json,application/xml,application/xhtml+xml,text/html;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh
Connection: keep-alive
Content-Type: application/json; charset=utf-8
User-Agent: Mozilla/5.0 (Linux; U; Android 5.0.2; zh-cn; Redmi Note 2 Build/LRX22G) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1
Content-Length: 25
Host: 192.168.0.52
{"Response":{"status":0}}
Type:1, Select:config, Command:command, Filename:switch We have a POST request.
• 由 Leung 写于 2019 年 2 月 27 日
• 参考:ESP8266_NONOS_SDK-2.1.0[vqhl]
ESP8266 Non-OS SDK API参考[zj6w]