您现在的位置是:首页 > 正文

ESP32 Arduino (十) HTTPClient库

2024-04-01 00:10:33阅读 2

一、简介

1.前言

很多时候我们需要ESP32去访问外网的服务器获取一些网络资源,HTTP服务器是最常见的服务器,在这个时候就需要ESP32作为HTTPClient使用,HttpClient是Apache中的一个开源的项目。它实现了HTTP标准中Client端的所有功能,使用它能够很容易地进行HTTP信息的传输。

HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。

HttpClient 相比传统 JDK 自带的 URLConnection,增加了易用性和灵活性,它不仅是客户端发送 HTTP 请求变得容易,而且也方便了开发人员测试接口(基于 HTTP 协议的),即提高了开发的效率,也方便提高代码的健壮性。因此熟练掌握 HttpClient 是很重要的必修内容,掌握 HttpClient 后,相信对于 HTTP 协议的了解会更加深入。

2.URL 基本介绍

平时我们俗称的”网址“,其实就是 URL(Uniform Resource Locator),翻译为统一资源定位符,互连网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它

(1)URL 基本格式

  • URL 的标准格式如下:

协议类型:[//服务器地址[:端口号]][/资源层级 UNIX 文件路径]文件名[?查询字符串][#片段标识符]
  • URL 的完整格式如下:

协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级 UNIX 文件路径]文件名[?查询字符串][#片段标识符]

(2)URL 参数介绍

3.json解析

一般我们的http请求和响应都以json数据来进行交互,这里就先简略说下如何解析json数据,我们可以使用arduinoJson库和一个解析网站来完成这些工作

https://arduinojson.org/v6/assistant/#/step1

以我们这一篇JSON为例

{"results":[{"location":{"id":"WW7MBNP039PE","name":"泰安","country":"CN","path":"泰安,泰安,山东,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"晴","code":"0","temperature":"28"},"last_update":"2020-09-09T10:09:00+08:00"}]}

我们只需要先安装arduinoJson库,然后进入这个网站

输入需要解析的文件

然后就可以生成对于的解析代码了

关于arduinoJson的使用后面再详细讲解。

二、相关函数

使用时包含头文件:

#include <HTTPClient.h>

1. 创建对象

HTTPClient http;

2. HTTPClient对象初始化

/**
 * 解析url以获得所有参数,默认port是80端口
 * @param url String
 */
bool begin(String url);

/**
 * 解析url以获得所有参数,默认port是80端口
 * @param client : 传入一个网络连接客户端
 * @param url String
 */
bool begin(WiFiClient &client, 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 = "/");

/**
 * 设置host port 以及uri
 * @param client 传入一个网络连接客户端
 * @param host String(192.168.1.12,不需要带上http://前缀)
 * @param port uint16_t
 * @param uri  String
 * @param https bool 是否启用https
 */
bool begin(WiFiClient &client, String host, uint16_t port, String uri = "/", bool https = false);

/**
 * 设置host port 以及uri
 * @param host String(192.168.1.12,不需要带上http://前缀)
 * @param port uint16_t
 * @param uri  String
 * @param CAcert CA证书(https)
 */
bool begin(String host, uint16_t port, String uri, const char* CAcert);
bool begin(String host, uint16_t port, String uri, const char* CAcert, const char* cli_cert, const char* cli_key);

2. HTTPClient设置请求头信息

1.设置长连接 : http_client.setReuse(true);

/**
 * try to reuse the connection to the server
 * keep-alive 请求头
 * @param reuse bool
 */
void setReuse(bool reuse); // keep-alive

2.设置封装标准请求头User-Agent

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

3.设置请求头中的Authorization;

void setAuthorization(const char * user, const char * password)
void setAuthorization(const char * auth)

4.设置封装自定义请求头

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

5.设置与服务器建立连接的超时时间;

void setConnectTimeout(int32_t connectTimeout)

6.设置TCP连接的超时时间;

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

7.设置http连接重定向处理方式:

void setFollowRedirects(followRedirects_t follow)

可选值如下:

HTTPC_DISABLE_FOLLOW_REDIRECTS 默认值,不进行重定向
HTTPC_STRICT_FOLLOW_REDIRECTS 只有使用GET或HEAD方法的请求将被重定向
HTTPC_FORCE_FOLLOW_REDIRECTS 进行重定向

8.单个请求重定向最大次数,默认为10;

void setRedirectLimit(uint16_t limit)

9.重新设置url

bool setURL(const String &url)

4. 发起HTTP请求

前言

HTTP 请求方法一般分为 8 种,它们分别是

  • GET 获取资源:GET 方法用来请求访问已被 URI 识别的资源。指定的资源经服务器端解析后返回响应内容。也就是说,如果请求的资源是文本,那就保持原样返回;

  • POST 传输实体:虽然 GET 方法也可以传输主体信息,但是便于区分,我们一般不用 GET 传输实体信息,反而使用 POST 传输实体信息。

  • PUT 传输文件,PUT 方法用来传输文件。就像 FTP 协议的文件上传一样,要求在请求报文的主体中包含文件内容,然后保存到请求 URI 指定的位置。

我们一般最常用的方法也就是 GET 方法、 POST 方法和PUT方法,其他方法暂时不用了解。

1.向服务器发起GET请求,并返回状态码或错误代码;

int GET()

2.向服务器发起PATCH请求,并返回状态码或错误代码;

int PATCH(uint8_t * payload, size_t size)
int PATCH(String payload)

payload为请求的body中的内容;

3.向服务器发起POST请求,并返回状态码或错误代码;

payloadw为请求的body中的内容;

int POST(uint8_t * payload, size_t size)
int POST(String payload)

4.向服务器发起PUT请求,并返回状态码或错误代码;

int PUT(uint8_t * payload, size_t size)
int PUT(String payload)

payloadw为请求的body中的内容;

5.向服务器发起请求,并返回状态码或错误代码;

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

5.获取http状态

1.返回当前是否建立连接;

bool connected(void)

2.设置需要获取的响应头键名与键值对数量;

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

RequestArgument定义如下:

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

注意点:

这个方法收集的headerKeys会在响应数据处理函数中应用到;

3.返回响应头键值对数量;(需要先设置collectHeaders)

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

4.判断是否存在某一个响应头;(需要先设置collectHeaders)

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

5.获取具体响应头参数值;(需要先设置collectHeaders)

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

6.获取第index个响应头参数值

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

7.获取第i个响应头名字

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

8.返回响应头中的重定向地址;

const String &getLocation(void)

5. 获取http响应内容

1.返回响应正文数据字节数;

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

2.返回响应正文数据流;

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

3.将响应正文数据流想入其它流对象;

如果成功则返回写入字节数,否则返回错误代码;

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

4.返回响应正文数据;

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

5.获取错误代码并且以字符串形式返回;

static String HTTPClient::errorToString(int error)

6. 关闭http

/**
 * end
 * called after the payload is handled
 */
void HTTPClient::end(void);

7.获取请求失败响应信息

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

三、代码讲解

1.HTTPClient库原理步骤

  1. HTTPClient从原理角度来说使用并不复杂,主要步骤如下:

  1. 引用库 #include <HTTPClient.h> ;

  1. 连上网;

  1. 声明 HTTPClient 对象;

  1. 使用 begin() 方法准备要访问的 url ;

  1. 填写请求头内容(如果需要的话);

  1. 发起 GET 、 POST 等请求,并接收返回的状态码;

  1. 根据需求读取响应头或响应正文内容;

  1. 使用 end() 方法结束当前连接;

2.示例

示例一

#include <WiFi.h>
#include <HTTPClient.h>

const char *ssid = "********";    //你的网络名称
const char *password = "********"; //你的网络密码

void setup()
{
  Serial.begin(115200);
  Serial.println();

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected!");

  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop()
{
  HTTPClient http; // 声明HTTPClient对象

  http.begin("http://example.com/index.html"); // 准备启用连接

  int httpCode = http.GET(); // 发起GET请求

  if (httpCode > 0) // 如果状态码大于0说明请求过程无异常
  {
    if (httpCode == HTTP_CODE_OK) // 请求被服务器正常响应,等同于httpCode == 200
    {
      String payload = http.getString(); // 读取服务器返回的响应正文数据
                                         // 如果正文数据很多该方法会占用很大的内存
      Serial.println(payload);
    }
  }
  else
  {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end(); // 结束当前连接

  delay(10000);
}

示例二

#include <WiFi.h>
#include <HTTPClient.h>

const char *ssid = "********";    //你的网络名称
const char *password = "********"; //你的网络密码

void setup()
{
  Serial.begin(115200);
  Serial.println();

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected!");

  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop()
{
  HTTPClient http; // 声明HTTPClient对象

  http.begin("http://example.com/index.html"); // 准备启用连接

  int httpCode = http.GET(); // 发起GET请求

  if (httpCode > 0) // 如果状态码大于0说明请求过程无异常
  {
    if (httpCode == HTTP_CODE_OK) // 请求被服务器正常响应,等同于httpCode == 200
    {
      uint8_t buff[128] = {0};

      int len = http.getSize(); // 读取响应正文数据字节数,如果返回-1是因为响应头中没有Content-Length属性

      WiFiClient *stream = http.getStreamPtr(); // 获取响应正文数据流指针

      while (http.connected() && (len > 0 || len == -1)) // 当前已连接并且有数据可读
      {
        size_t size = stream->available(); // 获取数据流中可用字节数
        if (size)
        {
          int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); // 读取数据到buff

          Serial.write(buff, c);
          if (len > 0)
          {
            len -= c;
          }
        }
        delay(1);
      }
    }
  }
  else
  {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end(); // 结束当前连接

  delay(10000);
}

示例三

#include <WiFi.h>
#include <HTTPClient.h>

const char *ssid = "********";    //你的网络名称
const char *password = "********"; //你的网络密码

void setup()
{
  Serial.begin(115200);
  Serial.println();

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi connected!");

  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

const char *headerKeys[] = {"Content-Type", "Content-Length"};

void loop()
{
  HTTPClient http; // 声明HTTPClient对象

  http.begin("http://example.com/index.html"); // 准备启用连接

  http.collectHeaders(headerKeys, 2); // 准备需要接收的响应头内容

  int httpCode = http.GET(); // 发起GET请求

  if (httpCode > 0) // 如果状态码大于0说明请求过程无异常
  {
    if (httpCode == HTTP_CODE_OK) // 请求被服务器正常响应,等同于httpCode == 200
    {
      Serial.print("Content-Type = ");
      Serial.println(http.header("Content-Type"));

      Serial.print("Content-Length = ");
      Serial.println(http.header("Content-Length"));
    }
  }
  else
  {
    Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
  }

  http.end(); // 结束当前连接

  delay(10000);
}

示例四:获取bilibili粉丝数

  1. 首先是API

http://api.bilibili.com/x/relation/stat?vmid=314597633
  1. 会返回一行json数据

{"code":0,"message":"0","ttl":1,"data":{"mid":314597633,"following":303,"whisper":0,"black":0,"follower":237}}

3.测试代码如下:

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

const char *ssid = "oudafa";
const char *pwd  = "00000000";

void setup() {
    Serial.begin(115200);

    //connect to WiFi
    Serial.printf("Connecting to %s ", ssid);
    WiFi.begin(ssid, pwd);
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
    }
    Serial.println("Connected!");

    // local ip
    Serial.println(WiFi.localIP().toString());

    // HTTPClient init
    HTTPClient http;
    String url = "http://api.bilibili.com/x/relation/stat?vmid=314597633";
    http.begin(url);

    // start get
    int http_code = http.GET();

    // handle http code
    if (http_code !=  HTTP_CODE_OK) {
        // get fail.
        Serial.printf("GET fail, http code is %s\n", http.errorToString(http_code).c_str());
        return;
    }

    // http response
    String response = http.getString();
    Serial.printf("response:[%s]\n", response.c_str());

    // extract follower
    int pos = response.indexOf("follower");
    String follower_str = response.substring(pos + 10, response.length() - 2);
    int follower = atoi(follower_str.c_str());
    Serial.printf("your follower:%d\n", follower);

    // close http
    http.end();
}

void loop() {
    delay(1000);
}

示例五:获取天气

1. API

获取的天气我使用心知天气,有免费版API可用。

接口API为:

https://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=beijing&language=zh-Hans&unit=c
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char *ssid = "oudafa";
const char *pwd  = "00000000";
const String xz_api_key = "your api key";
const String city = "shenzhen";
DynamicJsonDocument doc(1024);

/***************************************************
{
    "results":
    [{
        "location":
        {
            "id":"WS10730EM8EV",
            "name":"深圳",
            "country":"CN",
            "path":"深圳,深圳,广东,中国",
            "timezone":"Asia/Shanghai",
            "timezone_offset":"+08:00"
        },
        "now":
        {
            "text":"阴",
            "code":"9",
            "temperature":"23"
        },
        "last_update":"2022-03-15T19:23:46+08:00"
    }]
}
*****************************************************/

void setup() {
    Serial.begin(115200);

    //connect to WiFi
    Serial.printf("Connecting to %s ", ssid);
    WiFi.begin(ssid, pwd);
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
    }
    Serial.println("Connected!");

    // local ip
    Serial.println(WiFi.localIP().toString());
}

int get_weather(void)
{
    HTTPClient http;
    String url = "https://api.seniverse.com/v3/weather/now.json?key=" + xz_api_key + "&location=" + city + "&language=zh-Hans&unit=c";

    // HTTPClient init
    http.begin(url);

    // start get
    int http_code = http.GET();

    // handle http code
    if (http_code !=  HTTP_CODE_OK) {
        // get fail.
        Serial.printf("GET fail, http code is %s\n", http.errorToString(http_code).c_str());
        return -1;
    }

    // http response
    String response = http.getString();
    Serial.printf("response:[%s]\n", response.c_str());

    // extract weather
    deserializeJson(doc, response);
    JsonObject root = doc.as<JsonObject>();
    JsonArray results = root["results"];
    // location
    JsonObject location = results[0]["location"];
    const char *path = location["path"];
    Serial.println(path);
    // now
    JsonObject now = results[0]["now"];
    const char *text = now["text"];
    const char *temperature = now["temperature"];
    Serial.println(text);
    Serial.println(temperature);
    // last_update
    const char *last_update = results[0]["last_update"];
    Serial.println(last_update);

    // close http
    http.end();

    return 0;

}

void loop() {
    get_weather();
    delay(60000);
}

返回结果示例:

{
    "results":
    [{
        "location":
        {
            "id":"WS10730EM8EV",
            "name":"深圳",
            "country":"CN",
            "path":"深圳,深圳,广东,中国",
            "timezone":"Asia/Shanghai",
            "timezone_offset":"+08:00"
        },
        "now":
        {
            "text":"阴",
            "code":"9",
            "temperature":"23"
        },
        "last_update":"2022-03-15T19:23:46+08:00"
    }]
}

网站文章

  • 打印机文件服务器主机,打印机服务器主机名称是什么原因

    打印机文件服务器主机,打印机服务器主机名称是什么原因

    打印机服务器主机名称是什么原因 内容精选换一换成为进阶Linux大佬的第一步一、操作系统1、操作系统为接口的示意图2、不同领域的主流操作系统桌面操作系统服务器操作系统嵌入式操作系统移动设备操作系统 3...

    2024-04-01 00:10:20
  • Vue组件化学习之scoped

    Vue组件化学习之scoped

    简介主要介绍scoped的作用。先弄一个案例:main.js://引入vue依赖import Vue from 'vue'//引入组件Appimport App from './App.vue'// ...

    2024-04-01 00:10:14
  • JS判断是否为base64字符串&如何转换为图片src格式

    JS判断是否为base64字符串&如何转换为图片src格式

    JS判断是否为base64字符串&如何转换为图片src格式需求背景 :如何判断后端给返回的 字符串 是否为 base-64 位 呢 ?以及如果判断为是的话,如何给它进行转换为 img 标签可使用的那种 src 格式 呢 ?

    2024-04-01 00:09:44
  • 给我一个java 策略模式和工厂模式一起实用的例子

    策略模式可以用来实现一组可互换的算法,例如在不同环境下使用不同的算法。工厂模式可以用来创建一组相关的对象,例如在一个网站中创建不同类型的用户。一个实用的例子就是使用策略模式和工厂模式来创建一个网站,该网站根据不同的环境使用不同的算法来创建不同类型的用户。 ...

    2024-04-01 00:09:39
  • rmit计算机科学选课,RMIT最强最全的IT Master选课指南,“鼠”于你的高分秘籍!...

    rmit计算机科学选课,RMIT最强最全的IT Master选课指南,“鼠”于你的高分秘籍!...

    RMIT的IT课程设计十分地人性化,每个学期的课程根据自身知识的储备的不同会有多个方向的选择。但是,如果对自己的了解不够深入,或者对课程的英语描述不清楚的同学很容易踩雷。所以RMIT的学霸学长根据自己...

    2024-04-01 00:09:31
  • sql 字段中的百分号

    SQL模糊查询的时候,如果字段中有%,我们又希望查找出所有有%的字段,使用select * from table where name like &#39;%%%&#39;类似的语句是不行的。这时候我们可以使用select * from table where name like &#39;%[%]%。

    2024-04-01 00:09:06
  • Failed to execute goal org.apache.maven.pluginsmaven-install-plugin2.4install (default-install)

    Failed to execute goal org.apache.maven.pluginsmaven-install-plugin2.4install (default-install)

    项目打包的时候,出现下面的报错信息: Failed to execute goal org.apache.maven.plugins:maven-install-plugin:2.4:install ...

    2024-04-01 00:08:53
  • docker理论与安装

    Docker的理论与虚拟机上的安装

    2024-04-01 00:08:27
  • Vue使用js实现时间格式化 filters过滤器

    time | dataFormat(&#39;yyyy.MM.dd hh:mm:ss&#39;) 时间的格式化过滤器 filters: { dataFormat (value, fmt) { let getDate = new Date(value) let o = { &#39;M+&#39;: getDate.getMonth() + 1, &#39;d+&#39;: ge...

    2024-04-01 00:08:14
  • linux服务器内存快要溢出问题排查与解决

    linux服务器内存快要溢出问题排查与解决

    今天在工作的过程中,发现有一台服务器的内存达到了90%以上,所以立即对该台服务器进行了问题排查。 一、 问题排查思路 利用top命令查看进程信息(整体来看) top //使用top命令用于实时显示 process 的动态信息 按大写的M,进行排序内存大的排在前面,按小写的c命令,查看运行命令的绝对路径 查看情况如下图所示: 我们先来看一下java进程占用系统内存高的排查方法 (1) 定...

    2024-04-01 00:08:07