ESP8266開發之旅 網絡篇⑨ HttpClient——ESP8266HTTPClient庫的使用

授人以魚不如授人以漁,目的不是爲了教會你具體項目開發,而是學會學習的能力。但願你們分享給你周邊須要的朋友或者同窗,說不定大神成長之路有博哥的奠定石。。。html

QQ技術互動交流羣:ESP8266&32 物聯網開發 羣號622368884,不喜勿噴git

1、你若是想學基於Arduino的ESP8266開發技術

1、基礎篇github

  1. ESP8266開發之旅 基礎篇① 走進ESP8266的世界
  2. ESP8266開發之旅 基礎篇② 如何安裝ESP8266的Arduino開發環境
  3. ESP8266開發之旅 基礎篇③ ESP8266與Arduino的開發說明
  4. ESP8266開發之旅 基礎篇④ ESP8266與EEPROM
  5. ESP8266開發之旅 基礎篇⑤ ESP8266 SPI通訊和I2C通訊
  6. ESP8266開發之旅 基礎篇⑥ Ticker——ESP8266定時庫

2、網絡篇web

  1. ESP8266開發之旅 網絡篇① 認識一下Arduino Core For ESP8266
  2. ESP8266開發之旅 網絡篇② ESP8266 工做模式與ESP8266WiFi庫
  3. ESP8266開發之旅 網絡篇③ Soft-AP——ESP8266WiFiAP庫的使用
  4. ESP8266開發之旅 網絡篇④ Station——ESP8266WiFiSTA庫的使用
  5. ESP8266開發之旅 網絡篇⑤ Scan WiFi——ESP8266WiFiScan庫的使用
  6. ESP8266開發之旅 網絡篇⑥ ESP8266WiFiGeneric——基礎庫
  7. ESP8266開發之旅 網絡篇⑦ TCP Server & TCP Client
  8. ESP8266開發之旅 網絡篇⑧ SmartConfig——一鍵配網
  9. ESP8266開發之旅 網絡篇⑨ HttpClient——ESP8266HTTPClient庫的使用
  10. ESP8266開發之旅 網絡篇⑩ UDP服務
  11. ESP8266開發之旅 網絡篇⑪ WebServer——ESP8266WebServer庫的使用
  12. ESP8266開發之旅 網絡篇⑫ 域名服務——ESP8266mDNS庫
  13. ESP8266開發之旅 網絡篇⑬ SPIFFS——ESP8266 Flash文件系統
  14. ESP8266開發之旅 網絡篇⑭ web配網
  15. ESP8266開發之旅 網絡篇⑮ 真正的域名服務——DNSServer
  16. ESP8266開發之旅 網絡篇⑯ 無線更新——OTA固件更新

3、應用篇json

  1. ESP8266開發之旅 應用篇① 局域網應用 ——炫酷RGB彩燈
  2. ESP8266開發之旅 應用篇② OLED顯示天氣屏
  3. ESP8266開發之旅 應用篇③ 簡易版WiFi小車

4、高級篇後端

  1. ESP8266開發之旅 進階篇① 代碼優化 —— ESP8266內存管理
  2. ESP8266開發之旅 進階篇② 閒聊Arduino IDE For ESP8266配置
  3. ESP8266開發之旅 進階篇③ 閒聊 ESP8266 Flash
  4. ESP8266開發之旅 進階篇④ 常見問題 —— 解決困擾
  5. ESP8266開發之旅 進階篇⑤ 代碼規範 —— 像寫文章同樣優美
  6. ESP8266開發之旅 進階篇⑥ ESP-specific APIs說明

1. 前言

    在前面章節的博客中,博主介紹了ESP8266WiFi庫 Tcp client的用法,並模擬了Http請求。可是,能夠看出經過WiFiClient模擬Http請求,咱們須要本身拼裝Http請求協議,稍微不當心就很容易拼接錯誤。
    那麼有沒有針對Http請求操做的庫呢?答案確定是有的,這就是博主本篇須要跟你們講述的知識——ESP8266HTTPClient庫。
    請注意,ESP8266HTTPClient庫不屬於ESP8266WiFi庫的一部分,因此須要引入api

#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

2. 簡述Http協議

    在講述ESP8266HTTPClient庫以前,爲了更好的講解它的使用,博主給你們略講一下Http協議,更加深刻的瞭解請自行查閱資料。瀏覽器

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就能用。

2.2 HTTP特色

  1. 簡單快速:客戶端向服務端請求服務時,只須要傳送請求方法和路徑。HTTP協議簡單,使得HTTP服務器的程序規模小,於是通訊速度快;
  2. 靈活:HTTP容許傳輸任意類型的數據對象,正在傳輸的類型由Content-Type加以標記。
  3. 鏈接問題
  • HTTP0.9和1.0使用非持續鏈接:限制每次鏈接都只處理一個請求,服務端處理完客戶的請求,並收到客戶的應答後,即斷開鏈接;
  • HTTP1.1使用持續鏈接:沒必要爲每一個web對象建立一個新鏈接,一個鏈接能夠傳送多個對象,節省傳輸時間;
  1. 無狀態:HTTP協議是無狀態。對於事務處理沒有記憶能力,若是須要處理前面信息,則必須重傳,這樣可能致使每次鏈接傳送的數據量增大。

