授人以魚不如授人以漁,目的不是爲了教會你具體項目開發,而是學會學習的能力。但願你們分享給你周邊須要的朋友或者同窗,說不定大神成長之路有博哥的奠定石。。。html
QQ技術互動交流羣:ESP8266&32 物聯網開發 羣號622368884,不喜勿噴web
1、基礎篇json
2、網絡篇設計模式
- ESP8266開發之旅 網絡篇① 認識一下Arduino Core For ESP8266
- ESP8266開發之旅 網絡篇② ESP8266 工做模式與ESP8266WiFi庫
- ESP8266開發之旅 網絡篇③ Soft-AP——ESP8266WiFiAP庫的使用
- ESP8266開發之旅 網絡篇④ Station——ESP8266WiFiSTA庫的使用
- ESP8266開發之旅 網絡篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
- ESP8266開發之旅 網絡篇⑥ ESP8266WiFiGeneric——基礎庫
- ESP8266開發之旅 網絡篇⑦ TCP Server & TCP Client
- ESP8266開發之旅 網絡篇⑧ SmartConfig——一鍵配網
- ESP8266開發之旅 網絡篇⑨ HttpClient——ESP8266HTTPClient庫的使用
- ESP8266開發之旅 網絡篇⑩ UDP服務
- ESP8266開發之旅 網絡篇⑪ WebServer——ESP8266WebServer庫的使用
- ESP8266開發之旅 網絡篇⑫ 域名服務——ESP8266mDNS庫
- ESP8266開發之旅 網絡篇⑬ SPIFFS——ESP8266 Flash文件系統
- ESP8266開發之旅 網絡篇⑭ web配網
- ESP8266開發之旅 網絡篇⑮ 真正的域名服務——DNSServer
- ESP8266開發之旅 網絡篇⑯ 無線更新——OTA固件更新
3、應用篇瀏覽器
4、高級篇cookie
在前面章節的博客中,博主介紹了ESP8266WiFi庫 Tcp server的用法,並模擬了Http webserver的功能。可是,能夠看出經過Tcp server 處理http請求,咱們須要本身解析請求協議以及判斷各類數據,稍微不當心就很容易出現錯誤。
那麼有沒有針對Http webserver操做的庫呢?答案確定是有的,這就是博主本篇須要跟你們講述的知識——ESP8266WebServer庫。
請注意,ESP8266WebServer庫不屬於ESP8266WiFi庫的一部分,因此須要引入網絡
#include <ESP8266WebServer.h>
博主說過,Http是基於Tcp協議之上的,因此你在ESP8266WebServer源碼中會看到WiFiServer和WiFiClient的蹤影。app
............ 前面省略代碼 struct RequestArgument { String key; String value; }; WiFiServer _server; WiFiClient _currentClient; .............後面省略代碼
若是讀者有下載源碼的話,那麼能夠看到ESP8266WebServer庫 目錄以下:
tcp
老規矩,先上一個博主總結的百度腦圖:
ide
整體上,根據功能能夠把方法分爲兩大類:
注意點:
http請求方法又能夠有更好的細分。
函數說明:
/** * 建立webserver * @param addr IPAddress (IP地址) * @param port int (端口號,默認是80) */ ESP8266WebServer(IPAddress addr, int port = 80); /** * 建立webserver(使用默認的IP地址) * @param port int (端口號,默認是80) */ ESP8266WebServer(int port = 80);
函數說明:
/** * 啓動webserver */ void begin(); /** * 啓動webserver * @param port uint16_t 端口號 */ void begin(uint16_t port);
注意點:
函數說明:
/** * 關閉webserver,關閉TCP鏈接 */ void close();
函數說明:
/** * 關閉webserver * 底層就是調用close(); */ void stop();
函數1說明:
/** * 配置uri對應的handler,handler也就是處理方法 * @param uri const String (uri路徑) * @param handler THandlerFunction (對應uri處理函數) */ void on(const String &uri, THandlerFunction handler);
注意點:
函數2說明:
/** * 配置uri對應的handler,handler也就是處理方法 * @param uri const String (uri路徑) * @param method HTTPMethod(Http請求方法) * 可選參數:HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS * @param fn THandlerFunction (對應uri處理函數) */ void on(const String &uri, HTTPMethod method, THandlerFunction fn);
函數3說明:
/** * 配置uri對應的handler,handler也就是處理方法 * @param uri const String (uri路徑) * @param method HTTPMethod(Http請求方法) * 可選參數:HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, * HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS * @param fn THandlerFunction (對應uri處理函數) * @param ufn THandlerFunction (文件上傳處理函數) */ void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
注意點:
typedef std::function<void(void)> THandlerFunction; 也就是說咱們的請求處理函數定義應該是: void methodName(void); 通常咱們習慣寫成: void handleXXXX(){ //如下寫上處理代碼 }
在這裏,博主先給你們看看On方法的源碼:
/** * 綁定uri對應的請求回調方法 * @param uri const String (uri路徑) * @param method HTTPMethod(Http請求方法) * 可選參數:HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, * HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS * @param fn THandlerFunction (對應uri處理函數) * @param ufn THandlerFunction (文件上傳處理函數 */ void ESP8266WebServer::on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) { _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method)); } /** * 組成請求處理鏈表 * @param RequestHandler* 請求處理者 */ void ESP8266WebServer::_addRequestHandler(RequestHandler* handler) { if (!_lastHandler) { _firstHandler = handler; _lastHandler = handler; } else { _lastHandler->next(handler); _lastHandler = handler; } }
注意點:
函數說明:
/** * 添加一個自定義的RequestHandler(請求處理) * @param handler RequestHandler (自主實現的RequestHandler) */ void addHandler(RequestHandler* handler);
看看源碼:
/** * 添加一個自定義的RequestHandler(請求處理) * @param handler RequestHandler (自主實現的RequestHandler) */ void ESP8266WebServer::addHandler(RequestHandler* handler) { _addRequestHandler(handler); }
到這裏,咱們須要來了解一下 RequestHandler 是什麼神奇的類,咱們須要怎麼樣作處理呢?
先來看看 RequestHandler 的類結構:
/** * RequestHandler 的類結構 */ class RequestHandler { public: virtual ~RequestHandler() { } //判斷請求處理者是否能夠處理該uri,而且匹配method virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; } //判斷請求處理者是否能夠處理文件上傳,通常用於http文件上傳 virtual bool canUpload(String uri) { (void) uri; return false; } //調用處理方法 - 普通請求 virtual bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; } //調用處理方法 - 文件上傳 virtual void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; } //獲取當前處理者的下一個處理者 RequestHandler* next() { return _next; } //設置當前處理者的下一個處理者,這裏就是責任鏈的關鍵 void next(RequestHandler* r) { _next = r; } private: RequestHandler* _next = nullptr; };
能夠看出,RequestHandler 主要包裝了WebServer能夠處理的http請求。當有請求來的時候,就會調用對應的 RequestHandler;
接下來咱們來看一個用得最多的一個 RequestHandler 子類——FunctionRequestHandler類(博主上面說到了這個注意點):
class FunctionRequestHandler : public RequestHandler { public: FunctionRequestHandler(ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method) : _fn(fn) , _ufn(ufn) , _uri(uri) , _method(method) { } bool canHandle(HTTPMethod requestMethod, String requestUri) override { //如下判斷這個handler是否能夠處理這個 requestUri //判斷requestMethod是否匹配 if (_method != HTTP_ANY && _method != requestMethod) return false; //判斷requestUri是否匹配 if (requestUri != _uri) return false; return true; } bool canUpload(String requestUri) override { //如下判斷這個handler是否能夠處理這個 文件上傳請求 //判斷文件上傳函數是否實現 或者 開發者定義了文件上傳函數,可是method不是 HTTP_POST 或者 requestUri沒對上 if (!_ufn || !canHandle(HTTP_POST, requestUri)) return false; return true; } bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override { (void) server; if (!canHandle(requestMethod, requestUri)) return false; //調用請求處理函數 _fn(); return true; } void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) override { (void) server; (void) upload; if (canUpload(requestUri)) //調用處理文件上傳函數 _ufn(); } protected: //通用請求處理函數 ESP8266WebServer::THandlerFunction _fn; //文件上傳請求處理函數 ESP8266WebServer::THandlerFunction _ufn; //匹配的uri String _uri; //匹配的HTTPMethod HTTPMethod _method; };
經過上面的代碼分析,博主相信你們應該對請求處理類有一個初步的認識,用起來駕輕就熟。
函數說明:
/** * 配置無效uri的handler * @param fn THandlerFunction (對應uri處理函數) */ void onNotFound(THandlerFunction fn); //called when handler is not assigned
注意點:
...... if (!handled) { using namespace mime; //發送默認的404錯誤 send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); handled = true; } ......
函數說明:
/** * 配置處理文件上傳的handler * @param fn THandlerFunction (對應uri處理函數) */ void onFileUpload(THandlerFunction fn); //handle file uploads
函數說明:
/** * 獲取請求的uri */ String uri();
函數說明:
/** * 獲取請求的uri */ HTTPMethod method()
其中,HTTPMethod取值範圍爲:
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
函數說明:
/** * 根據請求key獲取請求參數的值 * @param name String(請求key) */ String arg(String name);// get request argument value by name
函數說明:
/** * 獲取第幾個請求參數的值 * @param i int(請求index) */ String arg(int i);// get request argument value by number
函數說明:
/** * 獲取第幾個請求參數的名字 * @param i int(請求index) */ String argName(int i);// get request argument name by number
函數說明:
/** * 獲取參數個數 */ int args(); // get arguments count
函數說明:
/** * 是否存在某個參數 */ bool hasArg(String name);// check if argument exists
函數說明:
/** * 設置須要收集的請求頭(1-n個) * @param headerKeys[] const char * 請求頭的名字 * @param headerKeysCount const size_t 請求頭的個數 */ void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
函數說明:
/** * 設置須要收集的請求頭(1-n個) * @param headerKeys[] const char * 請求頭的名字 * @param headerKeysCount const size_t 請求頭的個數 */ void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
函數說明:
/** * 獲取請求頭參數值 * @param name const char * 請求頭的名字 * @return value of headerkey(name) */ String header(String name);// get request header value by name
函數說明:
/** * 獲取第i個請求頭參數值 * @param i size_t 請求頭索引值 * @return value of header index */ String header(int i);// get request header value by number
函數說明:
/** * 獲取第i個請求頭名字 * @param i size_t 請求頭索引值 * @return name of header index */ String headerName(int i);// get request header name by number
函數說明:
/** * 獲取收集請求頭個數 * @return count int */ int headers();// get header count
函數說明:
/** * 判斷是否存在某一個請求頭 * @param name const char* 請求頭名字 * @return bool */ bool hasHeader(String name); // check if header exists
函數說明:
/** * 獲取請求頭Host的值 */ String hostHeader();// get request host header if available or empty String if not
函數說明:
/** * 認證校驗(Authorization) * @param fn THandlerFunction (對應uri處理函數) * @param username const char * 用戶帳號 * @param password const char * 用戶密碼 */ bool authenticate(const char * username, const char * password);
看看authenticate底層源碼:
bool ESP8266WebServer::authenticate(const char * username, const char * password){ //判斷是否存在 Authorization 請求頭 if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { String authReq = header(FPSTR(AUTHORIZATION_HEADER)); //判斷 Authorization的值是否是base64編碼 if(authReq.startsWith(F("Basic"))){ authReq = authReq.substring(6); authReq.trim(); char toencodeLen = strlen(username)+strlen(password)+1; char *toencode = new char[toencodeLen + 1]; if(toencode == NULL){ authReq = ""; return false; } char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; if(encoded == NULL){ authReq = ""; delete[] toencode; return false; } sprintf(toencode, "%s:%s", username, password); //判斷經過username:password生成的base64編碼是否和請求頭的Authorization的值同樣,同樣表示經過驗證 if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { authReq = ""; delete[] toencode; delete[] encoded; return true; } delete[] toencode; delete[] encoded; } else if(authReq.startsWith(F("Digest"))) { // HTTP Authorization 之 Digest Auth 用到MD5加密 authReq = authReq.substring(7); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println(authReq); #endif String _username = _extractParam(authReq,F("username=\"")); if(!_username.length() || _username != String(username)) { authReq = ""; return false; } // extracting required parameters for RFC 2069 simpler Digest String _realm = _extractParam(authReq, F("realm=\"")); String _nonce = _extractParam(authReq, F("nonce=\"")); String _uri = _extractParam(authReq, F("uri=\"")); String _response = _extractParam(authReq, F("response=\"")); String _opaque = _extractParam(authReq, F("opaque=\"")); if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { authReq = ""; return false; } if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { authReq = ""; return false; } // parameters for the RFC 2617 newer Digest String _nc,_cnonce; if(authReq.indexOf(FPSTR(qop_auth)) != -1) { _nc = _extractParam(authReq, F("nc="), ','); _cnonce = _extractParam(authReq, F("cnonce=\"")); } MD5Builder md5; md5.begin(); md5.add(String(username) + ':' + _realm + ':' + String(password)); // md5 of the user:realm:user md5.calculate(); String _H1 = md5.toString(); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("Hash of user:realm:pass=" + _H1); #endif md5.begin(); if(_currentMethod == HTTP_GET){ md5.add(String(F("GET:")) + _uri); }else if(_currentMethod == HTTP_POST){ md5.add(String(F("POST:")) + _uri); }else if(_currentMethod == HTTP_PUT){ md5.add(String(F("PUT:")) + _uri); }else if(_currentMethod == HTTP_DELETE){ md5.add(String(F("DELETE:")) + _uri); }else{ md5.add(String(F("GET:")) + _uri); } md5.calculate(); String _H2 = md5.toString(); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("Hash of GET:uri=" + _H2); #endif md5.begin(); if(authReq.indexOf(FPSTR(qop_auth)) != -1) { md5.add(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); } else { md5.add(_H1 + ':' + _nonce + ':' + _H2); } md5.calculate(); String _responsecheck = md5.toString(); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("The Proper response=" +_responsecheck); #endif if(_response == _responsecheck){ authReq = ""; return true; } } authReq = ""; } return false; }
注意點:
這是一個很是重要的方法,因此請認真閱讀博主的講解。
函數說明:
/** * 等待請求進來並處理 */ void handleClient();
接下來,博主將分析源碼,看看webserver是怎麼樣解析http請求而後調用具體的請求處理函數:
void ESP8266WebServer::handleClient() { //判斷當前狀態是否是空閒狀態 if (_currentStatus == HC_NONE) { //有http請求進來 WiFiClient client = _server.available(); if (!client) { return; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("New client"); #endif //設置當前的http client請求 _currentClient = client; //更改當前狀態爲等待讀取數據狀態 _currentStatus = HC_WAIT_READ; _statusChange = millis(); } bool keepCurrentClient = false; bool callYield = false; if (_currentClient.connected()) { switch (_currentStatus) { case HC_NONE: // No-op to avoid C++ compiler warning break; case HC_WAIT_READ: // Wait for data from client to become available //判斷是否有請求數據 if (_currentClient.available()) { //開始解析http請求 if (_parseRequest(_currentClient)) { _currentClient.setTimeout(HTTP_MAX_SEND_WAIT); _contentLength = CONTENT_LENGTH_NOT_SET; //處理請求 _handleRequest(); if (_currentClient.connected()) { _currentStatus = HC_WAIT_CLOSE; _statusChange = millis(); keepCurrentClient = true; } } } else { // !_currentClient.available() //等待請求數據到來,會設置一個超時時間 if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) { keepCurrentClient = true; } callYield = true; } break; case HC_WAIT_CLOSE: // Wait for client to close the connection if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) { keepCurrentClient = true; callYield = true; } } } if (!keepCurrentClient) { //斷開tcp鏈接 _currentClient = WiFiClient(); _currentStatus = HC_NONE; _currentUpload.reset(); } if (callYield) { yield(); } }
注意點:
bool ESP8266WebServer::_parseRequest(WiFiClient& client) { // 讀取http請求的第一行 String req = client.readStringUntil('\r'); client.readStringUntil('\n'); //重置請求頭 for (int i = 0; i < _headerKeysCount; ++i) { _currentHeaders[i].value =String(); } // First line of HTTP request looks like "GET /path HTTP/1.1" // Retrieve the "/path" part by finding the spaces int addr_start = req.indexOf(' '); int addr_end = req.indexOf(' ', addr_start + 1); if (addr_start == -1 || addr_end == -1) { #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Invalid request: "); DEBUG_OUTPUT.println(req); #endif return false; } //獲取Http requestMethod String methodStr = req.substring(0, addr_start); //獲取Http requestUri String url = req.substring(addr_start + 1, addr_end); String versionEnd = req.substring(addr_end + 8); //獲取Http 請求版本 _currentVersion = atoi(versionEnd.c_str()); String searchStr = ""; //判斷 requestUri裏面是否有queryParam,例如:/path?xxx=xxx int hasSearch = url.indexOf('?'); if (hasSearch != -1){ //把url和query param拆開來 searchStr = url.substring(hasSearch + 1); url = url.substring(0, hasSearch); } _currentUri = url; _chunked = false; //判斷Method HTTPMethod method = HTTP_GET; if (methodStr == F("POST")) { method = HTTP_POST; } else if (methodStr == F("DELETE")) { method = HTTP_DELETE; } else if (methodStr == F("OPTIONS")) { method = HTTP_OPTIONS; } else if (methodStr == F("PUT")) { method = HTTP_PUT; } else if (methodStr == F("PATCH")) { method = HTTP_PATCH; } _currentMethod = method; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("method: "); DEBUG_OUTPUT.print(methodStr); DEBUG_OUTPUT.print(" url: "); DEBUG_OUTPUT.print(url); DEBUG_OUTPUT.print(" search: "); DEBUG_OUTPUT.println(searchStr); #endif //attach handler RequestHandler* handler; //查找能夠處理該請求的requestHandler for (handler = _firstHandler; handler; handler = handler->next()) { if (handler->canHandle(_currentMethod, _currentUri)) break; } _currentHandler = handler; String formData; // POST請求 if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){ String boundaryStr; String headerName; String headerValue; bool isForm = false; bool isEncoded = false; uint32_t contentLength = 0; //解析請求頭 while(1){ req = client.readStringUntil('\r'); client.readStringUntil('\n'); if (req == "") break;//no moar headers int headerDiv = req.indexOf(':'); if (headerDiv == -1){ break; } headerName = req.substring(0, headerDiv); headerValue = req.substring(headerDiv + 1); headerValue.trim(); //收集請求頭信息 _collectHeader(headerName.c_str(),headerValue.c_str()); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("headerName: "); DEBUG_OUTPUT.println(headerName); DEBUG_OUTPUT.print("headerValue: "); DEBUG_OUTPUT.println(headerValue); #endif //判斷 Content_Type if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){ using namespace mime; // Content_Type = "text/plain" if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ isForm = false; } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ isForm = false; //有加了編碼 isEncoded = true; } else if (headerValue.startsWith(F("multipart/"))){ //獲取 boundary,用於分割不一樣的字段 boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); boundaryStr.replace("\"",""); isForm = true; } } else if (headerName.equalsIgnoreCase(F("Content-Length"))){ //判斷 Content-Length 數值 contentLength = headerValue.toInt(); } else if (headerName.equalsIgnoreCase(F("Host"))){ _hostHeader = headerValue; } } //不是 multipart/form-data if (!isForm){ size_t plainLength; //讀取請求內容 最大超時 HTTP_MAX_POST_WAIT char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT); if (plainLength < contentLength) { free(plainBuf); return false; } if (contentLength > 0) { // 屬於 application/x-www-form-urlencoded if(isEncoded){ //url encoded form 處理表單數據 if (searchStr != "") searchStr += '&'; searchStr += plainBuf; } //開始解析 queryParam _parseArguments(searchStr); if(!isEncoded){ //plain post json or other data RequestArgument& arg = _currentArgs[_currentArgCount++]; arg.key = F("plain"); arg.value = String(plainBuf); } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Plain: "); DEBUG_OUTPUT.println(plainBuf); #endif free(plainBuf); } else { // No content - but we can still have arguments in the URL. _parseArguments(searchStr); } } // multipart/form-data if (isForm){ //解析query param _parseArguments(searchStr); //讀取表單請求數據 if (!_parseForm(client, boundaryStr, contentLength)) { return false; } } } else { //如下是GET請求解析 String headerName; String headerValue; //parse headers while(1){ req = client.readStringUntil('\r'); client.readStringUntil('\n'); if (req == "") break;//no moar headers int headerDiv = req.indexOf(':'); if (headerDiv == -1){ break; } headerName = req.substring(0, headerDiv); headerValue = req.substring(headerDiv + 2); _collectHeader(headerName.c_str(),headerValue.c_str()); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("headerName: "); DEBUG_OUTPUT.println(headerName); DEBUG_OUTPUT.print("headerValue: "); DEBUG_OUTPUT.println(headerValue); #endif if (headerName.equalsIgnoreCase("Host")){ _hostHeader = headerValue; } } _parseArguments(searchStr); } client.flush(); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Request: "); DEBUG_OUTPUT.println(url); DEBUG_OUTPUT.print(" Arguments: "); DEBUG_OUTPUT.println(searchStr); #endif return true; } /** * 解析參數 */ void ESP8266WebServer::_parseArguments(String data) { #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("args: "); DEBUG_OUTPUT.println(data); #endif if (_currentArgs) delete[] _currentArgs; _currentArgs = 0; if (data.length() == 0) { _currentArgCount = 0; _currentArgs = new RequestArgument[1]; return; } _currentArgCount = 1; for (int i = 0; i < (int)data.length(); ) { i = data.indexOf('&', i); if (i == -1) break; ++i; ++_currentArgCount; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("args count: "); DEBUG_OUTPUT.println(_currentArgCount); #endif _currentArgs = new RequestArgument[_currentArgCount+1]; int pos = 0; int iarg; for (iarg = 0; iarg < _currentArgCount;) { int equal_sign_index = data.indexOf('=', pos); int next_arg_index = data.indexOf('&', pos); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("pos "); DEBUG_OUTPUT.print(pos); DEBUG_OUTPUT.print("=@ "); DEBUG_OUTPUT.print(equal_sign_index); DEBUG_OUTPUT.print(" &@ "); DEBUG_OUTPUT.println(next_arg_index); #endif if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("arg missing value: "); DEBUG_OUTPUT.println(iarg); #endif if (next_arg_index == -1) break; pos = next_arg_index + 1; continue; } RequestArgument& arg = _currentArgs[iarg]; arg.key = urlDecode(data.substring(pos, equal_sign_index)); arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index)); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("arg "); DEBUG_OUTPUT.print(iarg); DEBUG_OUTPUT.print(" key: "); DEBUG_OUTPUT.print(arg.key); DEBUG_OUTPUT.print(" value: "); DEBUG_OUTPUT.println(arg.value); #endif ++iarg; if (next_arg_index == -1) break; pos = next_arg_index + 1; } _currentArgCount = iarg; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("args count: "); DEBUG_OUTPUT.println(_currentArgCount); #endif } /** * 解析 multipart/form-data */ bool ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){ (void) len; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Parse Form: Boundary: "); DEBUG_OUTPUT.print(boundary); DEBUG_OUTPUT.print(" Length: "); DEBUG_OUTPUT.println(len); #endif String line; int retry = 0; do { line = client.readStringUntil('\r'); ++retry; } while (line.length() == 0 && retry < 3); client.readStringUntil('\n'); //開始讀取表單 if (line == ("--"+boundary)){ RequestArgument* postArgs = new RequestArgument[32]; int postArgsLen = 0; while(1){ String argName; String argValue; String argType; String argFilename; bool argIsFile = false; line = client.readStringUntil('\r'); client.readStringUntil('\n'); if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ int nameStart = line.indexOf('='); if (nameStart != -1){ argName = line.substring(nameStart+2); nameStart = argName.indexOf('='); if (nameStart == -1){ //文本內容 argName = argName.substring(0, argName.length() - 1); } else { //文件內容 argFilename = argName.substring(nameStart+2, argName.length() - 1); argName = argName.substring(0, argName.indexOf('"')); argIsFile = true; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg FileName: "); DEBUG_OUTPUT.println(argFilename); #endif //use GET to set the filename if uploading using blob if (argFilename == F("blob") && hasArg(FPSTR(filename))) argFilename = arg(FPSTR(filename)); } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Name: "); DEBUG_OUTPUT.println(argName); #endif using namespace mime; argType = FPSTR(mimeTable[txt].mimeType); line = client.readStringUntil('\r'); client.readStringUntil('\n'); if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){ argType = line.substring(line.indexOf(':')+2); //skip next line client.readStringUntil('\r'); client.readStringUntil('\n'); } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Type: "); DEBUG_OUTPUT.println(argType); #endif if (!argIsFile){ //文本內容處理方式 while(1){ line = client.readStringUntil('\r'); client.readStringUntil('\n'); if (line.startsWith("--"+boundary)) break; if (argValue.length() > 0) argValue += "\n"; argValue += line; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("PostArg Value: "); DEBUG_OUTPUT.println(argValue); DEBUG_OUTPUT.println(); #endif RequestArgument& arg = postArgs[postArgsLen++]; arg.key = argName; arg.value = argValue; if (line == ("--"+boundary+"--")){ //判斷讀取結束 #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("Done Parsing POST"); #endif break; } } else { //文件內容處理方式,開始處理文件上傳 _currentUpload.reset(new HTTPUpload()); _currentUpload->status = UPLOAD_FILE_START; _currentUpload->name = argName; _currentUpload->filename = argFilename; _currentUpload->type = argType; _currentUpload->totalSize = 0; _currentUpload->currentSize = 0; #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Start File: "); DEBUG_OUTPUT.print(_currentUpload->filename); DEBUG_OUTPUT.print(" Type: "); DEBUG_OUTPUT.println(_currentUpload->type); #endif //處理文件上傳 if(_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, *_currentUpload); //表示文件正在執行寫操做,咱們能夠在handler裏面存文件內容下來,用到FS _currentUpload->status = UPLOAD_FILE_WRITE; uint8_t argByte = _uploadReadByte(client); readfile: while(argByte != 0x0D){ if (!client.connected()) return _parseFormUploadAborted(); _uploadWriteByte(argByte); argByte = _uploadReadByte(client); } argByte = _uploadReadByte(client); if (!client.connected()) return _parseFormUploadAborted(); if (argByte == 0x0A){ argByte = _uploadReadByte(client); if (!client.connected()) return _parseFormUploadAborted(); if ((char)argByte != '-'){ //continue reading the file _uploadWriteByte(0x0D); _uploadWriteByte(0x0A); goto readfile; } else { argByte = _uploadReadByte(client); if (!client.connected()) return _parseFormUploadAborted(); if ((char)argByte != '-'){ //continue reading the file _uploadWriteByte(0x0D); _uploadWriteByte(0x0A); _uploadWriteByte((uint8_t)('-')); goto readfile; } } uint8_t endBuf[boundary.length()]; client.readBytes(endBuf, boundary.length()); if (strstr((const char*)endBuf, boundary.c_str()) != NULL){ if(_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, *_currentUpload); _currentUpload->totalSize += _currentUpload->currentSize; _currentUpload->status = UPLOAD_FILE_END; //上傳文件結束 if(_currentHandler && _currentHandler->canUpload(_currentUri)) _currentHandler->upload(*this, _currentUri, *_currentUpload); #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("End File: "); DEBUG_OUTPUT.print(_currentUpload->filename); DEBUG_OUTPUT.print(" Type: "); DEBUG_OUTPUT.print(_currentUpload->type); DEBUG_OUTPUT.print(" Size: "); DEBUG_OUTPUT.println(_currentUpload->totalSize); #endif line = client.readStringUntil(0x0D); client.readStringUntil(0x0A); if (line == "--"){ #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("Done Parsing POST"); #endif break; } continue; } else { _uploadWriteByte(0x0D); _uploadWriteByte(0x0A); _uploadWriteByte((uint8_t)('-')); _uploadWriteByte((uint8_t)('-')); uint32_t i = 0; while(i < boundary.length()){ _uploadWriteByte(endBuf[i++]); } argByte = _uploadReadByte(client); goto readfile; } } else { _uploadWriteByte(0x0D); goto readfile; } break; } } } } int iarg; int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount; for (iarg = 0; iarg < totalArgs; iarg++){ RequestArgument& arg = postArgs[postArgsLen++]; arg.key = _currentArgs[iarg].key; arg.value = _currentArgs[iarg].value; } if (_currentArgs) delete[] _currentArgs; _currentArgs = new RequestArgument[postArgsLen]; for (iarg = 0; iarg < postArgsLen; iarg++){ RequestArgument& arg = _currentArgs[iarg]; arg.key = postArgs[iarg].key; arg.value = postArgs[iarg].value; } _currentArgCount = iarg; if (postArgs) delete[] postArgs; return true; } #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.print("Error: line: "); DEBUG_OUTPUT.println(line); #endif return false; }
content-type取值能夠參考 四種常見的 POST 提交數據方式對應的content-type取值
void ESP8266WebServer::_handleRequest() { bool handled = false; if (!_currentHandler){ #ifdef DEBUG_ESP_HTTP_SERVER DEBUG_OUTPUT.println("request handler not found"); #endif } else { //調用對應的請求處理函數 handled = _currentHandler->handle(*this, _currentMethod, _currentUri); #ifdef DEBUG_ESP_HTTP_SERVER if (!handled) { DEBUG_OUTPUT.println("request handler failed to handle request"); } #endif } if (!handled && _notFoundHandler) { //沒有任何匹配的請求處理handler,默認提示404 _notFoundHandler(); handled = true; } if (!handled) { using namespace mime; send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); handled = true; } if (handled) { _finalizeResponse(); } _currentUri = ""; }
注意點:
在這裏,博主從新梳理了WebServer處理Http請求的邏輯:
當咱們通過handleClient()解析完http請求以後,咱們就能夠在requestHandler設置的請求處理回調函數裏面得到http請求的具體信息,而後根據具體信息給到對應的響應信息。那麼,咱們能夠在回調函數裏面作什麼呢?
函數說明:
/** * 獲取文件上傳處理對象 */ HTTPUpload& upload();
咱們來看看HTTPUpload的定義:
typedef struct { HTTPUploadStatus status;//上傳文件的狀態 String filename;//文件名字 String name; String type;//文件類型 size_t totalSize; // 文件大小 size_t currentSize; // size of data currently in buf uint8_t buf[HTTP_UPLOAD_BUFLEN];//緩衝區,這裏就是咱們須要處理的重點 } HTTPUpload;
實例使用(在文件上傳處理函數裏面):
//實例說明 非完整代碼,沒法直接運行,理解便可 /** * 處理文件上傳 HandlerFunction * 此方法會在文件上傳過程當中屢次回調,咱們能夠判斷上傳狀態 */ void handleFileUpload() { //判斷http requestUri if (server.uri() != "/edit") { return; } //得到 Http上傳文件處理對象 HTTPUpload& upload = server.upload(); //文件開始上傳 if (upload.status == UPLOAD_FILE_START) { String filename = upload.filename; if (!filename.startsWith("/")) { filename = "/" + filename; } DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); //本地文件系統建立一個文件用來保存內容 fsUploadFile = SPIFFS.open(filename, "w"); filename = String(); } else if (upload.status == UPLOAD_FILE_WRITE) { //文件開始寫入文件 //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); if (fsUploadFile) { //寫入文件 fsUploadFile.write(upload.buf, upload.currentSize); } } else if (upload.status == UPLOAD_FILE_END) { //文件上傳結束 if (fsUploadFile) { fsUploadFile.close(); } DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); } } //註冊文件上傳處理回調 server.on("/edit", HTTP_POST, []() { server.send(200, "text/plain", ""); }, handleFileUpload);
函數說明:
/** * 設置響應頭 * @param name 響應頭key * @param value 響應頭value * @param first 是否須要放在第一行 */ void sendHeader(const String& name, const String& value, bool first = false);
函數說明:
/** * 設置響應內容長度 * @param contentLength 長度 * 重大注意點:對於不知道長度調用server.setContentLength(CONTENT_LENGTH_UNKNOWN); * 而後調用若干個server.sendContent(),最後須要關閉與client的短鏈接(close)以表示內容結束。 */ void setContentLength(const size_t contentLength);
函數說明:
/** * 發送響應內容 * @param content 響應內容 */ void sendContent(const String& content); void sendContent_P(PGM_P content); void sendContent_P(PGM_P content, size_t size);
函數說明:
/** * 請求client認證(Authorization) * @param mode HTTPAuthMethod (驗證方式,默認BASIC_AUTH) * @param realm const char* * @param authFailMsg const String */ void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
函數說明:
/** * 發送響應文件流 * @param file 具體文件 * @param contentType 響應類型 * @param authFailMsg const String */ size_t streamFile(T &file, const String& contentType);
注意點:
這個是咱們發送響應數據的核心,須要仔細瞭解;
函數說明:
/** * 發送響應數據 * @param code 響應狀態碼 * @param content_type 響應內容類型 * @param content 具體響應內容 */ void send(int code, const char* content_type = NULL, const String& content = String("")); void send(int code, char* content_type, const String& content); void send(int code, const String& content_type, const String& content); void send_P(int code, PGM_P content_type, PGM_P content); void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
咱們這裏分析send的源碼:
void ESP8266WebServer::send(int code, const char* content_type, const String& content) { String header; //拼裝響應頭 _prepareHeader(header, code, content_type, content.length()); //發送響應頭 _currentClientWrite(header.c_str(), header.length()); if(content.length()) //發送響應內容 sendContent(content); } //發送響應頭 void ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { // http協議版本 response = String(F("HTTP/1.")) + String(_currentVersion) + ' '; //響應碼 response += String(code); response += ' '; response += _responseCodeToString(code); response += "\r\n"; using namespace mime; //響應類型 if (!content_type) content_type = mimeTable[html].mimeType; sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true); //響應內容長度 if (_contentLength == CONTENT_LENGTH_NOT_SET) { sendHeader(String(FPSTR(Content_Length)), String(contentLength)); } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { sendHeader(String(FPSTR(Content_Length)), String(_contentLength)); } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client //let's do chunked _chunked = true; sendHeader(String(F("Accept-Ranges")),String(F("none"))); sendHeader(String(F("Transfer-Encoding")),String(F("chunked"))); } sendHeader(String(F("Connection")), String(F("close"))); response += _responseHeaders; response += "\r\n"; _responseHeaders = ""; } /** * 發送響應內容 */ void ESP8266WebServer::sendContent(const String& content) { const char * footer = "\r\n"; size_t len = content.length(); if(_chunked) { char * chunkSize = (char *)malloc(11); if(chunkSize){ sprintf(chunkSize, "%x%s", len, footer); _currentClientWrite(chunkSize, strlen(chunkSize)); free(chunkSize); } } _currentClientWrite(content.c_str(), len); if(_chunked){ _currentClient.write(footer, 2); if (len == 0) { _chunked = false; } } }
講了那麼多的理論知識,該開始實際操做了,請看如下幾個例子。
實驗說明:
源碼:
/** * Demo: * 演示webserver基礎功能 * (當wifi模塊鏈接上ap以後,在pc瀏覽器中輸入ip+uri來訪問) * @author 單片機菜鳥 * @date 2019/09/10 */ #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* AP_SSID = "TP-LINK_5344"; // XXXXXX -- 使用時請修改成當前你的 wifi ssid const char* AP_PSK = "6206908you11011010"; // XXXXXX -- 使用時請修改成當前你的 wifi 密碼 const unsigned long BAUD_RATE = 115200; // serial connection speed //聲明一下函數 void initBasic(void); void initWifi(void); void initWebServer(void); ESP8266WebServer server(80);//建立一個webserver /** * 處理根目錄uri請求 * uri:http://server_ip/ */ void handleRoot() { server.send(200, "text/plain", "hello from esp8266!"); } /** * 處理無效uri * uri:http://server_ip/xxxx */ void handleNotFound() { //打印無效uri的信息 包括請求方式 請求參數 String message = "File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET) ? "GET" : "POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i = 0; i < server.args(); i++) { message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); } void setup(void) { initBasic(); initWifi(); initWebServer(); } /** * 初始化基礎功能:波特率 */ void initBasic(){ DebugBegin(BAUD_RATE); } /** * 初始化wifi模塊:工做模式 鏈接網絡 */ void initWifi(){ WiFi.mode(WIFI_STA); WiFi.begin(AP_SSID, AP_PSK); DebugPrintln(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); DebugPrint("."); } DebugPrintln(""); DebugPrint("Connected to "); DebugPrintln(AP_SSID); DebugPrint("IP address: "); DebugPrintln(WiFi.localIP()); } /** * 初始化webserver */ void initWebServer(){ //如下配置uri對應的handler server.on("/", handleRoot); server.on("/inline", []() { server.send(200, "text/plain", "this works as well"); }); server.onNotFound(handleNotFound); //啓動webserver server.begin(); DebugPrintln("HTTP server started"); } void loop(void) { server.handleClient(); }
實驗結果:
實驗說明:
源碼:
/** * Demo: * 演示webserver html功能 * (當wifi模塊鏈接上ap以後,在pc瀏覽器中輸入ip+uri來訪問) * @author 單片機菜鳥 * @date 2019/09/10 */ #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* AP_SSID = "TP-LINK_5344"; // XXXXXX -- 使用時請修改成當前你的 wifi ssid const char* AP_PSK = "6206908you11011010"; // XXXXXX -- 使用時請修改成當前你的 wifi 密碼 const unsigned long BAUD_RATE = 115200; // serial connection speed //聲明一下函數 void initBasic(void); void initWifi(void); void initWebServer(void); ESP8266WebServer server(80); /** * 處理根目錄uri請求 * uri:http://server_ip/ */ void handleRoot() { char temp[400]; int sec = millis() / 1000; int min = sec / 60; int hr = min / 60; snprintf(temp, 400, "<html>\ <head>\ <meta http-equiv='refresh' content='5'/>\ <title>ESP8266 Demo</title>\ <style>\ body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\ </style>\ </head>\ <body>\ <h1>Hello from ESP8266!</h1>\ <p>Uptime: %02d:%02d:%02d</p>\ <img src=\"/test.svg\" />\ </body>\ </html>", hr, min % 60, sec % 60 ); server.send(200, "text/html", temp); } /** * 處理無效uri * uri:http://server_ip/xxxx */ void handleNotFound() { //打印無效uri的信息 包括請求方式 請求參數 String message = "File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET) ? "GET" : "POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i = 0; i < server.args(); i++) { message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); } void setup(void) { initBasic(); initWifi(); initWebServer(); } void loop(void) { server.handleClient(); } /** * 初始化基礎功能:波特率 */ void initBasic(){ DebugBegin(BAUD_RATE); } /** * 初始化wifi模塊:工做模式 鏈接網絡 */ void initWifi(){ WiFi.mode(WIFI_STA); WiFi.begin(AP_SSID, AP_PSK); DebugPrintln(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); DebugPrint("."); } DebugPrintln(""); DebugPrint("Connected to "); DebugPrintln(AP_SSID); DebugPrint("IP address: "); DebugPrintln(WiFi.localIP()); } /** * 初始化webserver */ void initWebServer(){ //如下配置uri對應的handler server.on("/", handleRoot); server.on("/inline", []() { server.send(200, "text/plain", "this works as well"); }); server.on("/test.svg", drawGraph); server.onNotFound(handleNotFound); //啓動webserver server.begin(); DebugPrintln("HTTP server started"); } /** * 畫圖 */ void drawGraph() { String out = ""; char temp[100]; out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n"; out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n"; out += "<g stroke=\"black\">\n"; int y = rand() % 130; for (int x = 10; x < 390; x += 10) { int y2 = rand() % 130; sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2); out += temp; y = y2; } out += "</g>\n</svg>\n"; server.send(200, "image/svg+xml", out); }
實驗結果:
實驗說明:
源碼:
/** * Demo: * 演示webserver auth校驗功能 * (當wifi模塊鏈接上ap以後,在pc瀏覽器中輸入ip+uri來訪問,不過須要帶校驗請求頭) * @author 單片機菜鳥 * @date 2019/09/10 */ #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* AP_SSID = "TP-LINK_5344"; // XXXXXX -- 使用時請修改成當前你的 wifi ssid const char* AP_PSK = "xxxx"; // XXXXXX -- 使用時請修改成當前你的 wifi 密碼 const unsigned long BAUD_RATE = 115200; // serial connection speed const char* www_username = "admin"; const char* www_password = "esp8266"; //聲明一下函數 void initBasic(void); void initWifi(void); void initWebServer(void); ESP8266WebServer server(80);//建立webserver void setup() { initBasic(); initWifi(); initWebServer(); } void loop() { server.handleClient(); } /** * 初始化基礎功能:波特率 */ void initBasic(){ DebugBegin(BAUD_RATE); } /** * 初始化wifi模塊:工做模式 鏈接網絡 */ void initWifi(){ WiFi.mode(WIFI_STA); WiFi.begin(AP_SSID, AP_PSK); DebugPrintln(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); DebugPrint("."); } DebugPrintln(""); DebugPrint("Connected to "); DebugPrintln(AP_SSID); DebugPrint("IP address: "); DebugPrintln(WiFi.localIP()); } /** * 初始化webserver */ void initWebServer(){ //如下配置uri對應的handler server.on("/", []() { //校驗賬號和密碼 if (!server.authenticate(www_username, www_password)) { return server.requestAuthentication(); } server.send(200, "text/plain", "Login OK"); }); server.begin(); DebugPrint("Open http://"); DebugPrint(WiFi.localIP()); DebugPrintln("/ in your browser to see it working"); }
實驗結果:
實驗說明:
源碼:
/** * Demo: * 演示webserver auth校驗功能 * (當wifi模塊鏈接上ap以後,在pc瀏覽器中輸入ip+uri來訪問,不過須要帶校驗請求頭) * @author 單片機菜鳥 * @date 2019/09/10 */ #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> //如下三個定義爲調試定義 #define DebugBegin(baud_rate) Serial.begin(baud_rate) #define DebugPrintln(message) Serial.println(message) #define DebugPrint(message) Serial.print(message) const char* AP_SSID = "TP-LINK_5344"; // XXXXXX -- 使用時請修改成當前你的 wifi ssid const char* AP_PSK = "6206908you11011010"; // XXXXXX -- 使用時請修改成當前你的 wifi 密碼 const unsigned long BAUD_RATE = 115200; // serial connection speed //聲明一下函數 void initBasic(void); void initWifi(void); void initWebServer(void); ESP8266WebServer server(80); /** * 校驗是否存在cookie頭而且cookie頭的值是正確的 */ bool is_authentified() { DebugPrintln("Enter is_authentified"); //是否存在cookie頭 if (server.hasHeader("Cookie")) { DebugPrint("Found cookie: "); //獲取cookie頭的信息 String cookie = server.header("Cookie"); DebugPrintln(cookie); if (cookie.indexOf("ESPSESSIONID=1") != -1) { DebugPrintln("Authentification Successful"); return true; } } DebugPrintln("Authentification Failed"); return false; } /** * 處理登錄uri */ void handleLogin() { String msg; //判斷是否存在cookie頭 if (server.hasHeader("Cookie")) { DebugPrint("Found cookie: "); String cookie = server.header("Cookie"); DebugPrint(cookie); } //判斷是否存在DISCONNECT參數 if (server.hasArg("DISCONNECT")) { DebugPrintln("Disconnection"); server.sendHeader("Location", "/login"); server.sendHeader("Cache-Control", "no-cache"); server.sendHeader("Set-Cookie", "ESPSESSIONID=0"); server.send(301); return; } //判斷是否存在USERNAME和PASSWORD參數 if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) { if (server.arg("USERNAME") == "admin" && server.arg("PASSWORD") == "admin") { server.sendHeader("Location", "/"); server.sendHeader("Cache-Control", "no-cache"); server.sendHeader("Set-Cookie", "ESPSESSIONID=1"); server.send(301); DebugPrintln("Log in Successful"); return; } msg = "Wrong username/password! try again."; DebugPrintln("Log in Failed"); } //返回html 填寫帳號密碼頁面 String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>"; content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>"; content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>"; content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>"; content += "You also can go <a href='/inline'>here</a></body></html>"; server.send(200, "text/html", content); } /** * 根目錄處理器 */ //root page can be accessed only if authentification is ok void handleRoot() { DebugPrintln("Enter handleRoot"); String header; if (!is_authentified()) { //校驗不經過 server.sendHeader("Location", "/login"); server.sendHeader("Cache-Control", "no-cache"); server.send(301); return; } String content = "<html><body><H2>hello, you successfully connected to esp8266!</H2><br>"; if (server.hasHeader("User-Agent")) { content += "the user agent used is : " + server.header("User-Agent") + "<br><br>"; } content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>"; server.send(200, "text/html", content); } /** * 無效uri處理器 */ void handleNotFound() { String message = "File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET) ? "GET" : "POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i = 0; i < server.args(); i++) { message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); } void setup(void) { initBasic(); initWifi(); initWebServer(); } void loop(void) { server.handleClient(); } /** * 初始化基礎功能:波特率 */ void initBasic(){ DebugBegin(BAUD_RATE); } /** * 初始化wifi模塊:工做模式 鏈接網絡 */ void initWifi(){ WiFi.mode(WIFI_STA); WiFi.begin(AP_SSID, AP_PSK); DebugPrintln(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); DebugPrint("."); } DebugPrintln(""); DebugPrint("Connected to "); DebugPrintln(AP_SSID); DebugPrint("IP address: "); DebugPrintln(WiFi.localIP()); } /** * 初始化webserver */ void initWebServer(){ //如下配置uri對應的handler server.on("/", handleRoot); server.on("/login", handleLogin); server.on("/inline", []() { server.send(200, "text/plain", "this works without need of authentification"); }); server.onNotFound(handleNotFound); //設置須要收集的請求頭 const char * headerkeys[] = {"User-Agent", "Cookie"} ; size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); //收集頭信息 server.collectHeaders(headerkeys, headerkeyssize); server.begin(); DebugPrintln("HTTP server started"); }
這一篇章,主要講解了Http協議的webserver。本篇和Httpclient篇都是比較重要的篇章,但願讀者仔細研讀;