使用HTTP通过WiFi传输数据



HTTP(超文本传输协议)是最常见的通信形式之一,使用ESP32,我们可以使用HTTP请求与任何Web服务器交互。让我们在本节中了解如何操作。

关于HTTP请求的简要说明

HTTP请求发生在客户端和服务器之间。顾名思义,服务器根据请求向客户端“提供”信息。Web服务器通常提供网页。例如,当您在互联网浏览器中输入https://www.linkedin.com/login时,您的PC或笔记本电脑充当客户端,并向托管linkedin.com的服务器请求与/login地址对应的页面。您将收到一个HTML页面作为返回,然后由您的浏览器显示。

HTTP遵循请求-响应模型,这意味着通信始终由客户端发起。服务器不能无缘无故地与任何客户端对话,也不能与任何客户端启动通信。通信必须始终由客户端以请求的形式发起,服务器只能响应该请求。服务器的响应包含状态代码(记得404吗?这是一个状态代码),以及(如果适用)请求的内容。所有状态代码的列表可以在这里找到 这里

那么,服务器如何识别HTTP请求呢?通过请求的结构。HTTP请求遵循一个固定的结构,该结构包含三个部分

  • 请求行后跟回车换行符(CRLF = \r\n)

  • 零个或多个标题行,后跟CRLF和一个空行,再次后跟CRLF

  • 可选正文

典型的HTTP请求如下所示

POST / HTTP/1.1         //Request line, containing request method (POST in this case)
Host: www.example.com   //Headers
                        //Empty line between headers
key1=value1&key2=value2   //Body	

服务器响应如下所示:

HTTP/1.1 200 OK                     //Response line; 200 is the status code
Date: Mon, 23 May 2005 22:38:34 GMT //Headers
Content-Type: text/html; charset=UTF-8
Content-Length: 155
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f−1b6−3e1cb03b"
Accept-Ranges: bytes
Connection: close
                                    //Empty line between headers and body
<html>						
  <head>
    <title>An Example Page</title>
  </head>
  <body>
    <p>Hello World, this is a very simple HTML document.</p>
  </body>
</html>

事实上,TutorialsPoint本身上有一个关于HTTP请求结构的非常好的教程。它还向您介绍了各种请求方法(GET、POST、PUT等)。在本节中,我们将关注GET和POST方法。

GET请求将所有参数都以请求URL中键值对的形式包含在内。例如,如果使用GET而不是POST发送上面的相同示例请求,它将如下所示:

GET /test/demo_form.php?key1=value1&key2=value2 HTTP/1.1   //Request line
Host: www.example.com                                     //Headers	
                                                          //No need for a body

POST请求(正如您现在可能已经猜到的那样)将参数包含在正文中,而不是URL中。GET和POST之间还有其他一些区别,您可以在这里阅读。但关键是,您将使用POST与服务器共享敏感信息,例如密码。

代码演练

在本节中,我们将从头开始编写我们的HTTP请求。有一些库(如httpClient)专门用于处理ESP32 HTTP请求,这些库负责构建HTTP请求,但我们将自己构建请求。这给我们带来了更大的灵活性。在本教程中,我们将限制使用ESP32客户端模式。ESP32也可以使用HTTP服务器模式,但这留给您去探索。

我们将使用httpbin.org作为我们的服务器。它基本上是为测试您的HTTP请求而构建的。您可以使用此服务器测试GET、POST和各种其他方法。参见 这里

代码可以在 GitHub 上找到

我们首先包含WiFi库。

#include <WiFi.h>

接下来,我们将定义一些常量。对于HTTP,使用的端口是80。这是标准的。类似地,我们对HTTPS使用443,对FTP使用21,对DNS使用53,等等。这些是保留的端口号。

const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";

const char* server = "httpbin.org";
const int port = 80;

最后,我们创建我们的WiFiClient对象。

WiFiClient client

在setup中,我们只需使用提供的凭据以station模式连接到WiFi。

void setup() {
   Serial.begin(115200);
   WiFi.mode(WIFI_STA);          //The WiFi is in station mode. The other is the softAP mode
   WiFi.begin(ssid, password);
   while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
   }
   Serial.println("");  Serial.print("WiFi connected to: "); Serial.println(ssid);  Serial.println("IP address: ");  Serial.println(WiFi.localIP());
   delay(2000);
}

loop在这里变得很重要。HTTP请求就在那里执行。我们首先读取ESP32的芯片ID。我们将把它作为参数与我们的姓名一起发送给服务器。我们将使用这些参数构建HTTP请求的主体。