2.3 HTTP工做流程

一次HTTP操做稱爲一個事務,工做流程可分爲4步:

  1. 首先客戶端 client與服務端 server創建鏈接。
  2. 創建鏈接後,客戶端發送一個請求給服務端,請求方法的格式:統一資源標識符(URL)、HTTP協議版本號、請求頭、請求內容等;
  3. 服務端接收到請求後,給予相應的響應信息,其格式爲:狀態行(包括協議版本、成功或者失敗代碼)、服務器信息、實體信息等;
  4. 客戶端接收到服務端返回的信息,經過瀏覽器顯示在用戶的顯示屏上,而後客戶端與服務端斷開鏈接;

以上四步驟,只要其中一步出現錯誤,那麼就會產生錯誤信息返回給客戶端。

2.4 HTTP請求

    客戶端發送一個HTTP請求到服務器,請求信息包括如下格式:

  • 請求行(request line)
  • 請求頭部(header)
  • 空行 (empty line)
  • 請求數據 (request body)

image

    請求行以一個方法符號開頭,以空格分開,後面跟着請求的URI和協議的版本。

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. 第四部分:請求數據也叫主體,能夠添加任意的其餘數據。
  • 這個例子的請求數據爲空。

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. 第四部分:請求數據,第八行。

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部分爲響應正文。

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 //服務器當前不能處理客戶端的請求,一段時間後可能恢復正常

3. ESP8266HTTPClient庫

    在1.2中初略講述了Http協議,相信讀者應該有初步認識,那麼接下來咱們就能夠開始進入ESP8266HTTPClient庫了。
    老規矩,先上一個博主總結的百度腦圖:
image

整體上,根據功能能夠把方法分爲兩大類:

  • 跟http請求相關方法
  • 跟http響應相關方法

    有興趣看源碼的讀者請看 ESP8266HTTPClient.cpp

3.1 http請求方法

    http請求方法又能夠有更好的細分。

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;
  • 至於用哪種,就看需求

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

3.1.3 setUserAgent —— 封裝標準請求頭User-Agent

函數說明:

/**
 * set User Agent
 * User Agent請求頭:使得服務器可以識別客戶使用的操做系統及版本、CPU 類型、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語言、瀏覽器插件等。
 * @param userAgent const char *
 */
void setUserAgent(const String& userAgent);

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的標準請求頭還不止這些,請自行查閱資料。

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

}

3.1.6 GET 請求

函數說明:

/**
 * 發送一個get請求
 * @return http 狀態碼
 */
int GET();

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

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

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

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

3.1.11 setTimeout —— 設置請求超時

函數說明:

/**
 * 請求超時時間配置 ms爲單位
 * @param timeout unsigned int
 */
void setTimeout(uint16_t timeout);

注意點:

  • 默認的timeout爲5000ms,也就是5s;

3.1.12 useHTTP10 —— http協議版本

函數說明:

/**
 * http協議版本
 * @param usehttp10 true表示用http1.0,默認是false,用http1.1
 */
void useHTTP10(bool usehttp10 = true);

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狀況下不會斷開鏈接,只會狀況接收緩衝區;

3.2 http響應方法

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會在響應數據處理函數中應用到;

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(),那就會默認返回空字符串;

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(),那就會默認返回空字符串;

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(),那就會默認返回空字符串;

3.2.5 headers() —— 獲取收集響應頭個數

函數說明:

/**
 * 獲取收集響應頭個數
 * @return count int
 */
int headers();                     // get header count

源碼說明:

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

注意點:

  • 若是沒有調用collectHeaders(),那就會默認返回0;

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;

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

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,因此擁有了二者的方法。

3.2.9 getStream —— 獲取響應數據的流

函數說明:

/**
 * 獲取響應數據的流
 * @return WiFiClient& tcp響應數據的流
 */
WiFiClient& getStream(void);

3.2.10 getStreamPtr —— 獲取響應數據的流

函數說明:

/**
 * 獲取響應數據的流
 * @return WiFiClient& tcp響應數據的流
 */
WiFiClient* getStreamPtr(void);

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

經過上面的講解,博主相信你們應該有個初步瞭解了,但願仔細研讀。

3.2.12 getSize —— 獲取響應數據的字節數

函數說明:

/**
 * 獲取響應數據字節數
 * @return int 響應數據字節數
 */
int getSize(void);

注意點:

  • 對於有 Content-Length,會把Content-Length賦值給size;
  • 若是存在 Transfer-Encoding:chunked,size是經過計算響應內存長度來得到;

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

4. 實例操做

講了那麼多的理論知識,該開始實際操做了,請看如下幾個例子。

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

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

注意點:

  • 設置了請求頭,可是因爲接口限制,目前來講尚未發揮做用;

5. 總結

這一篇章,主要講解了Http協議的client,你們只須要記住一句話就好——Http是基於Tcp協議之上的應用層協議。

相關文章
相關標籤/搜索