
Connexus的头(第三部分):掌握HTTP请求头的使用方法
引言
本文是这一系列文章的延续,我们将构建一个名为Connexus的库。在第一篇文章中,我们了解了WebRequest函数的基本功能,理解了它的每个参数,并创建了一个示例代码,展示了这个函数的使用及其难点。在本文中,我们将探讨HTTP通信中请求头的重要性与实用性,以及这些元素在现代网络中用于不同目的的方式。
HTTP消息的结构,无论是响应还是请求,都由两个基本元素组成,我们将深入探讨:请求头和请求体。它们各自在通信过程中发挥作用,确保数据以有序、高效和安全的方式传输。
首先,让我们简要回顾HTTP请求和响应结构的工作原理。
HTTP请求的结构
HTTP请求通常遵循以下格式:
HTTP方法 | URL | HTTP版本 请求头 请求体(可选)
- HTTP方法:定义请求的意图(GET、POST、PUT、DELETE等)。
- URL:标识被请求的资源。(我们在上一篇文章中更详细地讨论了这一点)
- HTTP版本:指定正在使用的协议版本。
- 请求头:请求的元数据,例如内容类型、认证等。
- 请求体:请求的内容,通常出现在POST或PUT等方法中。
HTTP响应的结构
HTTP响应遵循类似的结构:
HTTP版本 | 状态码 | 状态消息 请求头 正文(可选)
- 状态码:指示结果(200 OK、404 Not Found、500 Internal Server Error等)。
- 请求头:关于返回内容的信息,例如大小、数据类型等。
- 请求体:响应的实际内容,例如页面的HTML或JSON数据。在我们的例子中,更常见的是接收JSON数据,但请注意,某些API可能会返回HTML。
在上一篇文章中,我们深入探讨了URL的格式,分别理解了每个元素,并将它们组合在一起形成完整的地址。在本文中,我们将深入探讨HTTP请求的头信息。它的用途是什么?如何使用它?可能的值有哪些,等等。
请求头
首先,让我们了解什么是请求头。在HTTP协议中,请求头是与请求或响应一起发送的附加数据集。HTTP头在客户端与服务器的通信中具有基本功能。它们的主要目的是提供关于请求的详细信息,这些信息不是URL或消息正文的直接部分。它们有助于控制通信流程,并提供上下文,以确保服务器正确地解释和处理数据。这些请求头信息有助于让服务器和客户端更好地理解请求或响应的上下文,例如内容类型、缓存、认证等。换句话说,它们作为元数据,告知服务器如何处理请求,以及客户端如何解释响应。
让我们提及HTTP请求头的一些主要功能:
- 认证:头信息最常见的用途之一是认证客户端,以便服务器知道谁在发送请求以及他们是否有权访问信息。例如,授权请求头发送一个令牌或凭据,服务器接收后可以用来在处理请求之前验证客户端。
- 缓存控制:像缓存控制这样的请求头允许客户端和服务器配置数据如何缓存,这可以避免客户端向服务器或服务器向其他服务发送不必要的额外请求。缓存数据可以存储在客户端、代理或其他中间点。
- 内容类型规范:内容类型头允许客户端告知服务器正在发送或接收的数据类型。通常使用JSON、XML或HTML等格式。这确保了通信双方都知道如何正确解释数据。
- 内容协商:客户端可以使用接受头告知服务器哪些响应格式是可以接受的,例如application/json(服务器将以JSON格式发送数据)或text/html(服务器将以HTML格式发送)。这允许服务器以客户端准备接收的格式发送响应。最常用的格式是JSON,这将是我们的重点,但还有其他支持的格式。
- 安全性:像严格传输安全这样的请求头有助于加强HTTPS的使用(HTTPS与HTTP相同,但包含额外的网络安全层。请求、响应、头部、正文、URL和其他格式完全相同,这就是为什么建议始终使用HTTPS)。其他头信息,如跨域资源共享(CORS),定义哪些域可以访问API的资源,从而增强安全性。通过这种方式,服务器正在筛选它发送信息的对象,只向预先定义的域发送信息,这样除了该域之外的任何人都无法访问数据。
- 速率限制:一些服务返回像X-RateLimit-Limit和X-RateLimit-Reaming这样的头,以告知客户端在给定时间段内允许的请求数量。这可以防止大量请求使服务器过载。
因此,请求头在HTTP通信中发挥着关键作用,提供了对请求和响应应该如何处理的控制、清晰度和安全性。
了解一些可能的值
让我们看看最常见的请求头的可能值。HTTP请求头非常灵活,可以根据客户端和服务器之间的通信需要进行定制。理解头信息可以包含的值取决于通信的上下文。以下是一些最常见的头及其可能值的示例:
- 授权:用于从客户端向服务器发送凭据或身份验证令牌,允许访问受保护的资源。根据使用的身份验证方法,它可以有不同的值。有几种身份验证方法,让我们看看最常用的一些:
-
承载令牌:这是最常用的格式之一,特别是在使用基于令牌的身份验证的现代API中。这个值通常是一个JWT(JSON Web Token)令牌,客户端在登录或向身份验证服务器进行身份验证后收到。
授权:Bearer <my_token_jwt>
<my_token_jwt>的值是身份验证令牌本身,通常是一个Base64编码的长字符串。这个令牌包含用户授权信息,并且有效期有限。
-
Basic:这个值使用HTTP基本身份验证,用户名和密码被Base64编码并直接在请求头中发送。这种方法不太安全,因为凭据可以很容易地被解码,如果没有HTTPS,不应使用。
授权:Basic base64(用户名:密码)
base64(用户名:密码)的值是用户名:密码对的Base64编码。尽管这种方法简单,但除非在加密连接上使用,否则容易受到攻击。
-
摘要:比基本更安全的方法,摘要使用用户凭据的哈希而不是直接发送凭据。随着OAuth和JWT的兴起,它在当今不太常见,但仍然可以在某些API中找到。
Authorization: Digest username="admin", realm="example", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", uri="/dir/index.html", response="6629fae49393a05397450978507c4ef1"
在这里,值包含几个字段,如用户名、nonce(一次性使用的数字)、realm(身份验证范围)和哈希加密的响应。
-
- 内容类型:这个头信息定义了请求或响应正文中存在的内容类型。让我们看看一些最常用的可能值:
-
application/json:在处理API时,这是最常见的值,因为JSON是一种轻量级且易于读写的数据传输格式。使用示例:
内容类型:application/json
-
application/xml:在JSON之前,XML(可扩展标记语言)被广泛使用,仍然在一些遗留系统或旧API中使用。但大多数当前的API支持JSON格式,所以目前不用担心XML。
内容类型:application/xml
-
multipart/form-data:这个值用于发送混合数据,特别是当客户端需要上传文件或表单数据时。目前这还不是我们的重点,但知道这种可能性是好的。
-
text/html:当需要发送或接收的内容为HTML时使用。这个值在网页请求和响应中很常见。
Content-Type: text/html
-
- User-Agent:客户端用于标识自身的头信息。它通常包含发起请求的设备类型、浏览器和操作系统的相关信息。虽然这里没有具体的规则,但有一些被广泛使用的标准:
-
Web Browsers:浏览器发送的值通常包含浏览器名称和版本的详细信息,以及操作系统的信息。
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
-
APIs or Applications:应用程序可以使用自定义值,例如:
User-Agent: Connexus/1.0 (MetaTrader 5 Terminal)
这可以让服务器知道请求来自特定的应用程序,便于在需要时进行调试或监控。
-
- Accept:客户端用于告知服务器它可以接受作为响应的内容类型。这个头信息的可能值与Content-Type类似,让我们来看一些示例:
-
application/json, application/xml, text/html:这些值我们在Content-Type部分已经见过。
-
image/png, image/jpeg, image/webp:当客户端期望接收图像时使用这些值。例如,在提供图形数据的API中,如缩略图或生成的图表。或者简单地接收图像,如用户的头像、网站的标志等。
Accept: image/png, image/jpeg
在这里,客户端告知它接受PNG和JPEG图像。
-
/:这个值表示客户端接受任何类型的响应内容。当客户端对特定格式没有偏好,或者准备好处理任何类型的响应时使用。
-
如何组织请求头?
现在我们已经了解了请求头的用途和一些可能的值,让我们来看看它是如何组织的。头信息以键值对的形式组织,并插入到请求中。尽管某些操作需要某些头,但大多数头信息是可选的,取决于应用程序的需求。HTTP请求的基本头信息示例:
GET /api/resource HTTP/1.1 Host: example.com Authorization: Bearer token123 Content-Type: application/json User-Agent: Connexus/1.0 (MetaTrader 5 Terminal)
如本例所示,我们有3个头:
- Authorization: 提供我们的访问令牌“token123”。
- Content-Type: 提供有关数据发送方式的信息,我们使用JSON。
- User-Agent: 向服务器提供信息,以便服务器知道如何处理请求。在本例中,我们使用“Connexus/1.0 (MetaTrader 5 Terminal)”。
它们向服务器提供信息,以便服务器知道如何处理请求。这只是一个简单的例子,我们很快将更详细地讨论哪些头信息最常用、可能的值以及每个头信息的用途。
动手实践代码
我们已经了解了请求头的工作原理、用途以及如何使用它们,现在让我们进入实践部分。还记得httpbin吗?这是一个免费服务,它简单地充当一个镜像,我们发送给它的任何内容都会返回给我们,我们将用它来检查我们发送的头信息,以及终端本身是否自动添加了任何头信息。为此,我将在Experts/Connexus/Test/TestHeader.mq5文件夹中创建一个名为TestHeader.mq5的文件。让我们创建一个不带任何头信息的POST请求,看看它会如何响应我们:
#include <Connexus2/Data/Json.mqh> #include <Connexus2/URL/URL.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- URL CURL url; url.Parse("https://httpbin.org"); url.Path("post"); //--- Data to be sent string method = "POST"; char body_send[]; string headers_send; //--- Data that will be received char body_receive[]; string headers_receive; //--- Send request int status_code = WebRequest(method,url.FullUrl(),headers_send,5000,body_send,body_receive,headers_receive); //--- Show response Print("Respose: ",CharArrayToString(body_receive)); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
当执行这段代码后,我们将得到如下响应:
Respose: { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt,en;q=0.5", "Content-Length": "0", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "MetaTrader 5 Terminal/5.4518 (Windows NT 11.0.22631; x64)", "X-Amzn-Trace-Id": "Root=1-66feb3d9-50de44d019af8b0c1058436b" }, "json": null, "origin": "189.74.63.39", "url": "https://httpbin.org/post" }
注意“headers”的值,它包含另一个JSON对象,其中有一些由终端自动定义的头部值。请记住,在我们的请求中,我们既没有在头部也没有在正文中发送任何数据。让我们来理解这些自动发送的头部:
-
Accept:正如我们之前看到的,它告诉服务器客户端愿意接受作为响应的内容类型。在这种情况下,值为 /,正如之前所见,这意味着客户端接受任何类型的响应内容。
-
Accept-Encoding:指定客户端可以接受的内容编码类型。编码用于 压缩数据,以节省网络带宽。
- gzip:这是一种用于减小发送响应大小的压缩格式。
- deflate:这是另一种与gzip相似但算法上有一些技术差异的数据压缩方式。
-
Accept-Language:这个头信息告诉服务器客户端偏好的语言,但服务器必须支持多种语言。它帮助服务器向用户提供最合适的语言内容。
- pt:客户端希望以葡萄牙语接收响应。
- en;q=0.5:这里,en代表英语,q=0.5是一个“质量因子”(范围从0到1),它告诉语言的相对优先级。q=1.0表示最大偏好,而q=0.5表示客户端接受英语,但更倾向于葡萄牙语。
-
Content-Length:表示请求正文的大小(以字节为单位)。在这种情况下,值为0,这意味着没有内容,正如我们所说,我们不在请求正文中发送数据。
-
Content-Type:告知服务器正在发送到服务器的数据类型。在这种情况下,值为application/x-www-form-urlencoded表示数据以URL编码表单格式发送。我不确定MetaTrader 5为什么默认设置这种格式。
-
Host:指定请求发送到的服务器名称。这是必要的,以便服务器知道哪个域名或IP地址被请求,尤其是如果它在一个IP地址上托管多个域名。
-
User-Agent:这是一个标识发起请求的 HTTP客户端(在这种情况下是MetaTrader 5)的字符串。它包含有关所使用的软件和操作系统的详细信息。
-
X-Amzn-Trace-Id:X-Amzn-Trace-Id头由AWS(Amazon Web Services)服务用于在其分布式基础设施中追踪请求。它有助于识别和调试在Amazon云中运行的应用程序中的问题。
- Root=1-66feb3d9-50de44d019af8b0c1058436b:这个值表示一个唯一的跟踪ID,可用于识别特定的事务。它对诊断和性能监控很有用。
- Meaning:这个标识符由AWS系统自动分配,可用于跟踪请求在Amazon基础设施内不同服务之间的路径。
- Common usage:这个头字段由AWS服务内部用于收集跟踪和监控信息。它在微服务架构中特别有用,用于诊断瓶颈和延迟问题。
我相信MetaTrader 5自动添加这个是为了跟踪终端发出的请求,用于平台的诊断或帮助修复错误。
现在我们已经看到了MetaTrader 5默认添加的头信息,让我们更改其中一些。请记住,头信息中不能有两个相同的键,也就是说,你不能使用Content-Type: application/json和Content-Type: application/x-www-form-urlencoded。因此,如果我们定义了Content-Type的值,它将覆盖旧值。让我们更改一些数据,为以下头信息定义其他值:
- Content-Type: Content-Type: application/json
- User-Agent: Connexus/1.0 (MetaTrader 5 Terminal)
让我们将这些值添加到“headers_send”变量中,这是一个字符串。记得添加“\n”以分隔各个头信息。以下是修改后的代码:
#include <Connexus2/Data/Json.mqh> #include <Connexus2/URL/URL.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- URL CURL url; url.Parse("https://httpbin.org"); url.Path("post"); //--- Data to be sent string method = "POST"; char body_send[]; //--- Headers that will be sent separated by "\n" string headers_send = "User-Agent: Connexus/1.0 (MetaTrader 5 Terminal)\nContent-Type: application/json"; //--- Data that will be received char body_receive[]; string headers_receive; //--- Send request int status_code = WebRequest(method,url.FullUrl(),headers_send,5000,body_send,body_receive,headers_receive); //--- Show response Print("Respose: ",CharArrayToString(body_receive)); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
执行上述代码后我们将得到如下结果:
Respose: { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt,en;q=0.5", "Content-Length": "0", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Connexus/1.0 (MetaTrader 5 Terminal)", "X-Amzn-Trace-Id": "Root=1-66feb90f-037374b15f220d3e28e1cb32" }, "json": null, "origin": "189.74.63.39", "url": "https://httpbin.org/post" }
注意,我们将“User-Agent”和“Content-Type”的值改为我们自己定义的值。现在我们已经有了一个发送自定义请求头的简单请求示例,让我们将这个功能添加到我们的库中。我们的目标是创建一个用于处理头部的类。这个类应该易于使用,具有简单直观的接口,并且能够轻松地添加、删除或更新头信息。
创建HttpHeaders类
让我们在Includes文件夹内的Connexus文件夹中创建一个新文件夹。这个新文件夹将被命名为Headers,在这个新文件夹中,我们创建一个名为HttpHeaders.mqh的新文件。最终的结构将如下所示:
//+------------------------------------------------------------------+ //| Header.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| class : CHttpHeader | //| | //| [PROPERTY] | //| Name : CHttpHeader | //| Heritage : No heritage | //| Description : Responsible for organizing and storing the headers | //| of a request. | //| | //+------------------------------------------------------------------+ class CHttpHeader { public: CHttpHeader(void); ~CHttpHeader(void); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHttpHeader::CHttpHeader(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CHttpHeader::~CHttpHeader(void) { } //+------------------------------------------------------------------+
为了存储这些头部,我们将使用一个JSON对象,其中JSON的键将是头部的键,同样地,JSON的值将是头部的值。为此,我们将导入JSON类,并在类中创建一个名为m_headers的新实例。
//+------------------------------------------------------------------+ //| Include the file CJson class | //+------------------------------------------------------------------+ #include "../Data/Json.mqh" //+------------------------------------------------------------------+ //| class : CHttpHeader | //| | //| [PROPERTY] | //| Name : CHttpHeader | //| Heritage : No heritage | //| Description : Responsible for organizing and storing the headers | //| of a request. | //| | //+------------------------------------------------------------------+ class CHttpHeader { private: CJson m_headers; public: CHttpHeader(void); ~CHttpHeader(void); }; //+------------------------------------------------------------------+
JSON对象已准备好存储数据,下一步将是定义这个类应该有哪些方法。最初,我们将创建以下方法:
- Add(string key, string value):向HTTP请求中添加一个新的头部,如果该头部已经存在,则更新它。
- Get(string key):根据头部的名称返回特定头部的值。
- Remove(string key):移除特定的头部。
- Has(string key):检查是否存在具有指定键的头部。
- Clear():从请求中移除所有头部。
- Count():返回头部的数量。
让我们先将这些更简单的方法添加到类中
//+------------------------------------------------------------------+ //| class : CHttpHeader | //| | //| [PROPERTY] | //| Name : CHttpHeader | //| Heritage : No heritage | //| Description : Responsible for organizing and storing the headers | //| of a request. | //| | //+------------------------------------------------------------------+ class CHttpHeader { private: CJson m_headers; public: CHttpHeader(void); ~CHttpHeader(void); //--- Functions to manage headers void Add(string key, string value); // Adds a new header to the HTTP request or updates it if it already exists string Get(string key); // Returns the value of a specific header, given its name. void Remove(string key); // Removes a specific header. bool Has(string key); // Checks whether a header with the specified key is present. void Clear(void); // Removes all headers from the request. int Count(void); // Returns the number of headers. }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CHttpHeader::CHttpHeader(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CHttpHeader::~CHttpHeader(void) { } //+------------------------------------------------------------------+ //| Adds a new header to the HTTP request or updates it if it already| //| exists | //+------------------------------------------------------------------+ void CHttpHeader::Add(string key,string value) { m_headers[key] = value; } //+------------------------------------------------------------------+ //| Returns the value of a specific header, given its name. | //+------------------------------------------------------------------+ string CHttpHeader::Get(string key) { return(m_headers[key].ToString()); } //+------------------------------------------------------------------+ //| Removes a specific header. | //+------------------------------------------------------------------+ void CHttpHeader::Remove(string key) { m_headers.Remove(key); } //+------------------------------------------------------------------+ //| Checks whether a header with the specified key is present. | //+------------------------------------------------------------------+ bool CHttpHeader::Has(string key) { return(m_headers.FindKey(key) != NULL); } //+------------------------------------------------------------------+ //| Removes all headers from the request. | //+------------------------------------------------------------------+ void CHttpHeader::Clear(void) { m_headers.Clear(); } //+------------------------------------------------------------------+ //| Returns the number of headers. | //+------------------------------------------------------------------+ int CHttpHeader::Count(void) { return(m_headers.Size()); } //+------------------------------------------------------------------+
现在我们已经添加了最简单的方法,让我们添加最后两个方法,它们是这个类的核心,分别是:
- Serialize():以字符串格式返回所有头部,准备好在HTTP请求中发送。
- Parse(string headers):将包含头部的字符串(通常在HTTP响应中收到)转换为可以在类中使用的格式。
//+------------------------------------------------------------------+ //| class : CHttpHeader | //| | //| [PROPERTY] | //| Name : CHttpHeader | //| Heritage : No heritage | //| Description : Responsible for organizing and storing the headers | //| of a request. | //| | //+------------------------------------------------------------------+ class CHttpHeader { private: CJson m_headers; public: CHttpHeader(void); ~CHttpHeader(void); //--- Auxiliary methods string Serialize(void); // Returns all headers in string format, ready to be sent in an HTTP request. bool Parse(string headers); // Converts a string containing headers (usually received in an HTTP response) into a format usable by the class. }; //+------------------------------------------------------------------+ //| Returns all headers in string format, ready to be sent in an HTTP| //| request. | //+------------------------------------------------------------------+ string CHttpHeader::Serialize(void) { //--- String with the result string headers; //--- Get size int size = this.Count(); for(int i=0;i<size;i++) { //--- Adds the header to the string in the format: "key: value" headers += m_headers[i].m_key + ": " + m_headers[i].ToString(); //--- If it's not the last time it adds "\n" at the end of the string if(i != size -1) { headers += "\n"; } } //--- Return result return(headers); } //+------------------------------------------------------------------+ //| Converts a string containing headers (usually received in an HTTP| //| response) into a format usable by the class. | //+------------------------------------------------------------------+ bool CHttpHeader::Parse(string headers) { //--- Array to store the key value sets string params[]; //--- Separate the string, using the "\n" character as a separator int size = StringSplit(headers,StringGetCharacter("\n",0),params); for(int i=0;i<size;i++) { //--- With the header separated using ": " int pos = StringFind(params[i],": "); if(pos >= 0) { //--- Get key and value string key = StringSubstr(params[i],0,pos); string value = StringSubstr(params[i],pos+2); //--- Clear value StringTrimRight(value); //--- Add in json this.Add(key,value); } } return(true); } //+------------------------------------------------------------------+
测试
为了进行类的测试,我将使用我们在文章开头创建的同一个文件,TestHeader.mq5。导入HttpHeader文件,然后创建CHttpHeader类的一个实例,将数据传递给该类。然后我使用Serialize()函数将其格式化为字符串。
#include <Connexus/Data/Json.mqh> #include <Connexus/URL/URL.mqh> #include <Connexus/Header/HttpHeader.mqh> //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- URL CURL url; url.Parse("https://httpbin.org"); url.Path("post"); //--- Data to be sent string method = "POST"; char body_send[]; //--- Headers CHttpHeader headers_send; headers_send.Add("User-Agent","Connexus/1.0 (MetaTrader 5 Terminal)"); headers_send.Add("Content-Type","application/json"); //--- Data that will be received char body_receive[]; string headers_receive; //--- Send request int status_code = WebRequest(method,url.FullUrl(),headers_send.Serialize(),5000,body_send,body_receive,headers_receive); //--- Show response Print("Respose: ",CharArrayToString(body_receive)); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
在终端中执行代码时,我们得到了相同的响应:
Respose: { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt,en;q=0.5", "Content-Length": "0", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Connexus/1.0 (MetaTrader 5 Terminal)", "X-Amzn-Trace-Id": "Root=1-66fed891-0d6adb5334becd71123795c9" }, "json": null, "origin": "189.74.63.39", "url": "https://httpbin.org/post" }
结论
简而言之,HTTP请求头就像是你在课堂上传递的小纸条,让服务器知道该如何处理你的请求。它们可以用于认证、设置内容类型、指导缓存,以及做更多事情。没有它们,HTTP通信就会像在点咖啡时没有说明咖啡的大小、糖量或牛奶类型一样混乱。目前,我们创建的类图如下所示:
既然你已经理解了头部以及它们的重要性,那么现在是时候来探讨一些更有趣的东西了:请求正文。在下一篇文章中,我们将深入探讨HTTP通信的核心——你想要发送给服务器的实际内容。毕竟,发送一封没有内容的信是没有意义的,对吧?
做好准备吧,下一章将探讨如何优雅且有效地在请求体中封装这些信息。期待与您再见!
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16043
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


