ESP8266学习笔记(10)——官方WebServer
发布日期:2021-05-06 23:36:49 浏览次数:8 分类:技术文章

本文共 26326 字,大约阅读时间需要 87 分钟。

一、背景

WebServer作为HTTP服务器,接收手机APP请求,配置网关接入网络。

Post请求和Get请求:

二、流程

① 初始化WebServer,绑定本地端口

② 开启监听
③ 解析请求数据包
④ 回发响应数据包

三、初始化WebServer

文件所在 ESP8266_NONOS_SDK-2.1.0\IoT_Demo\user/user_webserver.c

创建TCP服务端,参考。
传入参数,本地端口80。

void ICACHE_FLASH_ATTRuser_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_ATTRwebserver_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数据参考

/****************************************************************************** * 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_ATTRwebserver_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_ATTRcheck_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_ATTRsave_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_ATTRparse_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_ATTRresponse_send(void *arg, bool responseOK){       struct espconn *ptrespconn = arg;    data_send(ptrespconn, responseOK, NULL);}

6.2 json_send()

里面生成JSON数据json_ws_send()参考

/****************************************************************************** * 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_ATTRjson_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_ATTRdata_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:491A_dat:25,tot:25,lenght:25pusrdata:POST /config?command=switch HTTP/1.1Accept: application/json,application/xml,application/xhtml+xml,text/html;q=0.9,image/webp,*/*;q=0.8Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zhConnection: keep-aliveContent-Type: application/json; charset=utf-8User-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.1Content-Length: 25Host: 192.168.0.52{"Response":{"status":0}}Type:1, Select:config, Command:command, Filename:switch We have a POST request.

• 由 写于 2019 年 2 月 27 日

• 参考:[vqhl]

    [7qq6]

上一篇:CC2640R2F学习笔记(5)——自定义周期事件
下一篇:CC2640R2F学习笔记(4)——Multi_role一主多从连接

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2025年03月10日 06时13分03秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章