精华内容
参与话题
问答
  • 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,开发并发行的一种应用服务器。

    展开全文
  •     在前面章节的博客中,博主介绍了ESP8266WiFi库 Tcp server的用法,并模拟了Http webserver的功能。但是,可以看出通过Tcp server 处理http请求,我们需要自己解析请求协议以及判断各种数据,稍微不小心就很...

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。

    共同学习成长QQ群 622368884,不喜勿加,里面有一大群志同道合的探路人

    快速导航
    单片机菜鸟的博客快速索引(快速找到你要的)

    如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。

    文章目录

    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请求方法;

    注意点:

    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)
    1. 解析 HTTP requestUri、Http requestMethod、HttpVersion
    2. 寻找可以处理该请求的 requestHandler
    3. 对于GET请求,解析请求头、请求参数(requestArguments)、请求主机名;
    4. 对于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();
    }
    

    实验结果:

    image

    image

    image

    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);
    }
    

    实验结果:

    image

    image

    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");
    }
    

    实验结果:

    image

    image

    image

    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篇都是比较重要的篇章,希望读者仔细研读;

    展开全文
  • c# webserver上传文件到服务器的问题 遇到100多M的文件直接报 操作超时 我在web.config文件里面也已经配置了 ``` <system.web> ``` 但是没有用 下面是代码 webserver ``` //上传文件至服务器 [WebMethod] ...
  • 10月 30 14:36:12 tedu systemd[1]: Failed to start A high performance web server and a reverse proxy server. 10月 30 14:36:12 tedu systemd[1]: nginx.service: Unit entered failed state. 10月 30 14:36:...
  • 嵌入式下web server移植

    2016-03-13 05:24:01
    最近想在一块开发板上实现稍微复杂的 web server,就是想做一个跟路由器管理界面差不多的,请问这种是怎么实现的? 我主要的困惑的地方是在怎么在网页上修改系统配置,我想到的有两个方法,一个是使用系统调用的函数...
  • 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

    展开全文
  • Web Server与App Server

    千次阅读 2018-06-20 15:29:49
    Web Server  常见的Web Server有Apache Server与Nginx。  Apache Http Server是Apache软件基金会下的一个项目,是一款开源的HTTP服务器软件(它也可以作为邮件代理服务器、通用的TCP代理服务器)。  Nginx之前...

    Web Server
      常见的Web Server有Apache Server与Nginx。

      Apache Http Server是Apache软件基金会下的一个项目,是一款开源的HTTP服务器软件(它也可以作为邮件代理服务器、通用的TCP代理服务器)。

      Nginx之前有配置它的博文,大多数用它来做负载均衡。

      这两者基本相同,HTTP服务器本质上也是一种应用程序——通常运行在服务器之上,绑定服务器的IP地址并监听某一个tcp端口来接收并处理HTTP请求,这样客户端(各种浏览器)就能够通过HTTP协议来获取服务器上的网页(HTML格式)、文档(PDF格式)、音频(MP4格式)、视频(MOV格式)包括CSS、JS等等资源。下图描述的就是这一过程:

    这里写图片描述

      Web Server一般至于企业防火墙之外,这个防火墙可以认为是一个路由器,然后再CISCO路由器上开放了两个端口为:80和443。其中80端口用于正常的http访问。443端口用于https访问,即如果你在浏览器中输入https://www.xxx.com这样的地址,默认走的是443这个端口。
    总而言之,Web Server起到了占用服务器端口和只能解析一些静态文件的作用。

      一个HTTP Server关心的是HTTP协议层面的传输和访问控制,所以在Apache/Nginx上你可以看到代理、负载均衡等功能。客户端通过HTTP Server访问服务器上存储的资源(HTML文件、图片文件等等)。通过CGI技术,也可以将处理过的内容通过HTTP Server分发,但是一个HTTP Server始终只是把服务器上的文件如实的通过HTTP协议传输给客户端。

    App Server
      其至于企业防火墙之内,它和Web Server之间的连接必须且一定为内部IP连接。外部IP:即Internet IP地址,我们的Web服务器一般会有一个内部IP和一个外部IP,因此在这里,我们的App Server没有任何外部IP,只有内部IP,所以我们在这里说App Server与Web Server只能以内部IP形式连接。比如说我们用的App Server是Tomcat,它的端口为8080,那么这个IP地址上的8080端口只能由任何内部IP才能访问,外部的Internet是访问不了的,这样做就是为了安全!

      App Server用于解析我们的任何需要Java编译器才能解析的“动态”网页,其实App Server本身也能解析任何静态网页的。

      Apache HTTP Server和Nginx本身不支持生成动态页面,但它们可以通过其他模块来支持(例如通过Shell、PHP、Python脚本程序来动态生成内容)。
    如果想要使用Java程序来动态生成资源内容,使用这一类HTTP服务器很难做到。Java Servlet技术以及衍生的Java Server Pages技术可以让Java程序也具有处理HTTP请求并且返回内容(由程序动态控制)的能力,Tomcat正是支持运行Servlet/JSP应用程序的容器(Container):

      Tomcat是Apache软件基金会下的另一个项目,与Apache HTTP Server相比,Tomcat能够动态的生成资源并返回到客户端。Apache HTTP Server和Nginx都能够将某一个文本文件的内容通过HTTP协议返回到客户端,但是这个文本文件的内容是固定的——也就是说不能和网页进行一些交互,只能做简单的页面跳转,比如:包含显示当前时间的页面;显示当前IP地址的页面;

      Tomcat运行在JVM之上,它和HTTP服务器一样,绑定IP地址并监听TCP端口,同时还包含以下职责:管理Servlet程序的生命周期;将URL映射到指定的Servlet进行处理;与Servlet程序合作处理HTTP请求——根据HTTP请求生成HttpServletResponse对象并传递给Servlet进行处理,将Servlet中的HttpServletResponse对象生成的内容返回给浏览器。

      Tomcat可以认为是HTTP服务器,但通常它仍然会和Nginx配合在一起使用:
      动静态资源分离——运用Nginx的反向代理功能分发请求:所有动态资源的请求交给Tomcat,而静态资源的请求(例如图片、视频、CSS、JavaScript文件等)则直接由Nginx返回到浏览器,这样能大大减轻Tomcat的压力;负载均衡,当业务压力增大时,可能一个Tomcat的实例不足以处理,那么这时可以启动多个Tomcat实例进行水平扩展,而Nginx的负载均衡功能可以把请求通过算法分发到各个不同的实例进行处理。

    为什么既要有Web Server,又要有App Server?
      我们可以让专门负责解析静态网页的Web Server来解析HTML等内容,而让App Server专门用于解析任何需要Java编译器才能解析的东西,让它们“两人”各司其职。
      这样的好处:

    1.为App Server“减压”,同时也提高了performance(性能)。
    2.不用再把8080这个端口暴露在Internet上了,这样很安全;-),毕竟App Server上是有我们的代码的,就算是编译过的代码class文件,也是容易被“反编译”的。
    3.这也为将来的进一步的“集群扩展”打好了基础。

    转载自: https://www.cnblogs.com/zhengbin/p/5907242.html

    展开全文
  • 用thttpd做Web Server

    千次阅读 2011-06-28 13:50:00
    httpd是busybox中自带的web server,功能弱,不支持认证和CGI。thttpd和boa都支持认证CGI,功能比较全,Boa是一个单任务的小型http服务器,设计的小型系统不要数据库操作,所以可以使用thttpd作为server.1. 编译...
  • 目的 开启服务器 监听客户端 Web的请求与响应 实现Web Server功能 用过网页收发数据 建立网页 完善Web Server功能 总结
  • webserver总结

    千次阅读 2018-08-05 16:45:32
    前言 tomcat之外的其他webserver 小结
  • 什么是WEBserver? 经常使用的WEBserver有哪些?   一、什么是WEBserver  Webserver能够解析HTTP协议。当Webserver接收到一个HTTP请求,会返回一个HTTP响应,比如送回一个HTML页面。为了处理一个请求Webserver能够...
  • Qt webserver

    千次阅读 2018-01-10 14:40:25
    echoserver.h" #include "QtWebSockets/qwebsocketserver.h" #include "QtWebSockets/qwebsocket.h" #include <QtCore/QDebug> QT_USE_NAMESPACE EchoServer::EchoServer
  • WEB Server教程

    千次阅读 2007-10-17 12:19:00
    WEB Server教程 网络服务可以使你的应用程序称为网络应用程序通过使用网络服务,你的应用程序可以向世界的各个角落发布它的函数或信息。网络服务可以被其它应用程序使用。通过网络服务,你可以将公司会计部门的...
  • XML Web Server

    千次阅读 2004-07-09 21:41:00
    ?????? 伴随着Internet网络的一天天发展壮大,Internet已经成为一个人们不可缺少的工具,而人们为了更有效的利用网络的技术能力,而努力将各式各样的信息以各种不同的方式汇入到这个的bit海洋中,而这个信息的海洋也...
  • 使用NanoHttpd实现简易WebServer

    千次阅读 2016-02-17 16:16:32
    0x00 在介绍使用NanoHttpd实现简易WebServer之前,我们首先熟悉下局域网Socket通信。一个Client工程,代码地址为https://github.com/jltxgcy/AppVulnerability/tree/master/MyClient。一个Server工程,代码地址为...
  • 利用 python 的 http.server 包快速搭建web server 服务 一、背景说明 如何在两台电脑间传输大文件? 如果一个文件有 10G 那么大,一开始的想法是通过QQ传文件或者邮箱发送,但是这两种方法对文件大小都...
  • 继续小测python web server

    千次阅读 2012-10-23 12:39:43
    上次的测试见《小测几种python web server的性能》。前两天参加了PyCon2012上海站。虽然今年的PyCon被各种吐槽,但还是有点收获的。比如ShellXu的元编程,赖总的state/message,沈大侠谈的pypy等。回来就想测一下用...
  • 1)Webserver Web container Application server的区别: 马克-to-win:我下面的这段话介绍非常重要,大家定要牢记。(初学者不必看懂)i)Webserver又名http server:主要处理静态网页http,css,代表作apache,...
  • 卸载HTTPSERVER 和 WEBSERVER

    千次阅读 2007-08-30 09:55:00
    Linux:/apps/IBMIHS/_uninst # java -jar uninstall.jarLinux:/apps/WebSphere/AppServer/_uninst # java -jar uninstall.jar 

空空如也

1 2 3 4 5 ... 20
收藏数 119,914
精华内容 47,965
关键字:

webserver