美文网首页ESP8266 Arduino物联网开发之旅
ESP8266开发之旅 网络篇⑨ HttpClient——ESP

ESP8266开发之旅 网络篇⑨ HttpClient——ESP

作者: 单片机菜鸟博哥 | 来源:发表于2019-02-13 15:18 被阅读64次

    1.1 前言

        在前面章节的博客中,博主介绍了ESP8266WiFi库 Tcp client的用法,并模拟了Http请求。但是,可以看出通过WiFiClient模拟Http请求,我们需要自己拼装Http请求协议,稍微不小心就很容易拼接错误。
        那么有没有针对Http请求操作的库呢?答案肯定是有的,这就是博主本篇需要跟大家讲述的知识——ESP8266HTTPClient库。
        请注意,ESP8266HTTPClient库不属于ESP8266WiFi库的一部分,所以需要引入

    #include <ESP8266HTTPClient.h>
    

        博主说过,Http是基于Tcp协议之上的,所以你在ESP8266HTTPClient源码中会看到TcpClient的踪迹。

    #ifndef ESP8266HTTPClient_H_
    #define ESP8266HTTPClient_H_
    
    #include <memory>
    #include <Arduino.h>
    #include <WiFiClient.h> //这里就是我们熟悉的TCP
    
    #ifdef DEBUG_ESP_HTTP_CLIENT
    #ifdef DEBUG_ESP_PORT
    #define DEBUG_HTTPCLIENT(...) DEBUG_ESP_PORT.printf( __VA_ARGS__ )
    #endif
    #endif
    

    1.2 简述Http协议

        在讲述ESP8266HTTPClient库之前,为了更好的讲解它的使用,博主给大家略讲一下Http协议,更加深入的了解请自行查阅资料。

    1.2.1 HTTP简介

        HTTP协议是Hyper Text Transfer Protocol的缩写,简称超文本传输协议,用于从WWW服务器传输文本到本地浏览器的传送协议。
        HTTP是一个基于TCP/IP通信协议来传递数据,浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。WEB服务器根据接收到的请求后,向客户端发送响应信息。

    image
        HTTP协议作为TCP/IP模型中应用层的协议,承载于TCP协议之上,有时也承载于TLS或者SSL协议层之上,这个时候就是我们时常说的HTTPS。 image

        HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP默认的端口号是80,HTTPS的端口号是443。
        浏览网页是HTTP主要应用,但不代表只用于网页浏览。HTTP只是一种协议,只要通信双方遵守这个协议,HTTP就能用。

    1.2.2 HTTP特点

    1. 简单快速:客户端向服务端请求服务时,只需要传送请求方法和路径。HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度快;
    2. 灵活:HTTP允许传输任意类型的数据对象,正在传输的类型由Content-Type加以标记。
    3. 连接问题
    • HTTP0.9和1.0使用非持续连接:限制每次连接都只处理一个请求,服务端处理完客户的请求,并收到客户的应答后,即断开连接;
    • HTTP1.1使用持续连接:不必为每个web对象创建一个新连接,一个连接可以传送多个对象,节省传输时间;
    1. 无状态:HTTP协议是无状态。对于事务处理没有记忆能力,如果需要处理前面信息,则必须重传,这样可能导致每次连接传送的数据量增大。

    1.2.3 HTTP工作流程

    一次HTTP操作称为一个事务,工作流程可分为4步:

    1. 首先客户端 client与服务端 server建立连接。
    2. 建立连接后,客户端发送一个请求给服务端,请求方法的格式:统一资源标识符(URL)、HTTP协议版本号、请求头、请求内容等;
    3. 服务端接收到请求后,给予相应的响应信息,其格式为:状态行(包括协议版本、成功或者失败代码)、服务器信息、实体信息等;
    4. 客户端接收到服务端返回的信息,通过浏览器显示在用户的显示屏上,然后客户端与服务端断开连接;

    以上四步骤,只要其中一步出现错误,那么就会产生错误信息返回给客户端。

    1.2.4 HTTP请求

        客户端发送一个HTTP请求到服务器,请求信息包括以下格式:

    • 请求行(request line)
    • 请求头部(header)
    • 空行 (empty line)
    • 请求数据 (request body)
    image

        请求行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本。

    1.2.4.1 Get请求

    请求例子,使用Charles抓取的request:

    GET /562f25980001b1b106000338.jpg HTTP/1.1
    Host    img.mukewang.com
    User-Agent    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
    Accept    image/webp,image/*,*/*;q=0.8
    Referer    http://www.imooc.com/
    Accept-Encoding    gzip, deflate, sdch
    Accept-Language    zh-CN,zh;q=0.8
    
    1. 第一部分:请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本.
    • GET说明请求类型为GET,[/562f25980001b1b106000338.jpg]为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。
    1. 第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息:
    • 从第二行起为请求头部,HOST将指出请求的目的地.User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.
    • 该信息由你的浏览器来定义,并且在每个请求中自动发送等等
    1. 第三部分:空行,请求头部后面的空行是必须的:
    • 即使第四部分的请求数据为空,也必须有空行。
    1. 第四部分:请求数据也叫主体,可以添加任意的其他数据。
    • 这个例子的请求数据为空。
    1.2.4.2 POST请求

    请求例子,使用Charles抓取的request:

    POST / HTTP1.1
    Host:www.wrox.com
    User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
    Content-Type:application/x-www-form-urlencoded
    Content-Length:40
    Connection: Keep-Alive
     
    name=Professional%20Ajax&publisher=Wiley
    
    1. 第一部分:请求行,第一行明了是post请求,以及http1.1版本。
    2. 第二部分:请求头部,第二行至第六行。
    3. 第三部分:空行,第七行的空行。
    4. 第四部分:请求数据,第八行。

    1.2.5 HTTP Response响应信息

    一般情况下,服务端接收并处理客户端发过来的请求会返回一个HTTP的响应信息。HTTP响应也由四个部分组成,分别是:

    • 状态行
    • 消息报头
    • 空行
    • 响应正文
    image
    1. 第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
    • 第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
    1. 第二部分:消息报头,用来说明客户端要使用的一些附加信息:
    • 第二行和第三行为消息报头
    • Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8
    1. 第三部分:空行,消息报头后面的空行是必须的 4. 第四部分:响应正文,服务器返回给客户端的文本信息。
    • 空行后面的html部分为响应正文。

    1.2.6 HTTP状态码

    状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:

    • 1xx:指示信息--表示请求已接收,继续处理
    • 2xx:成功--表示请求已被成功接收、理解、接受
    • 3xx:重定向--要完成请求必须进行更进一步的操作
    • 4xx:客户端错误--请求有语法错误或请求无法实现
    • 5xx:服务器端错误--服务器未能实现合法的请求

    常见状态码:

    • 200 OK //客户端请求成功
    • 400 Bad Request //客户端请求有语法错误,不能被服务器所理解
    • 401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
    • 403 Forbidden //服务器收到请求,但是拒绝提供服务
    • 404 Not Found //请求资源不存在,eg:输入了错误的URL
    • 500 Internal Server Error //服务器发生不可预期的错误
    • 503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

    1.3 ESP8266HTTPClient库

        在1.2中初略讲述了Http协议,相信读者应该有初步认识,那么接下来我们就可以开始进入ESP8266HTTPClient库了。
        老规矩,先上一个博主总结的百度脑图:

    [图片上传失败...(image-c498f8-1550042303496)].png)

    总体上,根据功能可以把方法分为两大类:

    • 跟http请求相关方法
    • 跟http响应相关方法

        有兴趣看源码的读者请看 ESP8266HTTPClient.cpp

    1.3.1 http请求方法

        http请求方法又可以有更好的细分。

    1.3.1.1 begin —— 封装请求Url

    函数说明:

    /**
     * 解析url以获得所有参数,默认port是80端口
     * @param url String
     */
    bool begin(String url);
     
    /**
     * 设置host port 以及uri
     * @param host String(192.168.1.12,不需要带上http://前缀)
     * @param port uint16_t
     * @param uri  String
     */
    bool begin(String host, uint16_t port, String uri = "/");
    

    注意点:

    1. url可以有以下几种形态
    1. http://192.168.1.12/test.html
    2. http://user:password@192.168.1.12/test.html
    3. http://user:password@192.168.1.12:8888/test.html
    

        三者共同点:都需要http://开头 有host(192.168.1.12) 有uri(/test.html)。

    • 对于1,没有Authorization(跟用户校验有关,会转成base64编码),也没有重新设置端口(默认端口80);
    • 对于2,有Authorization(user:password,跟用户校验有关,会转成base64编码),但没有重新设置端口;
    • 对于3,有Authorization也有重新设置端口号为8888;
    • 至于用哪一种,就看需求
    1.3.1.2 setReuse —— 封装标准请求头keep-alive

    函数说明:

    /**
     * try to reuse the connection to the server
     * keep-alive 请求头
     * @param reuse bool
     */
    void setReuse(bool reuse); // keep-alive
    
    1.3.1.3 setUserAgent —— 封装标准请求头User-Agent

    函数说明:

    /**
     * set User Agent
     * User Agent请求头:使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
     * @param userAgent const char *
     */
    void setUserAgent(const String& userAgent);
    
    1.3.1.4 setAuthorization —— 封装标准请求头Authorization

    函数说明:

    /**
     * set the Authorizatio for the http request(访问权限认证请求头信息)*  Authorization 是采用 basic auth 授权方式验证客户端请求,Authorization 请求头对应的值是 (basic base64编码) 忽略括号,
    *  其中 base64编码是将 用户名:密码 这种格式进行处理生成**的,并且自动在 header 中添加 Authorization。 
     * @param user const char *
     * @param password const char *
     */
    void setAuthorization(const char * user, const char * password);
    /**
     * set the Authorizatio for the http request(访问权限认证请求头信息)
     * @param auth const char * base64
     */
    void setAuthorization(const char * auth);
    

    当然,Http的标准请求头还不止这些,请自行查阅资料。

    1.3.1.5 addHeader —— 封装自定义请求头

    函数说明:

    /**
     * adds Header to the request
     * @param name  自定义请求头的名字
     * @param value 自定义请求头的参数值
     * @param first 是否要把当前请求头放在请求头的最前面
     * @param replace 是否需要替换之前已经存在该请求头的参数值,默认就是覆盖旧值
     */
    void addHeader(const String& name, const String& value, bool first = false, bool replace = true);
    

    注意点:

    • 自定义请求头,请求头不能为 Connection、User-Agent、Host、Authorization。
    /**
     * adds Header to the request
     * @param name
     * @param value
     * @param first
     */
    void HTTPClient::addHeader(const String& name, const String& value, bool first, bool replace)
    {
        // 过滤请求头
        if(!name.equalsIgnoreCase(F("Connection")) &&
           !name.equalsIgnoreCase(F("User-Agent")) &&
           !name.equalsIgnoreCase(F("Host")) &&
           !(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())){
    
            String headerLine = name;
            headerLine += ": ";
    
            if (replace) {
                int headerStart = _headers.indexOf(headerLine);
                if (headerStart != -1) {
                    int headerEnd = _headers.indexOf('\n', headerStart);
                    _headers = _headers.substring(0, headerStart) + _headers.substring(headerEnd + 1);
                }
            }
    
            headerLine += value;
            headerLine += "\r\n";
            if(first) {
                _headers = headerLine + _headers;
            } else {
                _headers += headerLine;
            }
        }
    
    }
    
    1.3.1.6 GET 请求

    函数说明:

    /**
     * 发送一个get请求
     * @return http 状态码
     */
    int GET();
    
    1.3.1.7 POST 请求

    函数说明:

    /**
     * 发送一个post请求
     * @param payload uint8_t * 需要提交的数据
     * @param size size_t 提交的数据的字节数
     * @return http 状态码
     */
    int POST(uint8_t * payload, size_t size);
     
    /**
     * 发送一个post请求
     * @param payload String 需要提交的数据
     * @return http 状态码
     */
    int POST(String payload);
    
    1.3.1.8 PUT 请求

    函数说明:

    /**
     * 发送一个PUT请求(博主也没有用过PUT)
     * @param payload uint8_t * 需要提交的数据
     * @param size size_t 提交的数据的字节数
     * @return http 状态码
     */
    int PUT(uint8_t * payload, size_t size);
    /**
     * 发送一个PUT请求(博主也没有用过PUT)
     * @param payload String 需要提交的数据
     * @return http 状态码
     */
    int PUT(String payload);
    
    1.3.1.9 PATCH 请求

    函数说明:

    /**
     * 发送一个PATCH请求(博主也没有用过PATCH)
     * @param payload uint8_t * 需要提交的数据
     * @param size size_t 提交的数据的字节数
     * @return http 状态码
     */
    int PATCH(uint8_t * payload, size_t size);
    /**
     * 发送一个PATCH请求(博主也没有用过PATCH)
     * @param payload String 需要提交的数据
     * @return http 状态码
     */
    int PATCH(String payload);
    
    1.3.1.10 sendRequest 发送请求

        GET、POST、PUT、PATCH最终都会调用sendRequest方法。
    函数说明:

    /**
     * GET、POST、PUT、PATCH最终都会调用sendRequest方法
     * sendRequest
     * @param type const char * 请求类型    "GET", "POST", ....
     * @param payload String  请求携带的数据  data for the message body
     * @return
     */
    int sendRequest(const char * type, String payload);
    /**
     * sendRequest
     * @param type const char * 请求类型 "GET", "POST", ....
     * @param payload uint8_t * 请求携带的数据  data for the message body if null not send
     * @param size size_t  请求携带的数据字节数 size for the message body if 0 not send
     * @return -1 if no info or > 0 when Content-Length is set by server
     */
    int sendRequest(const char * type, uint8_t * payload = NULL, size_t size = 0);
    /**
     * sendRequest
     * @param type const char *  请求类型 "GET", "POST", ....
     * @param stream Stream *  请求携带的数据流 data stream for the message body
     * @param size size_t   数据流大小 size for the message body if 0 not Content-Length is send
     * @return -1 if no info or > 0 when Content-Length is set by server
     */
    int sendRequest(const char * type, Stream * stream, size_t size = 0);
    

    我们来看看sendRequest底层源码:

    int HTTPClient::sendRequest(const char * type, uint8_t * payload, size_t size)
    {
        // connect to server
        if(!connect()) {
        //如果没有连接到服务器就提示Http拒绝连接
            return returnError(HTTPC_ERROR_CONNECTION_REFUSED);
        }
    
        //判断是否有需要提交的内容
        if(payload && size > 0) {
            //添加请求头 Content-Length
            addHeader(F("Content-Length"), String(size));
        }
    
        // 拼装并发送Http请求头
        if(!sendHeader(type)) {
            return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
        }
    
        // 根据需要,发送Http请求内容body
        if(payload && size > 0) {
            //这里就是我们熟悉的TCP发送
            if(_tcp->write(&payload[0], size) != size) {
                return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
            }
        }
    
        // 处理服务响应数据 (Header)
        return returnError(handleHeaderResponse());
    }
    
    /**
     * 拼装并发送Http请求头
     * @param type (GET, POST, ...)
     * @return status
     */
    bool HTTPClient::sendHeader(const char * type)
    {
        if(!connected()) {
            return false;
        }
    
        //Http协议版本
        String header = String(type) + " " + (_uri.length() ? _uri : F("/")) + F(" HTTP/1.");
    
        if(_useHTTP10) {
            header += "0";
        } else {
            header += "1";
        }
        //Http Host主机
        header += String(F("\r\nHost: ")) + _host;
        if (_port != 80 && _port != 443)
        {
            header += ':';
            header += String(_port);
        }
        header += String(F("\r\nUser-Agent: ")) + _userAgent +
                  F("\r\nConnection: ");
    
        if(_reuse) {
            header += F("keep-alive");
        } else {
            header += F("close");
        }
        header += "\r\n";
    
        if(!_useHTTP10) {
            header += F("Accept-Encoding: identity;q=1,chunked;q=0.1,*;q=0\r\n");
        }
    
        if(_base64Authorization.length()) {
            _base64Authorization.replace("\n", "");
            header += F("Authorization: Basic ");
            header += _base64Authorization;
            header += "\r\n";
        }
    
        //自定义请求头
        header += _headers + "\r\n";
    
        DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
    
        return (_tcp->write((const uint8_t *) header.c_str(), header.length()) == header.length());
    }
    
    
    1.3.1.11 setTimeout —— 设置请求超时

    函数说明:

    /**
     * 请求超时时间配置 ms为单位
     * @param timeout unsigned int
     */
    void setTimeout(uint16_t timeout);
    

    注意点:

    • 默认的timeout为5000ms,也就是5s;
    1.3.1.12 useHTTP10 —— http协议版本

    函数说明:

    /**
     * http协议版本
     * @param usehttp10 true表示用http1.0,默认是false,用http1.1
     */
    void useHTTP10(bool usehttp10 = true);
    
    1.3.1.13 end —— 结束请求

    函数说明:

    /**
     * 结束请求
     * called after the payload is handled
     */
    void end(void);
    

    看看源码:

    void HTTPClient::end(void)
    {
        if(connected()) {
            if(_tcp->available() > 0) {
                //清除接收缓冲区
                DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _tcp->available());
                while(_tcp->available() > 0) {
                    _tcp->read();
                }
            }
            if(_reuse && _canReuse) {
                //keep-alive的话,保持连接
                DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n");
            } else {
               //情况情况就结束连接
                DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n");
                _tcp->stop();
            }
        } else {
            DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp is closed\n");
        }
    }
    

    注意点:

    • keep-alive情况下不会断开连接,只会情况接收缓冲区;

    1.3.2 http响应方法

    1.3.2.1 collectHeaders —— 设置需要收集的响应头

    函数说明:

    /**
     * 设置需要收集的响应头(1-n个)
     * @param headerKeys[] const char *   响应头的名字
     * @param headerKeysCount const size_t 响应头的个数
     * 注意点:headerKeys数组元素个数需要大于等于 headerKeysCount
     */
    void collectHeaders(const char* headerKeys[], const size_t headerKeysCount);
    

    源码说明:

    void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount)
    {
        //设置需要收集响应头的个数
        _headerKeysCount = headerKeysCount;
        if(_currentHeaders) {
            //释放旧内存空间
            delete[] _currentHeaders;
        }
        //申请新空间
        _currentHeaders = new RequestArgument[_headerKeysCount];
        for(size_t i = 0; i < _headerKeysCount; i++) {
            //设置响应头的key
            _currentHeaders[i].key = headerKeys[i];
        }
    }
    

    RequestArgument定义如下:

    struct RequestArgument {
        String key;//键值对里面的key
        String value;//键值对里面的value
    };
    

    注意点:

    • 这个方法收集的headerKeys会在响应数据处理函数中应用到;
    1.3.2.2 header(name) —— 获取具体响应头参数值

    函数说明:

    /**
     * 获取响应头参数值
     * @param name   const char *   响应头的名字
     * @return value of headerkey(name)
     */
    String header(const char* name);
    

    源码说明:

    String HTTPClient::header(const char* name)
    {
        for(size_t i = 0; i < _headerKeysCount; ++i) {
            if(_currentHeaders[i].key == name) {
                //_currentHeaders由collectHeaders方法生成
                return _currentHeaders[i].value;
            }
        }
        return String();
    }
    

    注意点:

    • 如果没有调用collectHeaders(),那就会默认返回空字符串;
    1.3.2.3 header(index) —— 获取第index个响应头参数值

    函数说明:

    /**
     * 获取第i个响应头参数值
     * @param i   size_t   响应头索引值
     * @return value of header index
     */
    String header(size_t i);
    

    源码说明:

    String HTTPClient::header(size_t i)
    {   
        //index不能超过收集响应头的个数
        if(i < _headerKeysCount) {
            return _currentHeaders[i].value;
        }
        return String();
    }
    

    注意点:

    • 如果没有调用collectHeaders(),那就会默认返回空字符串;
    1.3.2.4 headerName(index) —— 获取第i个响应头名字

    函数说明:

    /**
     * 获取第i个响应头名字
     * @param i   size_t   响应头索引值
     * @return name of header index
     */
    String headerName(size_t i);
    

    源码说明:

    String HTTPClient::headerName(size_t i)
    {
        //index不能超过收集响应头的个数
        if(i < _headerKeysCount) {
            return _currentHeaders[i].key;
        }
        return String();
    }
    

    注意点:

    • 如果没有调用collectHeaders(),那就会默认返回空字符串;
    1.3.2.5 headers() —— 获取收集响应头个数

    函数说明:

    /**
     * 获取收集响应头个数
     * @return count int
     */
    int headers();                     // get header count
    

    源码说明:

    int HTTPClient::headers()
    {
        return _headerKeysCount;
    }
    

    注意点:

    • 如果没有调用collectHeaders(),那就会默认返回0;
    1.3.2.6 hasHeader(name) —— 判断是否存在某一个响应头

    函数说明:

    /**
     * 判断是否存在某一个响应头
     * @param name   const char*   响应头名字
     * @return bool
     */
    bool hasHeader(const char* name);  // check if header exists
    

    源码说明:

    bool HTTPClient::hasHeader(const char* name)
    {
        for(size_t i = 0; i < _headerKeysCount; ++i) {
            if((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0)) {
                return true;
            }
        }
        return false;
    }
    

    注意点:

    • 如果没有调用collectHeaders(),那就会默认返回false;
    1.3.2.7 handleHeaderResponse —— 处理响应头数据

    函数说明:

    /**
     * 读取从服务器返回的响应头数据
     * @return int http状态码
     */
    int handleHeaderResponse()
    

    函数源码:

    /**
     * reads the response from the server
     * @return int http code
     */
    int HTTPClient::handleHeaderResponse()
    {
        //判断tcp是否连接上
        if(!connected()) {
            //没有和服务器建立连接
            return HTTPC_ERROR_NOT_CONNECTED;
        }
    
        //传输编码
        String transferEncoding;
        _returnCode = -1;
        _size = -1;
        _transferEncoding = HTTPC_TE_IDENTITY;
        unsigned long lastDataTime = millis();
    
        while(connected()) {
            //判断接收缓冲区是否有数据返回
            size_t len = _tcp->available();
            if(len > 0) {
                //读取响应数据的第一行
                String headerLine = _tcp->readStringUntil('\n');
                headerLine.trim(); // remove \r
    
                lastDataTime = millis();
    
                DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str());
    
                //判断http协议版本
                if(headerLine.startsWith("HTTP/1.")) {
                    //获取状态码
                    _returnCode = headerLine.substring(9, headerLine.indexOf(' ', 9)).toInt();
                } else if(headerLine.indexOf(':')) {
                    //处理响应头key和value
                    String headerName = headerLine.substring(0, headerLine.indexOf(':'));
                    String headerValue = headerLine.substring(headerLine.indexOf(':') + 1);
                    headerValue.trim();
    
                    //返回响应数据长度
                    if(headerName.equalsIgnoreCase("Content-Length")) {
                        _size = headerValue.toInt();
                    }
    
                    //Connection连接状态
                    if(headerName.equalsIgnoreCase("Connection")) {
                        _canReuse = headerValue.equalsIgnoreCase("keep-alive");
                    }
                    //获取传输编码
                    if(headerName.equalsIgnoreCase("Transfer-Encoding")) {
                        transferEncoding = headerValue;
                    }
    
                    //这里处理我们需要收集响应头的信息,还记得我们前面有一个 collectHeaders 方法
                    for(size_t i = 0; i < _headerKeysCount; i++) {
                        if(_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
                            _currentHeaders[i].value = headerValue;
                            break;
                        }
                    }
                }
    
                if(headerLine == "") {
                    DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] code: %d\n", _returnCode);
    
                    if(_size > 0) {
                        DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] size: %d\n", _size);
                    }
    
                    if(transferEncoding.length() > 0) {
                        DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Transfer-Encoding: %s\n", transferEncoding.c_str());
                        //传输编码是chunked分块编码
                        if(transferEncoding.equalsIgnoreCase("chunked")) {
                            _transferEncoding = HTTPC_TE_CHUNKED;
                        } else {
                            //不支持其他传输编码,目前http1.1只支持chunked分块编码
                            return HTTPC_ERROR_ENCODING;
                        }
                    } else {
                        _transferEncoding = HTTPC_TE_IDENTITY;
                    }
    
                    if(_returnCode) {
                        //返回状态码
                        return _returnCode;
                    } else {
                        DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!");
                        return HTTPC_ERROR_NO_HTTP_SERVER;
                    }
                }
    
            } else {
                //判断请求是否超时
                if((millis() - lastDataTime) > _tcpTimeout) {
                    //read超时错误
                    return HTTPC_ERROR_READ_TIMEOUT;
                }
                delay(0);
            }
        }
    
        return HTTPC_ERROR_CONNECTION_LOST;
    }
    
    1.3.2.8 getString —— 获取响应数据

    函数说明:

    /**
     * 把响应数据转成字符串 (可能需要很大内存空间)
     * @return String 响应数据转成字符串
     */
    String getString(void);
    

    注意点:

    • getString底层调用 writeToStream,用到了StreamString。
      StreamString定义如下:
    class StreamString: public Stream, public String {
    public:
        size_t write(const uint8_t *buffer, size_t size) override;
        size_t write(uint8_t data) override;
    
        int available() override;
        int read() override;
        int peek() override;
        void flush() override;
    };
    
    
    #endif
    

    可以看出,StreamString继承了Stream和String,所以拥有了两者的方法。

    1.3.2.9 getStream —— 获取响应数据的流

    函数说明:

    /**
     * 获取响应数据的流
     * @return WiFiClient& tcp响应数据的流
     */
    WiFiClient& getStream(void);
    
    1.3.2.10 getStreamPtr —— 获取响应数据的流

    函数说明:

    /**
     * 获取响应数据的流
     * @return WiFiClient& tcp响应数据的流
     */
    WiFiClient* getStreamPtr(void);
    
    1.3.2.11 writeToStream —— 获取响应数据的流,并写到其他流对象

    在讲解该函数之前,博主先给读者简单介绍一下 分块编码(Transfer-Encoding: chunked):

    • Transfer-Encoding,是一个 HTTP 头部字段(响应头域),字面意思是「传输编码」。最新的 HTTP 规范里,只定义了一种编码传输:分块编码(chunked)。
    • 分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许HTTP由网页服务器发送给客户端的数据可以分成多个部分。分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供。
    • 数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。
    • 具体方法
    1. 在头部加入 Transfer-Encoding: chunked 之后,就代表这个报文采用了分块编码。这时,报文中的实体需要改为用一系列分块来传输。
    2. 每个分块包含十六进制的长度值和数据,长度值独占一行,长度不包括它结尾的 CRLF(\r\n),也不包括分块数据结尾的 CRLF。
    3. 最后一个分块长度值必须为 0,对应的分块数据没有内容,表示实体结束。
    • 例:
    HTTP/1.1 200 OK
    Content-Type: text/plain
    Transfer-Encoding: chunked
       
    25\r\n
    This is the data in the first chunk\r\n
    1C\r\n
    and this is the second one\r\n
    3\r\n
    con\r\n
    8\r\n
    sequence\r\n
    0\r\n
    \r\n
    

    接下来开始讲解该函数,函数说明:

    /**
     * 把响应数据的流写到其他流对象
     * @param Stream* 其他流对象
     * @return int 写成功的字节数
     */
    int writeToStream(Stream* stream);
    

    这个方法可以说是解析响应数据最重要的方法,所以博主在这里需要重点讲解一下,源码如下:

    /**
     * write all  message body / payload to Stream
     * @param stream Stream *
     * @return bytes written ( negative values are error codes )
     */
    int HTTPClient::writeToStream(Stream * stream)
    {
    
        if(!stream) {
            //本地流对象错误
            return returnError(HTTPC_ERROR_NO_STREAM);
        }
    
        if(!connected()) {
            //当前无连接
            return returnError(HTTPC_ERROR_NOT_CONNECTED);
        }
    
        // get length of document (is -1 when Server sends no Content-Length header)
        int len = _size;
        int ret = 0;
    
        if(_transferEncoding == HTTPC_TE_IDENTITY) {
            //没有分块编码
            ret = writeToStreamDataBlock(stream, len);
    
            // have we an error?
            if(ret < 0) {
                return returnError(ret);
            }
        } else if(_transferEncoding == HTTPC_TE_CHUNKED) {
            //分块编码
            int size = 0;
            while(1) {
                if(!connected()) {
                    return returnError(HTTPC_ERROR_CONNECTION_LOST);
                }
                //读取每一个分块的头信息
                String chunkHeader = _tcp->readStringUntil('\n');
    
                if(chunkHeader.length() <= 0) {
                    return returnError(HTTPC_ERROR_READ_TIMEOUT);
                }
    
                chunkHeader.trim(); // remove \r
    
                // read size of chunk 获取分块的大小
                len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16);
                size += len;
                DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len);
    
                // data left?
                if(len > 0) {
                    //读取分块数据
                    int r = writeToStreamDataBlock(stream, len);
                    if(r < 0) {
                        // error in writeToStreamDataBlock
                        return returnError(r);
                    }
                    ret += r;
                } else {
    
                    // if no length Header use global chunk size
                    if(_size <= 0) {
                        _size = size;
                    }
    
                    // check if we have write all data out
                    if(ret != _size) {
                        return returnError(HTTPC_ERROR_STREAM_WRITE);
                    }
                    break;
                }
    
                // 读取分块数据的结束字符串
                char buf[2];
                auto trailing_seq_len = _tcp->readBytes((uint8_t*)buf, 2);
                if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') {
                    return returnError(HTTPC_ERROR_READ_TIMEOUT);
                }
    
                delay(0);
            }
        } else {
            return returnError(HTTPC_ERROR_ENCODING);
        }
    
        end();
        return ret;
    }
    

    再来看看 writeToStreamDataBlock 函数:

    /**
     * write one Data Block to Stream
     * @param stream Stream * 流对象
     * @param size int  读取字节数
     * @return < 0 = error >= 0 = size written  读取成功个数
     */
    int HTTPClient::writeToStreamDataBlock(Stream * stream, int size)
    {
        //读一次的缓冲区大小,最大为1460
        int buff_size = HTTP_TCP_BUFFER_SIZE;
        int len = size;
        int bytesWritten = 0;
    
        // 如果len小于buff_size,设置len大小的缓冲区,优化内存使用
        if((len > 0) && (len < HTTP_TCP_BUFFER_SIZE)) {
            buff_size = len;
        }
    
        // 申请读内存空间
        uint8_t * buff = (uint8_t *) malloc(buff_size);
    
        if(buff) {
            // read all data from server
            while(connected() && (len > 0 || len == -1)) {
    
                // 获取接收缓冲区的数据字节数
                size_t sizeAvailable = _tcp->available();
    
                if(sizeAvailable) {
    
                    int readBytes = sizeAvailable;
    
                    // 判断读取字节数
                    if(len > 0 && readBytes > len) {
                        
                        readBytes = len;
                    }
    
                    // not read more the buffer can handle
                    if(readBytes > buff_size) {
                        //readBytes的最大值是申请缓冲区的大小
                        readBytes = buff_size;
                    }
    
                    // 读取数据
                    int bytesRead = _tcp->readBytes(buff, readBytes);
    
                    // 把数据写到其他流对象,比如数组 字符串
                    int bytesWrite = stream->write(buff, bytesRead);
                    bytesWritten += bytesWrite;
    
                    // are all Bytes a writen to stream ?
                    if(bytesWrite != bytesRead) {
                        //数据没有被成功一次性写入流对象
                        DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d retry...\n", bytesRead, bytesWrite);
    
                        // 检测写错误
                        if(stream->getWriteError()) {
                            DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
    
                            //reset write error for retry
                            stream->clearWriteError();
                        }
    
                        // some time for the stream
                        delay(1);
    
                        int leftBytes = (readBytes - bytesWrite);
    
                        // 尝试重新写入剩余的字节数
                        bytesWrite = stream->write((buff + bytesWrite), leftBytes);
                        bytesWritten += bytesWrite;
    
                        if(bytesWrite != leftBytes) {
                            // 重新尝试之后还是失败了
                            DEBUG_HTTPCLIENT("[HTTP-Client][writeToStream] short write asked for %d but got %d failed.\n", leftBytes, bytesWrite);
                            //释放内存空间,返回错误
                            free(buff);
                            return HTTPC_ERROR_STREAM_WRITE;
                        }
                    }
    
                    // check for write error
                    if(stream->getWriteError()) {
                        DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] stream write error %d\n", stream->getWriteError());
                        free(buff);
                        return HTTPC_ERROR_STREAM_WRITE;
                    }
    
                    // count bytes to read left
                    if(len > 0) {
                        //计算剩余需要读取的字节数,每读一次重新计算一次
                        len -= readBytes;
                    }
    
                    delay(0);
                } else {
                    delay(1);
                }
            }
    
            //读取完数据后释放空间
            free(buff);
    
            DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] connection closed or file end (written: %d).\n", bytesWritten);
    
            //判断是否读取够了leg长度的字节数
            if((size > 0) && (size != bytesWritten)) {
                DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] bytesWritten %d and size %d mismatch!.\n", bytesWritten, size);
                return HTTPC_ERROR_STREAM_WRITE;
            }
    
        } else {
            DEBUG_HTTPCLIENT("[HTTP-Client][writeToStreamDataBlock] too less ram! need %d\n", HTTP_TCP_BUFFER_SIZE);
            return HTTPC_ERROR_TOO_LESS_RAM;
        }
    
        return bytesWritten;
    }
    

    通过上面的讲解,博主相信大家应该有个初步了解了,希望仔细研读。

    1.3.2.12 getSize —— 获取响应数据的字节数

    函数说明:

    /**
     * 获取响应数据字节数
     * @return int 响应数据字节数
     */
    int getSize(void);
    

    注意点:

    • 对于有 Content-Length,会把Content-Length赋值给size;
    • 如果存在 Transfer-Encoding:chunked,size是通过计算响应内存长度来获得;
    1.3.2.13 errorToString —— 获取请求失败响应信息

    函数说明:

    /**
     * 根据错误码error返回具体错误信息
     * @param error 错误码
     * @return String 错误码对应的错误信息
     */
    static String errorToString(int error);
    

    错误码定义:

    /// HTTP client errors
    #define HTTPC_ERROR_CONNECTION_REFUSED  (-1)
    #define HTTPC_ERROR_SEND_HEADER_FAILED  (-2)
    #define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
    #define HTTPC_ERROR_NOT_CONNECTED       (-4)
    #define HTTPC_ERROR_CONNECTION_LOST     (-5)
    #define HTTPC_ERROR_NO_STREAM           (-6)
    #define HTTPC_ERROR_NO_HTTP_SERVER      (-7)
    #define HTTPC_ERROR_TOO_LESS_RAM        (-8)
    #define HTTPC_ERROR_ENCODING            (-9)
    #define HTTPC_ERROR_STREAM_WRITE        (-10)
    #define HTTPC_ERROR_READ_TIMEOUT        (-11)
    

    函数源码:

    /**
     * converts error code to String
     * @param error int
     * @return String
     */
    String HTTPClient::errorToString(int error)
    {
        switch(error) {
        case HTTPC_ERROR_CONNECTION_REFUSED:
            return F("connection refused");//拒绝连接,一般多是权限问题
        case HTTPC_ERROR_SEND_HEADER_FAILED:
            return F("send header failed");//请求头错误
        case HTTPC_ERROR_SEND_PAYLOAD_FAILED:
            return F("send payload failed");//发送请求数据错误
        case HTTPC_ERROR_NOT_CONNECTED:
            return F("not connected");//没有和服务器建立连接
        case HTTPC_ERROR_CONNECTION_LOST:
            return F("connection lost");//连接断开
        case HTTPC_ERROR_NO_STREAM:
            return F("no stream");
        case HTTPC_ERROR_NO_HTTP_SERVER:
            return F("no HTTP server");//没有找到Http server
        case HTTPC_ERROR_TOO_LESS_RAM:
            return F("too less ram");//内存不够用
        case HTTPC_ERROR_ENCODING:
            return F("Transfer-Encoding not supported");//不支持该传输编码
        case HTTPC_ERROR_STREAM_WRITE:
            return F("Stream write error");//写数据流失败
        case HTTPC_ERROR_READ_TIMEOUT:
            return F("read Timeout");//读取数据超时
        default:
            return String();
        }
    }
    

    1.4 实例操作

    讲了那么多的理论知识,该开始实际操作了,请看以下几个例子。

    1.4.1 获取天气请求

    源码:

    /**
     * Demo:
     *    演示Http请求天气接口信息
     * @author 单片机菜鸟
     * @date 2019/09/09
     */
    #include <ESP8266WiFi.h>
    #include <ArduinoJson.h>
    #include <ESP8266HTTPClient.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     = "xxxxx";         // XXXXXX -- 使用时请修改为当前你的 wifi ssid
    const char* AP_PSK = "xxxxxx";         // XXXXXX -- 使用时请修改为当前你的 wifi 密码
    const char* HOST = "http://api.seniverse.com";
    const char* APIKEY = "wcmquevztdy1jpca";        //API KEY
    const char* CITY = "guangzhou";
    const char* LANGUAGE = "zh-Hans";//zh-Hans 简体中文  会显示乱码
      
    const unsigned long BAUD_RATE = 115200;                   // serial connection speed
    const unsigned long HTTP_TIMEOUT = 5000;               // max respone time from server
     
    // 我们要从此网页中提取的数据的类型
    struct WeatherData {
      char city[16];//城市名称
      char weather[32];//天气介绍(多云...)
      char temp[16];//温度
      char udate[32];//更新时间
    };
     
    HTTPClient http;
    String GetUrl;
    String response;
    WeatherData weatherData;
     
    void setup() {
      // put your setup code here, to run once:
      WiFi.mode(WIFI_STA);     //设置esp8266 工作模式
      DebugBegin(BAUD_RATE);
      DebugPrint("Connecting to ");//写几句提示,哈哈
      DebugPrintln(AP_SSID);
      WiFi.begin(AP_SSID, AP_PSK);   //连接wifi
      WiFi.setAutoConnect(true);
      while (WiFi.status() != WL_CONNECTED) {
        //这个函数是wifi连接状态,返回wifi链接状态
        delay(500);
        DebugPrint(".");
      }
      DebugPrintln("");
      DebugPrintln("WiFi connected");
      DebugPrintln("IP address: " + WiFi.localIP());
     
      //拼接get请求url  博哥后面考虑看看是否可以封装一个方法来用用 不需要自己一个个拼装这个url
      GetUrl = String(HOST) + "/v3/weather/now.json?key=";
      GetUrl += APIKEY;
      GetUrl += "&location=";
      GetUrl += CITY;
      GetUrl += "&language=";
      GetUrl += LANGUAGE;
      //设置超时
      http.setTimeout(HTTP_TIMEOUT);
      //设置请求url
      http.begin(GetUrl);
      //以下为设置一些头  其实没什么用 最重要是后端服务器支持
      http.setUserAgent("esp8266");//用户代理版本
      http.setAuthorization("esp8266","boge");//用户校验信息
    }
     
    void loop() {
      //心知天气  发送http  get请求
      int httpCode = http.GET();
      if (httpCode > 0) {
          Serial.printf("[HTTP] GET... code: %d\n", httpCode);
          //判断请求是否成功
          if (httpCode == HTTP_CODE_OK) {
            //读取响应内容
            response = http.getString();
            DebugPrintln("Get the data from Internet!");
            DebugPrintln(response);
            //解析响应内容
            if (parseUserData(response, &weatherData)) {
              //打印响应内容
              printUserData(&weatherData);
            }
          }
      } else {
          Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
      }
      http.end();
      delay(1000);//每1s调用一次 
    }
      
    /**
     * @Desc 解析数据 Json解析
     * 数据格式如下:
     * {
     *    "results": [
     *        {
     *            "location": {
     *                "id": "WX4FBXXFKE4F",
     *                "name": "北京",
     *                "country": "CN",
     *                "path": "北京,北京,中国",
     *                "timezone": "Asia/Shanghai",
     *                "timezone_offset": "+08:00"
     *            },
     *            "now": {
     *                "text": "多云",
     *                "code": "4",
     *                "temperature": "23"
     *            },
     *            "last_update": "2017-09-13T09:51:00+08:00"
     *        }
     *    ]
     *}
     */
    bool parseUserData(String content, struct WeatherData* weatherData) {
    //    -- 根据我们需要解析的数据来计算JSON缓冲区最佳大小
    //   如果你使用StaticJsonBuffer时才需要
    //    const size_t BUFFER_SIZE = 1024;
    //   在堆栈上分配一个临时内存池
    //    StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
    //    -- 如果堆栈的内存池太大,使用 DynamicJsonBuffer jsonBuffer 代替
      DynamicJsonBuffer jsonBuffer;
       
      JsonObject& root = jsonBuffer.parseObject(content);
       
      if (!root.success()) {
        DebugPrintln("JSON parsing failed!");
        return false;
      }
        
      //复制我们感兴趣的字符串
      strcpy(weatherData->city, root["results"][0]["location"]["name"]);
      strcpy(weatherData->weather, root["results"][0]["now"]["text"]);
      strcpy(weatherData->temp, root["results"][0]["now"]["temperature"]);
      strcpy(weatherData->udate, root["results"][0]["last_update"]);
      //  -- 这不是强制复制,你可以使用指针,因为他们是指向“内容”缓冲区内,所以你需要确保
      //   当你读取字符串时它仍在内存中
      return true;
    }
       
    // 打印从JSON中提取的数据
    void printUserData(const struct WeatherData* weatherData) {
      DebugPrintln("Print parsed data :");
      DebugPrint("City : ");
      DebugPrint(weatherData->city);
      DebugPrint(", \t");
      DebugPrint("Weather : ");
      DebugPrint(weatherData->weather);
      DebugPrint(",\t");
      DebugPrint("Temp : ");
      DebugPrint(weatherData->temp);
      DebugPrint(" C");
      DebugPrint(",\t");
      DebugPrint("Last Updata : ");
      DebugPrint(weatherData->udate);
      DebugPrintln("\r\n");
    }
    

    源码解析:setup中配置好url,串口参数,以及httpclient,并设置了client的请求头。loop中,每个1s去请求一次get服务,把获取回来的天气信息通过json库转成具体对应的数值。

    测试结果:


    image

    1.4.2 演示响应头获取信息,仍然以上面的天气接口为例

    源码:

    /**
     * Demo:
     *    演示Http请求天气接口信息,演示响应头操作
     * @author 单片机菜鸟
     * @date 2019/09/09
     */
    #include <ESP8266WiFi.h>
    #include <ArduinoJson.h>
    #include <ESP8266HTTPClient.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 char* HOST = "http://api.seniverse.com";
    const char* APIKEY = "wcmquevztdy1jpca";        //API KEY
    const char* CITY = "guangzhou";
    const char* LANGUAGE = "zh-Hans";//zh-Hans 简体中文  会显示乱码
    const char *keys[] = {"Content-Length","Content-Type","Connection","Date"};//需要收集的响应头的信息
      
    const unsigned long BAUD_RATE = 115200;                   // serial connection speed
    const unsigned long HTTP_TIMEOUT = 5000;               // max respone time from server;
    
    HTTPClient http;
    String GetUrl;
    String response;
    
    void setup() {
      // put your setup code here, to run once:
      WiFi.mode(WIFI_STA);     //设置esp8266 工作模式
      DebugBegin(BAUD_RATE);
      DebugPrint("Connecting to ");//写几句提示,哈哈
      DebugPrintln(AP_SSID);
      WiFi.begin(AP_SSID, AP_PSK);   //连接wifi
      WiFi.setAutoConnect(true);
      while (WiFi.status() != WL_CONNECTED) {
        //这个函数是wifi连接状态,返回wifi链接状态
        delay(500);
        DebugPrint(".");
      }
      DebugPrintln("");
      DebugPrintln("WiFi connected");
      DebugPrintln("IP address: " + WiFi.localIP());
    
      //拼接get请求url  博哥后面考虑看看是否可以封装一个方法来用用 不需要自己一个个拼装这个url
      GetUrl = String(HOST) + "/v3/weather/now.json?key=";
      GetUrl += APIKEY;
      GetUrl += "&location=";
      GetUrl += CITY;
      GetUrl += "&language=";
      GetUrl += LANGUAGE;
      //设置超时
      http.setTimeout(HTTP_TIMEOUT);
      //设置请求url
      http.begin(GetUrl);
      //以下为设置一些头  其实没什么用 最重要是后端服务器支持
      http.setUserAgent("esp8266");//用户代理版本
      http.setAuthorization("esp8266","boge");//用户校验信息
      http.addHeader("myname","cainiaobo");
    
      //设置获取响应头的信息
      http.collectHeaders(keys,4);
    }
    
    void loop() {
      //心知天气  发送http  get请求
      int httpCode = http.GET();
      if (httpCode > 0) {
          Serial.printf("[HTTP] GET... code: %d\n", httpCode);
          //判断请求是否成功
          if (httpCode == HTTP_CODE_OK) {
            //读取响应内容
            response = http.getString();
            DebugPrintln("Get the data from Internet!");
            DebugPrintln(response);
            DebugPrintln(String("Content-Length:")+ http.header("Content-Length"));
            DebugPrintln(String("Content-Type:")+ http.header("Content-Type"));
            DebugPrintln(String("Connection:")+ http.header("Connection"));
            DebugPrintln(String("Date:")+ http.header("Date"));
          }
      } else {
          Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
      }
      http.end();
      delay(1000);//每1s调用一次 
    }
    

    源码解析:

    • 我们设置了获取四个请求头的信息,"Content-Length","Content-Type","Connection","Date"。然后通过header这个方法获取它们的数值。

    测试结果:


    image

    注意点:

    • 设置了请求头,但是由于接口限制,目前来说还没有发挥作用;

    1.5 总结

    这一篇章,主要讲解了Http协议的client,大家只需要记住一句话就好——Http是基于Tcp协议之上的应用层协议。

    博哥ESP8266 qq交流群:869920142

    image

    相关文章

      网友评论

        本文标题:ESP8266开发之旅 网络篇⑨ HttpClient——ESP

        本文链接:https://www.haomeiwen.com/subject/oqrdeqtx.html