授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。但愿你们分享给你周边须要的朋友或者同窗,说不定大神成长之路有博哥的奠定石。。。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篇都是比较重要的篇章,但愿读者仔细研读;