-
Web Server
2017-12-10 20:35:17初步理解 Web server就是一台由各种编程语言建立起来提供网页的电脑,最常见的是通过Http协议传给客户端的网页浏览器。 另一方面也可以说这是一个提供网页的服务器程序。常见产品 · Apache软件基金会的...初步理解
Web server就是一台由各种编程语言建立起来提供网页的电脑,最常见的是通过Http协议传给客户端的网页浏览器。
另一方面也可以说这是一个提供网页的服务器程序。常见产品
· Apache软件基金会的Apache HTTP服务器
· Microsoft的Internet Information Server(IIS)
· Google的Google Web Server
· Nginx公司的nginx
· 淘宝从nginx改良的Tengine
· lighttpd公司的lighttpd
· Cherokee_(Web服务器)
· Microsoft的FrontPage
共同点:不管是哪个公司的产品,但是宗旨是没有变化的,都是一个web server发送一个HTTP请求,HTTP回复一般包含一个HTML文件,有时也可以包含纯文本文件、图像或其他类型的文件。产品使用
在UNIX和LINUX平台下使用最广泛的免费HTTPserver是W3C、NCSA和APACHEserver,而Windows平台NT/2000/2003使用IIS的WEBserver。
在选择使用WEBserver应考虑的本身特性因素有:性能、安全性、日志和统计、虚拟主机、代理server、缓冲服务和集成应用程序等。这里说到了IIS。
是由微软公司提供的基于运行Microsoft Windows的互联网基本服务。
IIS可设置的内容包括:虚拟目录及访问权限、默认文件名称、以及是否允许浏览目录。IBM WebSphere。
是由IBM遵照开放标准,例如Java EE、XML及Web Services,开发并发行的一种应用服务器。 -
ESP8266开发之旅 网络篇⑪ WebServer——ESP8266WebServer库的使用
2019-06-20 08:56:38在前面章节的博客中,博主介绍了ESP8266WiFi库 Tcp server的用法,并模拟了Http webserver的功能。但是,可以看出通过Tcp server 处理http请求,我们需要自己解析请求协议以及判断各种数据,稍微不小心就很...授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。
共同学习成长QQ群 622368884,不喜勿加,里面有一大群志同道合的探路人
如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。
文章目录
- 1. 前言
- 2. ESP8266WebServer库
- 2.1 webserver管理方法
- 2.1.1 ESP8266WebServer() —— 创建web server
- 2.1.2 begin() —— 启动web server
- 2.1.3 close() —— 关闭webserver
- 2.1.4 stop() —— 关闭webserver
- 2.2 处理client请求方法
- 2.2.1 on() —— 官方请求响应回调
- 2.2.2 addHandler() —— 自定义请求响应回调
- 2.2.3 onNotFound() —— 配置无效uri的handler
- 2.2.4 onFileUpload() —— 配置处理文件上传的handler
- 2.3 处理client请求方法
- 2.3.1 uri() —— 获取请求的uri
- 2.3.2 method() —— 获取请求方法
- 2.3.2 arg(name) —— 获取请求参数的值
- 2.3.3 arg(index) —— 获取请求参数的值
- 2.3.4 argName(index) —— 获取请求参数的名称
- 2.3.5 args() —— 获取参数个数
- 2.3.6 hasArg() —— 是否存在某个参数
- 2.3.7 collectHeaders() —— 设置需要收集的请求头
- 2.3.8 collectHeaders() —— 设置需要收集的请求头
- 2.3.9 header(name) —— 获取请求头参数值
- 2.3.10 header(index) —— 获取第index个请求头参数值
- 2.3.11 headerName(index) —— 获取第index个请求头名字
- 2.3.12 headers() —— 获取收集请求头个数
- 2.3.13 hasHeader(name) —— 判断是否存在某一个请求头
- 2.3.14 hostHeader() —— 获取请求头Host的值
- 2.3.15 authenticate() —— 认证校验
- 2.3.16 handleClient() —— 处理http请求
- 2.4 响应client请求方法
- 3. 实例操作
- 4. 总结
1. 前言
在前面章节的博客中,博主介绍了ESP8266WiFi库 Tcp server的用法,并模拟了Http webserver的功能。但是,可以看出通过Tcp server 处理http请求,我们需要自己解析请求协议以及判断各种数据,稍微不小心就很容易出现错误。
那么有没有针对Http webserver操作的库呢?答案肯定是有的,这就是博主本篇需要跟大家讲述的知识——ESP8266WebServer库。
请注意,ESP8266WebServer库不属于ESP8266WiFi库的一部分,所以需要引入#include <ESP8266WebServer.h>
博主说过,Http是基于Tcp协议之上的,所以你在ESP8266WebServer源码中会看到WiFiServer和WiFiClient的踪迹。
............ 前面省略代码 struct RequestArgument { String key; String value; }; WiFiServer _server; WiFiClient _currentClient; .............后面省略代码
2. ESP8266WebServer库
如果读者有下载源码的话,那么可以看到ESP8266WebServer库 目录如下:
老规矩,先上一个博主总结的百度脑图:
总体上,根据功能可以把方法分为两大类:
- 管理webserver方法;
- 处理client请求方法;
- 响应client请求方法;
注意点:
- 博主希望读者可以先理解 ESP8266开发之旅 网络篇⑨ HttpClient——ESP8266HTTPClient库的使用(该篇讲述了一些http协议的知识点),然后再来仔细查看本篇内容,我相信会事半功倍;
2.1 webserver管理方法
http请求方法又可以有更好的细分。
2.1.1 ESP8266WebServer() —— 创建web server
函数说明:
/** * 创建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);
2.1.2 begin() —— 启动web server
函数说明:
/** * 启动webserver */ void begin(); /** * 启动webserver * @param port uint16_t 端口号 */ void begin(uint16_t port);
注意点:
- 尽量在配置好各个请求处理之后再调用begin方法;
2.1.3 close() —— 关闭webserver
函数说明:
/** * 关闭webserver,关闭TCP连接 */ void close();
2.1.4 stop() —— 关闭webserver
函数说明:
/** * 关闭webserver * 底层就是调用close(); */ void stop();
2.2 处理client请求方法
2.2.1 on() —— 官方请求响应回调
函数1说明:
/** * 配置uri对应的handler,handler也就是处理方法 * @param uri const String (uri路径) * @param handler THandlerFunction (对应uri处理函数) */ void on(const String &uri, THandlerFunction handler);
注意点:
- 注意点:这里对应的Http_Method 是Http_ANY,也就是不区分GET、POST等
函数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);
注意点:
- 函数1和函数2两个on方法最终都会调用到这个方法
- 请求处理函数THandlerFunction定义为:
typedef std::function<void(void)> THandlerFunction; 也就是说我们的请求处理函数定义应该是: void methodName(void); 一般我们习惯写成: void handleXXXX(){ //以下写上处理代码 }
- 最终底层代码会把fn ufn uri method封装成一个RequestHandler(FunctionRequestHandler)。
在这里,博主先给大家看看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; } }
注意点:
- 这里用到了一种思路叫做“责任链设计模式”,各个请求组成一个顺序的链表(责任链设计模式,请自行百度了解);
- 既然是链表,那么是不是意味着排在前面的优先得到处理呢?(读者可以考虑哪些请求概率比较高,然后优先放在链表的前面)。
- on()函数用到了FunctionRequestHandler来包装请求处理;
2.2.2 addHandler() —— 自定义请求响应回调
函数说明:
/** * 添加一个自定义的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; };
通过上面的代码分析,博主相信大家应该对请求处理类有一个初步的认识,用起来得心应手。
2.2.3 onNotFound() —— 配置无效uri的handler
函数说明:
/** * 配置无效uri的handler * @param fn THandlerFunction (对应uri处理函数) */ void onNotFound(THandlerFunction fn); //called when handler is not assigned
注意点:
- 当找不到可以处理某一个http请求的时候就会调用该函数配置的fn;
- 当然,如果你没有配置这个方法也可以,因为核心库底层有了默认实现:
...... if (!handled) { using namespace mime; //发送默认的404错误 send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); handled = true; } ......
2.2.4 onFileUpload() —— 配置处理文件上传的handler
函数说明:
/** * 配置处理文件上传的handler * @param fn THandlerFunction (对应uri处理函数) */ void onFileUpload(THandlerFunction fn); //handle file uploads
2.3 处理client请求方法
2.3.1 uri() —— 获取请求的uri
函数说明:
/** * 获取请求的uri */ String uri();
2.3.2 method() —— 获取请求方法
函数说明:
/** * 获取请求的uri */ HTTPMethod method()
其中,HTTPMethod取值范围为:
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
2.3.2 arg(name) —— 获取请求参数的值
函数说明:
/** * 根据请求key获取请求参数的值 * @param name String(请求key) */ String arg(String name);// get request argument value by name
2.3.3 arg(index) —— 获取请求参数的值
函数说明:
/** * 获取第几个请求参数的值 * @param i int(请求index) */ String arg(int i);// get request argument value by number
2.3.4 argName(index) —— 获取请求参数的名称
函数说明:
/** * 获取第几个请求参数的名字 * @param i int(请求index) */ String argName(int i);// get request argument name by number
2.3.5 args() —— 获取参数个数
函数说明:
/** * 获取参数个数 */ int args(); // get arguments count
2.3.6 hasArg() —— 是否存在某个参数
函数说明:
/** * 是否存在某个参数 */ bool hasArg(String name);// check if argument exists
2.3.7 collectHeaders() —— 设置需要收集的请求头
函数说明:
/** * 设置需要收集的请求头(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
2.3.8 collectHeaders() —— 设置需要收集的请求头
函数说明:
/** * 设置需要收集的请求头(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
2.3.9 header(name) —— 获取请求头参数值
函数说明:
/** * 获取请求头参数值 * @param name const char * 请求头的名字 * @return value of headerkey(name) */ String header(String name);// get request header value by name
2.3.10 header(index) —— 获取第index个请求头参数值
函数说明:
/** * 获取第i个请求头参数值 * @param i size_t 请求头索引值 * @return value of header index */ String header(int i);// get request header value by number
2.3.11 headerName(index) —— 获取第index个请求头名字
函数说明:
/** * 获取第i个请求头名字 * @param i size_t 请求头索引值 * @return name of header index */ String headerName(int i);// get request header name by number
2.3.12 headers() —— 获取收集请求头个数
函数说明:
/** * 获取收集请求头个数 * @return count int */ int headers();// get header count
2.3.13 hasHeader(name) —— 判断是否存在某一个请求头
函数说明:
/** * 判断是否存在某一个请求头 * @param name const char* 请求头名字 * @return bool */ bool hasHeader(String name); // check if header exists
2.3.14 hostHeader() —— 获取请求头Host的值
函数说明:
/** * 获取请求头Host的值 */ String hostHeader();// get request host header if available or empty String if not
2.3.15 authenticate() —— 认证校验
函数说明:
/** * 认证校验(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; }
注意点:
- 这里涉及到了HTTP Authorization的两种验证方式:HTTP Basic Auth 和 HTTP Digest Auth(感兴趣的读者请自行查阅资料);
- 该方法会对http请求进行验证用户信息,如果不通过,理论上需要用户重新输入正确用户名称和密码以便再次请求;
2.3.16 handleClient() —— 处理http请求
这是一个非常重要的方法,所以请认真阅读博主的讲解。
函数说明:/** * 等待请求进来并处理 */ 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(); } }
注意点:
- _parseRequest 方法负责解析http请求:
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取值
- _handleRequest 方法负责处理请求:
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 = ""; }
注意点:
- 处理文件上传,使用到了 HTTPUpload,当不断把文件内容写入buf时,也会不断触发我们对应的url 请求处理回调函数,这就意味着我们可以在请求处理回调函数里面把文件内容保存在本地文件系统(FS,后面博主会讲解这一块);
- 后面读者会发现,我们会在代码中用到这个类,这里先初略知道有这么一个关于Http上传的封装类;
在这里,博主重新梳理了WebServer处理Http请求的逻辑:
- 首先,获取有效的Http请求:_currentClient.available();
- 然后,开始解析Http请求:_parseRequest(_currentClient);
- 解析 HTTP requestUri、Http requestMethod、HttpVersion;
- 寻找可以处理该请求的 requestHandler;
- 对于GET请求,解析请求头、请求参数(requestArguments)、请求主机名;
- 对于POST、PUT等非GET请求,也会解析请求头、请求参数、请求主机名;,然后根据Content_Type的类型去匹配不同的读取数据方法。如果 Content_Type 是 multipart/form-data,那么会处理表单数据,需用到boundaryStr(特别地,如果涉及到文件上传功能,这会在文件上传处理过程中回调我们注册进去的文件上传处理回调函数,请读者自行往上翻阅);如果 Content_Type 属于其他的,则直接读取处理;
- 最后,匹配可以处理该请求的方法:_handleRequest();在该方法中会回调我们在第2步找到的 requestHandler,requestHandler会回调我们注册进去的对应请求的回调函数;
- 至此,整体的Http请求解析完成;
2.4 响应client请求方法
当我们经过handleClient()解析完http请求之后,我们就可以在requestHandler设置的请求处理回调函数里面获得http请求的具体信息,然后根据具体信息给到对应的响应信息。那么,我们可以在回调函数里面做什么呢?
2.4.1 upload() —— 处理文件上传
函数说明:
/** * 获取文件上传处理对象 */ 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);
2.4.2 sendHeader() —— 设置响应头
函数说明:
/** * 设置响应头 * @param name 响应头key * @param value 响应头value * @param first 是否需要放在第一行 */ void sendHeader(const String& name, const String& value, bool first = false);
2.4.3 setContentLength() —— 设置响应体长度
函数说明:
/** * 设置响应内容长度 * @param contentLength 长度 * 重大注意点:对于不知道长度调用server.setContentLength(CONTENT_LENGTH_UNKNOWN); * 然后调用若干个server.sendContent(),最后需要关闭与client的短连接(close)以表示内容结束。 */ void setContentLength(const size_t contentLength);
2.4.4 sendContent()/sendContent_P() —— 设置响应内容
函数说明:
/** * 发送响应内容 * @param content 响应内容 */ void sendContent(const String& content); void sendContent_P(PGM_P content); void sendContent_P(PGM_P content, size_t size);
2.4.5 requestAuthentication() —— 请求client认证
函数说明:
/** * 请求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("") );
2.4.6 streamFile() —— 发送响应文件流
函数说明:
/** * 发送响应文件流 * @param file 具体文件 * @param contentType 响应类型 * @param authFailMsg const String */ size_t streamFile(T &file, const String& contentType);
注意点:
- 该函数需要结合FS来讲解,本篇章略;
2.4.7 send() —— 发送响应数据
这个是我们发送响应数据的核心,需要仔细了解;
函数说明:/** * 发送响应数据 * @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; } } }
3. 实例操作
讲了那么多的理论知识,该开始实际操作了,请看以下几个例子。
3.1 演示webserver的基础功能
实验说明:
- 演示webserver的基础功能,wifi模块连接上热点之后,在pc浏览器输入serverip+uri来访问
源码:
/** * 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(); }
实验结果:
3.2 演示webserver返回html功能
实验说明:
- 演示webserver返回html功能,wifi模块连接上热点之后,在pc浏览器输入serverip+uri来访问
源码:
/** * 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); }
实验结果:
3.3 演示webserver校验
实验说明:
- 演示webserver校验帐号密码功能,Authenticate请求头
源码:
/** * 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"); }
实验结果:
3.4 演示webserver登陆功能
实验说明:
- 演示webserver登陆功能,html登陆页面
源码:
/** * 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"); }
4. 总结
这一篇章,主要讲解了Http协议的webserver。本篇和Httpclient篇都是比较重要的篇章,希望读者仔细研读;
-
什么是WEBserver? 经常使用的WEBserver有哪些?
2018-12-06 00:18:13什么是WEBserver? 经常使用的WEBserver有哪些? 一、什么是WEBserver Webserver能够解析HTTP协议。当Webserver接收到一个HTTP请求,会返回一个HTTP响应,比如送回一个HTML页面。为了处理一个请求Webserver能够...什么是WEBserver? 经常使用的WEBserver有哪些?
一、什么是WEBserverWebserver能够解析HTTP协议。当Webserver接收到一个HTTP请求,会返回一个HTTP响应,比如送回一个HTML页面。为了处理一个请求Webserver能够响应一个静态页面或图片,进行页面跳转或者把动态响应的产生托付给一些其他的程序比如CGI脚本,JSP脚本,servlets,ASP脚本,server端JavaScript,或者一些其他的server端技术。不管它们(译者注:脚本)的目的怎样,这些server端的程序通常产生一个HTML的响应来让浏览器能够浏览。
二。经常使用的WEBserver有哪些?
在UNIX和LINUX平台下使用最广泛的免费HTTPserver是W3C、NCSA和APACHEserver,而Windows平台NT/2000/2003使用IIS的WEBserver。在选择使用WEBserver应考虑的本身特性因素有:性能、安全性、日志和统计、虚拟主机、代理server、缓冲服务和集成应用程序等,以下介绍几种经常使用的WEBserver。
Microsoft IIS
Microsoft的Webserver产品为Internet Information Server (IIS), IIS 是同意在公共Intranet或Internet上公布信息的Webserver。IIS是眼下最流行的Webserver产品之中的一个,非常多著名的站点都是建立在IIS的平台上。IIS提供了一个图形界面的管理工具,称为 Internet服务管理器,可用于监视配置和控制Internet服务。IIS是一种Web服务组件,当中包含Webserver、FTPserver、NNTPserver和SMTPserver,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包含互联网和局域网)上公布信息成了一件非常easy的事。它提供ISAPI(Intranet Server API)作为扩展Webserver功能的编程接口;同一时候,它还提供一个Internet数据库连接器,能够实现对数据库的查询和更新。
IBM WebSphere
WebSphere Application Server 是 一 种功能完好、开放的Web应用程序server,是IBM电子商务计划的核心部分,它是基于 Java 的应用环境,用于建立、部署和管理 Internet 和 Intranet Web 应用程序。 这一整套产品进行了扩展,以适应 Web 应用程序server的须要,范围从简单到高级直到企业级。WebSphere 针对以 Web 为中心的开发者,他们都是在基本 HTTPserver和 CGI 编程技术上成长起来的。IBM 将提供 WebSphere 产品系列,通过提供综合资源、可反复使用的组件、功能强大并易于使用的工具、以及支持 HTTP 和 IIOP 通信的可伸缩执行时环境,来帮助这些用户从简单的 Web 应用程序转移到电子商务世界。
BEA WebLogic
BEA WebLogic Server 是一种多功能、基于标准的web应用server,为企业构建自己的应用提供了坚实的基础。各种应用开发、部署全部关键性的任务,不管是集成各种系统和数据库,还是提交服务、跨 Internet 协作,起始点都是 BEA WebLogic Server。因为 它具有全面的功能、对开放标准的遵从性、多层架构、支持基于组件的开发,基于 Internet 的企业都选择它来开发、部署最佳的应用。BEA WebLogic Server 在使应用server成为企业应用架构的基础方面继续处于率先地位。BEA WebLogic Server 为构建集成化的企业级应用提供了稳固的基础,它们以 Internet 的容量和速度,在连网的企业之间共享信息、提交服务,实现协作自己主动化。
APACHE
apache仍然是世界上用的最多的Webserver,市场占有率达60%左右。它源于NCSAhttpdserver,当NCSA WWWserver项目停止后,那些使用NCSA WWWserver的人们開始交换用于此server的补丁,这也是apache名称的由来(pache 补丁)。世界上非常多著名的站点都是Apache的产物,它的成功之处主要在于它的源码开放、有一支开放的开发队伍、支持跨平台的应用(能够执行在差点儿全部的Unix、Windows、Linux系统平台上)以及它的可移植性等方面。
Tomcat
Tomcat是一个开放源码、执行servlet和JSP Web应用软件的基于Java的Web应用软件容器。Tomcat Server是依据servlet和JSP规范进行执行的,因此我们就能够说Tomcat Server也实行了Apache-Jakarta规范且比绝大多数商业应用软件server要好。
Tomcat是Java Servlet 2.2和JavaServer Pages 1.1技术的标准实现,是基于Apache许可证下开发的自由软件。Tomcat是全然重写的Servlet API 2.2和JSP 1.1兼容的Servlet/JSP容器。Tomcat使用了JServ的一些代码,特别是Apache服务适配器。随着Catalina Servlet引擎的出现,Tomcat第四版号的性能得到提升,使得它成为一个值得考虑的Servlet/JSP容器,因此眼下很多WEBserver都是採用Tomcat。
眼下,很多大型Web应用一般将Apache和Tomcat结合使用,Apache负责接收用户的HTTP请求,假设请求是Servlet、Jsp,则把请求转发给Tomcat处理,并将处理结果封装响应给用户。 -
webserver技术总结之一:webserver概念
2019-06-14 18:03:22比如我们在开发项目的过程中,需要调用别的公司提供的数据,这里我们就需要使用到webserver。当前的应用程序开发逐步的呈现了两种迥然不同的倾向:1:基于浏览器的瘦客户端应用程序,2:基于浏览器的富客户端应用...一:为什么需要WebService
大家或多或少都可能听说WebService,也可能用到过。比如我们在开发项目的过程中,需要调用别的公司提供的数据,这里我们就需要使用到webserver。当前的应用程序开发逐步的呈现了两种迥然不同的倾向:1:基于浏览器的瘦客户端应用程序,2:基于浏览器的富客户端应用程序(RIA)。当然后一种技术相对来说更加的时髦一些(如现在很流行的Html5技术),这里主要讲前者。
基于浏览器的瘦客户端应用程序并不是 因为瘦客户能够提供更好的用户界面,而是因为它能够避免花在桌面应用程序发布上的高成本。发布桌面应用程序成本很高,
一半是因为应用程序安装和配置的问 题,另一半是因为客户和服务器之间通信的问题。传统的Windows富客户应用程序使用DCOM来与服务器进行通信和调用远程对象。
配置好DCOM使其在 一个大型的网络中正常工作将是一个极富挑战性的工作,同时也是许多IT工程师的噩梦。事实上,许多IT工程师宁愿忍受浏览器所带来的功能限制,
也不愿在局 域网上去运行一个DCOM。关于客户端与服务器的通信问题,一个完美的解决方法是使用HTTP协议来通信。这是因为任何运行Web浏览器的机器都在使用 HTTP协议。
同时,当前许多防火墙也配置为只允许HTTP连接。许多商用程序还面临另一个问题,那就是与其他程序的互操作性。如果所有的应用程序都是使用COM或.NET语言写的,
并且都运行在Windows平台上,那就天下太平了。然而,事实上大多数商业数据仍然在大型主机上以非关系文件(VSAM) 的形式存放,并由COBOL语言编写的大型机程序访问。
而且,目前还有很多商用程序继续在使用C++、Java、Visual Basic和其他各种各样 的语言编写。现在,除了最简单的程序之外,所有的应用程序都需要与运行在其他异构平台上
的应用程序集成并进行数据交换。这样的任务通常都是由特殊的方法, 如文件传输和分析,消息队列,还有仅适用于某些情况的的API,
如IBM的高级程序到程序交流(APPC)等来完成的。
在以前,没有一个应用程序通信标 准,是独立于平台、组建模型和编程语言的。只有通过Web Service,客户端和服务器才能够自由的用HTTP进行通信,
不论两个程序的平台和编程语言是什么。
二:WebService的含义
综上所述,WebService是一种跨编程语言和跨操作系统的远程调用技术。或者说是一种以HTTP协议为基础,通过xml进行客户端和服务器端通讯的框架或者组件。
天气预报系统、淘宝网、校内网等会把自己的服务以web service的形式暴露出来,外界可以通过Web进行调用。我们调用这个web service的应用程序就是客户端,提供webservice服务的就是服务器端。
值得注意的是,我们编写的webservice必须符合它的标准。
三:WebService的实现原理
XML+XSD,SOAP和WSDL就是构成WebService平台的三大技术。
1:XML+XSD
WebService采用HTTP协议传输数据,采用XML格式封装数据(即XML中说明调用远程服务对象的哪个方法,传递的参数是什么,
以及服务对象的 返回结果是什么)。
XML是WebService平台中表示数据的格式。除了易于建立和易于分析外,XML主要的优点在于它既是平台无关的,又是厂商无关 的。无关性是比技术优越性更重要的:软件厂商是不会选择一个由竞争对手所发明的技术的。
XML解决了数据表示的问题,但它没有定义一套标准的数据类型,更没有说怎么去扩展这套数据类型。
例如,整形数到底代表什么?16位,32位,64位?
这 些细节对实现互操作性很重要。XML Schema(XSD)就是专门解决这个问题的一套标准。它定义了一套标准的数据类型,
并给出了一种语言来扩展这套数据类型。WebService平台就 是用XSD来作为其数据类型系统的。
当你用某种语言(如VB.NET或C#)来构造一个Web service时,为了符合WebService标准,所 有你使用的数据类型都
必须被转换为XSD类型。你用的工具可能已经自动帮你完成了这个转换,但你很可能会根据你的需要修改一下转换过程。
2:SOAP
WebService通过HTTP发送请求和接收结果。发送的请求内容和结果内容都采用xml格式封装,并增加了一些特定的HTTP消息头,
这些特定的HTTP消息头和xml内容格式就是SOAP协议。
SOAP协议=HTTP 协议+XML数据格式
SOAP协议定义了SOAP消息的格式,SOAP协议是基于HTTP协议的,SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。
打个比喻:HTTP就是普通公路,XML就是中间的绿色隔离带和两边的防护栏,SOAP就是普通公路经过加隔离带和防护栏改造过的高速公路。
3:wsdl
好比我们去商店买东西,首先要知道商店里有什么东西可买,然后再来购买,商家的做法就是张贴广告海报。 WebService也一样,
WebService客户端要调用一个WebService服务,首先要有知道这个服务的地址在哪,以及这个服务里有什么方 法可以调用,
所以,WebService务器端首先要通过一个WSDL文件来说明自己家里有啥服务可以对外调用,
服务是什么(服务中有哪些方法,方法接受 的参数是什么,返回值是什么),服务的网络地址用哪个url地址表示,服务通过什么方式来调用。
WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述Web Service及其函数、参数和返回值。
它是WebService客户端和服务器端都能理解的标准格式。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,
这将是一个很大的好处。一些最新的开发工具既能根据你的 Web service生成WSDL文档,又能导入WSDL文档,生成调用相应WebService的
代理类代码。
WSDL 文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用一个WebService服务之前,
要知道该服务的WSDL文件的地址。
WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:
1.注册到UDDI服务器,以便被人查找;
2.直接告诉给客户端调用者。
四:WebService开发
Webservice开发分为:服务端开发和客户端开发。
1:服务端开发
把公司内部系统的业务方法发布成WebService服务,供远程合作单位和个人调用。
(借助一些WebService框架可以很轻松地把自己的业务对象发布成WebService服务,
Java方面的典型WebService框架包括:axis,xfire,cxf 等,java ee服务器通常也支持发布WebService服务,例如JBoss。)
2:客户端开发
调用别人发布的WebService服务,大多数人从事的开发都属于这个方面,例如,调用天气预报WebService服务。
(使用厂 商的WSDL2Java之类的工具生成静态调用的代理类代码;
使用厂商提供的客户端编程API类;使用SUN公司早期标准的jax-rpc开发包;使用 SUN公司最新标准的jax-ws开发包。当然SUN已被ORACLE收购)
3:WebService原理
1:编写好WebService服务端后,需要向UUID服务器注册供别人使用。
2:客户端去UUID服务器上查询自己需要的WebService。
3:客户端向WebService提供者询问确切的调用方法
4:WebService服务器向客户端发送一个Wsdl文件。该WSDL文件描述了它所能提供的所有方法接口。
5:客户端了解之后,将WSDL中描述的接口方法封装成HTTP请求,发送给WebService服务器。
6:WebService服务器端响应客户端发送的HTTP请求,将处理结果以同样的SOAP报文形式通过HTTP协议发送给客户端。
参考资料:
http://www.cnblogs.com/xdp-gacl/p/4048937.html
http://blog.csdn.net/ostrichmyself/article/details/6393627
-
从零开始的ESP8266探索(06)-使用Server功能搭建Web Server
2018-05-28 16:55:10目的 开启服务器 监听客户端 Web的请求与响应 实现Web Server功能 用过网页收发数据 建立网页 完善Web Server功能 总结 -
Web Server与App Server
2018-06-20 15:29:49Web Server 常见的Web Server有Apache Server与Nginx。 Apache Http Server是Apache软件基金会下的一个项目,是一款开源的HTTP服务器软件(它也可以作为邮件代理服务器、通用的TCP代理服务器)。 Nginx之前... -
webserver总结
2018-08-05 16:45:32前言 tomcat之外的其他webserver 小结 -
Webserver ,Web container, Application server的区别
2019-04-12 08:39:521)Webserver Web container Application server的区别: 马克-to-win:我下面的这段话介绍非常重要,大家定要牢记。(初学者不必看懂)i)Webserver又名http server:主要处理静态网页http,css,代表作apache,... -
区别:web server VS. application server
2019-06-20 11:11:38web服务(web server)主要用于接收和响应客户端用户的请求,一般用于处理静态资源。 应用服务(application server) 主要用于处理,一般用于处理动态资源。 应用服务整合了多种服务,例如web服务、数据库服务。 ... -
[MacOS]XAMPP Apache Web Server 无法启动,提示Another web server is already running.
2018-05-21 15:25:42安装XAMPP后,Apache Web Server默认使用的端口是80(此端口有可能会被其他进程占用了)。打开Terminal 使用start命令 以启动Serversudo /Applications/XAMPP/xamppfiles/xampp start可以看到“Another web server is ... -
web server(web服务器)简单了解
2017-10-24 17:29:50web server简单了解(一) -
解决启动eureka报错Unable to start web server; nested exception is org.springframework.boot.web....
2020-04-01 16:21:06主要是Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat at Error starting ApplicationContext. To display the ... -
Preview on Web Server 插件
2018-07-30 22:38:22Preview on Web Server 个人认为是一个非常好用的插件,他可以让你预览的web界面同步手机,浏览器,互相同步,达到非常好的测试效果。安装方法如下: 1、点击扩展 2、搜索 Preview on Web Server 3、点击安装 4... -
SQL Server 访问URL 调用WebServer
2016-03-10 10:40:06SQL Server 访问URL 调用WebServer以下整理的SQL Server中访问URL地址的方法,并已封装成存储过程,可以实现POST/GET请求SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON GO /** 存储过程发起URL请求启用 Ole ... -
ESP8266 webserver
2018-07-18 19:27:43ESP8266 webserver 简单测试了一下,还不错。连上ESP8266的热点,打开IE浏览器地址栏输入192.168.4.1回车可看到HTTP测试成功 字样。使用其他的浏览器(谷歌)结果会下载一个文件,打开下载的文件可看到成功字样,... -
Unable to start web server; nested exception is org.springframework.boot.web
2020-03-10 20:45:25Unable to start web server; nested exception is org.springframework.context.ApplicationContextExcept ———————————————— -
使用MicroPython开发ESP32(06):WebServer功能实现简单说明
2020-02-28 16:31:13文章目录目的WebServer基础说明官方WebServer例程演示第三方WebServer库介绍总结 目的 WebServer基础说明 官方WebServer例程演示 MicroPython官方的WebServer例程可以在下面链接中找到: ...下面拿其中一... -
用Python实现简单的Web Server
2019-05-02 17:05:43Web Server的概念 用Python实现Web Server Python 2中SimpleHTTPServer模块被合并到Python 3的http.server模块。它支持目录浏览,指定端口,指定绑定地址等。 方法一:直接在命令行调用http.server模... -
PB9 web server 搭建环境
2015-01-02 16:02:11PB9怎么样调用web server 数据,web server 用其它软件发布,然后用pb9去读取数据。 -
c++ Webserver的实现
2018-07-05 16:23:39github:https://github.com/viktorika/Webserver 模型: 参考muduo部分代码,采用Reactor模型+EPOLL(ET)非阻塞IO模式.外加线程池提高服务器的并发性能.主线程accept,其他线程读-解析-写. Reactor模型运行过程 ... -
利用 python 的 http.server 包快速搭建web server 服务
2018-09-21 16:00:19利用 python 的 http.server 包快速搭建web server 服务 一、背景说明 如何在两台电脑间传输大文件? 如果一个文件有 10G 那么大,一开始的想法是通过QQ传文件或者邮箱发送,但是这两种方法对文件大小都... -
Web Server的启动过程
2018-06-07 22:13:11一 Neutron Server的范围先看下图:图中,加粗虚框内,自Web Server以下,从urlmap app部件开始,包括Version Filter Apps、Extension Service Filter Apps、Core Service App等,都是符合WSGI规范的Application。... -
LoadRunner WebTours web Server端口被占用问题
2017-01-05 11:10:44解决三个问题:1 规避端口本身已经使用的问题;2 解决端口被这个同样的程序占用的问题;...启动Samples-Web-Start Web Server时,提示Could not open port 1080-Port is already used by another server(wsaeaddri -
goahead Web Server 环境搭建
2015-11-13 17:20:02GoAhead WebServer GoAhead WebServer,它是一个源码,免费、功能强大、可以在多个平台运行的嵌入式WebServer。 GoAhead WebServer的主要特性有: 1. 支持ASP 2. 嵌入式的javascript 3. 标准的CGI执行 4. 内存中的... -
unable to start debugging on the web server. The web server could not find the requested resource
2012-06-19 06:10:31Bug:unable to start debugging on the web server. The web server could not find the requested resource In II7 and Visual Studio 2010 Solution: !. Check IIS Default Web Site is start or no -
PHP内置的Web Server的使用
2017-12-21 11:13:17让我们来看看php Web Server的简单使用: 启动php Web Server php -S localhost:8080 通过 php -S 命令即可启动PHP自带的Web Server,后面跟网络地址及监听的端口号,默认的网站根目录... -
Mongoose web server in Linux
2015-03-03 22:24:03It’s very easy to start a Python web server on localhost. If you want something more serious but still lightweight, check out the Mongoose web server. Fetch and compile: 1 2 3 -
使用NanoHttpd实现简易WebServer
2016-02-17 16:16:320x00 在介绍使用NanoHttpd实现简易WebServer之前,我们首先熟悉下局域网Socket通信。一个Client工程,代码地址为https://github.com/jltxgcy/AppVulnerability/tree/master/MyClient。一个Server工程,代码地址为... -
[Erlang]图解分析Mochiweb web server
2014-11-03 17:48:07一、图解分析mochiweb web server 首先,建立web.app 文件 web_app.erl main启动启动监督gen_server 根据监督策略 执行到 web_web:start(Options)进入mochiweb_http 模块启动web server服务器socket参数设置... -
使用Java实现一个最简单的Web Server
2017-08-28 23:42:30Hello Web ServerWeb Server没有你想象的那么难实现(当然要实现一个好的Java Web Server还是很有难度...我们先来一个屌丝版,这个屌丝版能显示Hello Web Serverpackage exec.network.webserver;import java.io.Buffer