HTTP Client
本篇文章我们将详细介绍如何在W55MH32芯片上面实现HTTP Client功能,并通过实战例程,为大家讲解如何向一个指定的网站提交数据。 为方便讲解,在该例程中,我们选用了一个专门测试HTTP协议的网站:httpbin.org,并实现了GET和POST这两种常用的HTTP提交数据的方法, 供大家参考。
该例程用到的其他网络协议,例如DHCP,DNS, 请参考相关章节。有关 W55MH32 的初始化过程,请参考Network install,这里将不再赘述。
HTTP协议简介
HTTP(超文本传输协议,HyperText Transfer Protocol)是一种用于分布式、协作式、超媒体信息系统的应用层协议, 基于 TCP/IP 通信协议来传递数据,是万维网(WWW)的数据通信的基础。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法,通过 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。 以上是HTTP协议的简介,如想深入了解该协议,请参考mozilla网站上的介绍:
HTTP 概述-HTTP|MDNHTTP协议特点
- 基于请求-响应模型:客户端发起请求,服务器处理后返回响应。例如,用户在浏览器输入网址时, 浏览器会向对应服务器发送HTTP请求,服务器返回网页内容。
- 无状态性: HTTP本身不保存请求之间的状态,每次请求独立。但可以通过Cookie、Session等机制实现状态保持。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求并收到客户的应答后,便立即断开连接。
HTTP应用场景
接下来,我们了解下在W55MH32上可以使用HTTP 客户端模式完成哪些操作及应用呢?
- 数据采集与上传: 将传感器采集到的数据上传到服务器中。
- 远程配置与管理:通过向服务器上请求配置文件或者管理指令,实现远程管理和控制。例如工控设备获取最新的运行策略或指令。
- 固件更新(OTA):通过向服务器请求下载最新的固件包,实现远程升级固件的功能。
- 日志和错误报告上传:定时上传系统运行日志,用于分析设备状态,或在出现异常情况时上传错误报告,方便快速定位和解决问题。
- 用户认证与授权管理:通过服务器进行交互,验证用户或设备的身份。
HTTP协议的基本工作流程
HTTP的请求-响应模型通常由以下几个步骤组成
- 建立连接:客户端与服务器之间基于TCP/IP协议建立连接。
- 发送请求:客户端向服务器发送请求,请求中包含要访问的资源的 URL、请求方法(GET、POST、PUT、DELETE 等)、 请求头(例如,Accept、User-Agent)以及可选的请求体(对于 POST 或 PUT 请求)。
- 处理请求:服务器接收到请求后,根据请求中的信息找到相应的资源,执行对应的处理操作。 这可能涉及从数据库中检索数据、生成动态内容或者简单地返回静态文件。
- 发送响应:服务器将处理后的结果封装在响应中,并将其发送回客户端。响应包含状态码(用于指示请求的成功或失败)、 响应头(例如,Content-Type、Content-Length)以及可选的响应体(例如,HTML 页面、图像数据)。
- 关闭连接:在完成请求-响应周期后,客户端和服务器之间的连接将被关闭, 除非使用了持久连接(如 HTTP/1.1 中的 keep-alive)。
HTTP请求方法
在 HTTP协议中, GET和 POST是两种常用的请求方法, 用于客户端向服务器发送数据和获取资源。
GET 方法
GET方法通常用于从服务器获取资源。它有以下特点
- 参数传递: 请求参数通过URL中的查询字符串传递,形如?key1=value1&key2=value2。
- 数据大小限制:由于参数附加在URL后,长度可能受URL长度限制(取决于浏览器和服务器设置)。
- 安全性: 数据在URL中明文显示,不适合传递敏感信息。
请求格式:
GET <Request-URI> HTTP/<Version>
<Headers>
<Blank Line>
- Request-URI:表示目标资源的路径,可能包含参数。
- Version: HTTP协议版本。
- Headers:包含元信息,例如客户端的属性、支持的格式等。
- Blank Line:空行。
POST 方法
POST方法通常用于向服务器提交数据。它有以下特点:
- 参数传递:数据放在请求体中,而不是URL中。
- 数据大小限制: POST请求的体积没有明显限制,可以传递大量数据。
- 安全性: 数据在请求体中传输,相对来说更安全。
请求格式:
POST <Request-URI> HTTP/<Version>
<Headers>
<Blank Line>
<Body>
- Request-URI: 目标资源的路径,通常是API的端点。
- Headers: 元信息,例如内容类型和长度。
- Blank Line: 空行,区分头和主体。
- Body: 数据的主体,包含客户端发送到服务器的长度。
HTTP协议响应内容
HTTP协议响应内容包含状态行、响应头以及响应体三个部分。
状态行
HTTP 状态行包含HTTP协议版本、状态码以及状态描述。
状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型。
状态码分为五类:
- 1xx(信息性状态码): 表示接收的请求正在处理。
- 2xx(成功状态码):表示请求正常处理完毕。
- 3xx(重定向状态码):需要后续操作才能完成这一请求。
- 4xx(客户端错误状态码):表示请求包含语法错误或无法完成。
- 5xx(服务器错误状态码): 服务器在处理请求的过程中发生了错误。
HTTP/1.1 200 OK
响应头
响应头则会包含内容类型、长度、编码等信息。
常见的响应头字段有:
- Content-Type: 响应内容的MIME类型,例如 text/html、application/json。
- Content-Length: 响应内容的字节长度。
- Server: 服务器信息。
- Set-Cookie: Set-Cookie:
示例:
Content-Type: text/html; charset=UTF-8
Content-Length: 3495
Server: Apache/2.4.41 (Ubuntu)
响应体
响应体包含实际的数据内容,具体形式取决于响应的类型和请求内容。例如:HTML页面内容,JSON数据,文件的二进制数据等。
如果是状态码为204 No Content 或 304 Not Modified的响应,则通常没有正文。
请注意:
响应体和响应头之间会添加一个空行来分隔内容。
HTTP请求及响应实例
GET请求示例如下:
//请求
GET /get?username=admin&password=admin HTTP/1.1
Host:httpbin.org
//响应
HTTP/1.1 200 OK
Date: Tue, 10 Dec 2024 10:41:13 GMT
Content-Type: application/json
Content-Length: 278
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {
"password": "admin",
"username": "admin"
},
"headers": {
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-67581ac9-236349c67cb21dcc24c54215"
},
"origin": "118.99.2.9",
"url": "http://httpbin.org/get?username=admin&password=admin"
}
POST请求示例如下:
//请求
POST /post HTTP/1.1
Host:httpbin.org
Content-Type:application/x-www-form-urlencode
Content-Length:29
username=admin&password=admin
//响应
HTTP/1.1 200 OK
Date: Tue, 10 Dec 2024 10:44:52 GMT
Content-Type: application/json
Content-Length: 374
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "username=admin&password=admin",
"files": {},
"form": {},
"headers": {
"Content-Length": "29",
"Content-Type": "application/x-www-form-urlencode",
"Host": "httpbin.org",
"X-Amzn-Trace-Id": "Root=1-67581ba4-744fdecf1da1bcf3180f9fa3"
},
"json": null,
"origin": "118.99.2.9",
"url": "http://httpbin.org/post"
}
实现过程
接下来,我们看看如何在W55MH32上实现HTTP客户端模式发送GET和POST请求示例。
例程中使用的是httpbin.org的服务器,它的get请求路径为httpbin.org/get,post请求路径为httpbin.org/post。
我们向httpbin.org发送请求后,它会把我们请求时提交的参数体现在响应内容中。
步骤1:通过DNS协议解析HTTP服务器的域名
在利用 HTTP 向httpbin.org服务器提交数据时,首先要与该服务器建立 TCP 链接。要知道,建立 TCP 链接是必须基于 IP 地址来进行连接操作的。 而此处的 do_dns () 所承担的任务,正是借助 DNS 协议对域名进行解析,进而获取到相应 IP 地址的过程。
if (do_dns(ethernet_buf, org_server_name, org_server_ip))
{
printf("DNS request failed.\r\n");
while (1)
{
}
}
步骤2:进行HTTP GET和POST请求组包
接下来,我们需要按前面介绍的HTTP协议的规范来组包。
本例程中,我们定义了两个函数来分别生产HTTP GET Header()和HTTP POST Header()。详细代码如下:
/**
* @brief HTTP GET : Request package combination package.
* @param pkt: Array cache for grouping packages
* @return pkt: Package length
*/
uint32_t http_get_pkt(uint8_t *pkt)
{
*pkt = 0;
// request type URL HTTP protocol version
strcat((char *)pkt, "GET /get?username=admin&password=admin HTTP/1.1\r\n");
// Host address, which can be a domain name or a specific IP address.
strcat((char *)pkt, "Host: httpbin.org\r\n");
// end
strcat((char *)pkt, "\r\n");
return strlen((char *)pkt);
}
/**
* @brief HTTP POST : Request package combination package.
* @param pkt: Array cache for grouping packages
* @return pkt: Package length
*/
uint32_t http_post_pkt(uint8_t *pkt)
{
*pkt = 0;
// request type URL HTTP protocol version
strcat((char *)pkt, "POST /post HTTP/1.1\r\n");
// Host address, which can be a domain name or a specific IP address.
strcat((char *)pkt, "Host: httpbin.org\r\n");
// Main content format
strcat((char *)pkt, "Content-Type:application/x-www-form-urlencode\r\n");
// Main content lenght
strcat((char *)pkt, "Content-Length:29\r\n");
// separator
strcat((char *)pkt, "\r\n");
// main content
strcat((char *)pkt, "username=admin&password=admin");
return strlen((char *)pkt);
}
http_get_pkt()组包的内容如下:
GET /get?username=admin&password=admin HTTP/1.1
Host: httpbin.org
http_post_pkt()组包的内容如下:
POST /post HTTP/1.1
Host: httpbin.org
Content-Type:application/x-www-form-urlencode
Content-Length:29
username=admin&password=admin
步骤3:发送HTTP 请求以及超时和响应内容处理
len = http_get_pkt(ethernet_buf);
do_http_request(SOCKET_ID, ethernet_buf, len, org_server_ip, org_port);
// Send a POST request.
len = http_post_pkt(ethernet_buf);
do_http_request(SOCKET_ID, ethernet_buf, len, org_server_ip, org_port);
do_http_request()函数为发送HTTP请求并监听响应,具体内容如下:
/**
* @brief HTTP Client get data stream test.
* @param sn: socket number
* @param buf: request message content
* @param len request message length
* @param destip: destion ip
* @param destport: destion port
* @return 0:timeout,1:Received response..
*/
uint8_t do_http_request(uint8_t sn, uint8_t *buf, uint16_t len, uint8_t *destip, uint16_t destport)
{
uint16_t local_port = 50000;
uint16_t recv_timeout = 0;
uint8_t send_flag = 0;
while (1)
{
switch (getSn_SR(sn))
{
case SOCK_INIT:
// Connect to http server.
connect(sn, destip, destport);
break;
case SOCK_ESTABLISHED:
if (send_flag == 0)
{
// send request
send(sn, buf, len);
send_flag = 1;
printf("send request:\r\n");
for (uint16_t i = 0; i < len; i++)
{
printf("%c", *(buf + i));
}
printf("\r\n");
}
// Response content processing
len = getSn_RX_RSR(sn);
if (len > 0)
{
printf("Receive response:\r\n");
while (len > 0)
{
len = recv(sn, buf, len);
for (uint16_t i = 0; i < len; i++)
{
printf("%c", *(buf + i));
}
len = getSn_RX_RSR(sn);
}
printf("\r\n");
disconnect(sn);
close(sn);
return 1;
}
else
{
recv_timeout++;
delay_ms(1000);
}
// timeout handling
if (recv_timeout > 10)
{
printf("request fail!\r\n");
disconnect(sn);
close(sn);
return 0;
}
break;
case SOCK_CLOSE_WAIT:
// If there is a request error, the server will immediately send a close request,
// so the error response content needs to be processed here.
len = getSn_RX_RSR(sn);
if (len > 0)
{
printf("Receive response:\r\n");
while (len > 0)
{
len = recv(sn, buf, len);
for (uint16_t i = 0; i < len; i++)
{
printf("%c", *(buf + i));
}
len = getSn_RX_RSR(sn);
}
printf("\r\n");
disconnect(sn);
close(sn);
return 1;
}
close(sn);
break;
case SOCK_CLOSED:
// close socket
close(sn);
// open socket
socket(sn, Sn_MR_TCP, local_port, 0x00);
break;
default:
break;
}
}
}
在该函数中,程序会执行一个TCP Client模式的状态机,具体详细讲解请看TCP Client示例章节, 当程序处于SOCK_ESTABLISHED状态时,会发送1次请求内容到服务器。接着就是监听服务器响应数据以及超时处理。
如果服务器返回异常响应,则会立即关闭链接,因此我们需要在SOCK_CLOSE_WAIT状态中处理服务器异常响应的内容。
运行结果
请注意:
因为本示例需要访问互联网,请确保W55MH32的网络环境及配置能够正常访问互联网。
烧录例程运行后,首先可以看到打印了PHY链路检测和DHCP获取网络信息,然后是DNS解析HTTP服务器域名结果,如下图所示:

接着我们发送了一次GET请求报文,然后HTTP服务器返回了响应报文。 请求和响应原文都通过串口打印出来,如下图所示:

最后,我们发送了一次POST请求报文,然后HTTP服务器返回了响应报文。 请求和响应原文都通过串口打印出来,如下图所示:

总结
本文介绍了在 W55MH32 芯片上实现 HTTP Client 功能的方法,实现向httpbin.org网站获取数据。阐述了 HTTP 协议的概念、特点、应用场景、工作流程、请求方法、响应内容,并给出请求及响应实例。以及展示了在W55MH32上的实现过程。 下一篇将讲解在该芯片上实现 HTTP Server 功能,介绍通过浏览器修改 W55MH32 网络地址信息的原理和实现步骤。敬请期待!