void loop() {
   int  conn;
   int chip_id = ESP.getEfuseMac();;
   Serial.printf("  Flash Chip id = %08X\t", chip_id);
   Serial.println();
   Serial.println();
   String body = "ChipId=" + String(chip_id) + "&SentBy=" + "your_name";
   int body_len = body.length();

注意SentBy字段之前的&&在HTTP请求中用作不同键值对的分隔符。接下来,我们连接到服务器。

Serial.println(".....");
Serial.println(); Serial.print("For sending parameters, connecting to ");      Serial.println(server);
conn = client.connect(server, port);

POST请求

如果我们的连接成功,client.connect()将返回1。我们在发出请求之前检查这一点。

if (conn == 1)  {
   Serial.println(); Serial.print("Sending Parameters...");
   //Request
   client.println("POST /post HTTP/1.1");
   //Headers
   client.print("Host: "); client.println(server);
   client.println("Content-Type: application/x−www−form−urlencoded");
   client.print("Content-Length: "); client.println(body_len);
   client.println("Connection: Close");
   client.println();
   //Body
   client.println(body);
   client.println();

   //Wait for server response
   while (client.available() == 0);

   //Print Server Response
   while (client.available()) {
      char c = client.read();
      Serial.write(c);
   }
} else {
   client.stop();
   Serial.println("Connection Failed");
}

如您所见,我们使用client.print()client.println()发送我们的请求行。请求、标头和正文通过注释清楚地表明。在请求行中,POST /post HTTP/1.1等效于POST http://httpbin.org/post HTTP/1.1。由于我们已经在client.connect(server,port)中提到了服务器,因此/post指的是服务器/post URL。

特别是对于POST请求,Content-Length标头非常重要。如果没有它,许多服务器会假设内容长度为0,这意味着没有正文。Content-Type已保留为application/x−www−form−urlencoded,因为我们的正文表示表单数据。在典型的表单提交中,您将拥有像Name、Address等键以及相应的值。您可以拥有其他几种内容类型。有关完整列表,请参阅 这里

Connection: Close标头告诉服务器在处理请求后关闭连接。如果您希望在请求处理后保持连接,则可以替代地发送Connection: Keep-Alive

这些只是我们可以包含的一些标头。HTTP标头的完整列表可以在 这里 找到。

现在,httpbin.org/post URL通常只是回显我们的正文。一个示例响应如下所示:

HTTP/1.1 200 OK
Date: Sat, 21 Nov 2020 16:25:47 GMT
Content−Type: application/json
Content−Length: 402
Connection: close
Server: gunicorn/19.9.0
Access−Control−Allow−Origin: *
Access−Control−Allow−Credentials: true
{
   "args": {}, 
   "data": "", 
   "files": {}, 
   "form": {
      "ChipId": "1780326616", 
      "SentBy": "Yash"
   }, 
   "headers": {
      "Content−Length": "34", 
      "Content−Type": "application/x−www−form−urlencoded", 
      "Host": "httpbin.org", 
      "X-Amzn−Trace−Id": "Root=1−5fb93f8b−574bfb57002c108a1d7958bb"
   }, 
   "json": null, 
   "origin": "183.87.63.113", 
   "url": "http://httpbin.org/post"
}
Post Request Response

如您所见,“form”字段中回显了POST正文的内容。您应该在串口监视器上看到类似于上面的内容。另请注意URL字段。它清楚地表明请求行中的/post地址被解释为http://httpbin.org/post。

最后,我们将等待5秒钟,然后结束循环,从而再次发出请求。

  delay(5000);
}

GET请求

此时,您可能想知道,将此POST请求转换为GET请求需要进行哪些更改。实际上,这很简单。首先,您将调用/get地址而不是/post。然后,您将在问号(?)后将正文的内容附加到URL。最后,您将方法替换为GET。此外,不再需要Content-Length和Content−Type标头,因为您的正文为空。因此,您的请求块将如下所示:

if (conn == 1) {
   String path = String("/get") + String("?") +body;
   Serial.println(); Serial.print("Sending Parameters...");
   //Request
   client.println("GET "+path+" HTTP/1.1");
   //Headers
   client.print("Host: "); client.println(server);
   client.println("Connection: Close");
   client.println();
   //No Body

   //Wait for server response
   while (client.available() == 0);

   //Print Server Response
   while (client.available()) {
      char c = client.read();
      Serial.write(c);
   }
} else {
   client.stop();
   Serial.println("Connection Failed");
}

相应的响应将如下所示:

HTTP/1.1 200 OK
Date: Tue, 17 Nov 2020 18:05:34 GMT
Content-Type: application/json
Content-Length: 497
Connection: close
Server: gunicorn/19.9.0
Access-Control−Allow−Origin: *
Access-Control-Allow-Credentials: true

{
   "args": {
      "ChipID": "3F:A0:A1:77:0D:84", 
      "SentBy": "Yash"
   }, 
   "headers": {
      "Accept": "*/*", 
      "Accept-Encoding": "deflate, gzip", 
      "Host": "httpbin.org", 
      "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", 
      "X−Amzn−Trace−Id": "Root=1−5fb410ee−3630963b0b7980c959c34038"
   }, 
   "origin": "206.189.180.4", 
   "url": "https://httpbin.org/get?ChipID=3F:A0:A1:77:0D:84&SentBy=Yash"
}
GET request response

如您所见,发送到服务器的参数现在返回在args字段中,因为它们作为URL中的参数发送。

恭喜!!您已成功使用ESP32发送HTTP请求。

参考文献

广告