Connexus的头(第三部分):掌握HTTP请求头的使用方法·综合运用
HTTP 头容器的增删查清实现
在 MT5 里做 AIGC 行情接口调用时,CHttpHeader 这个类负责把请求头塞进 WebRequest。它的底层用一个键值映射 m_headers 来存,所有操作都围绕这张表转。 析构函数直接留空,说明头信息不持有需要手动释放的系统资源,跟随对象生命周期走就行。 Add 方法用 m_headers[key] = value 直接写映射:键存在就覆盖,不存在就新建。这意味着同名的头只会保留最后一次写入的值,重复 Add 不会堆叠。 Get 返回 m_headers[key].ToString(),注意如果键不存在,映射默认构造出的空值会被转成空串返回,不会抛错。 Remove 调 m_headers.Remove(key) 删单条;Has 用 FindKey(key) != NULL 判断存在性,比直接取更稳,避免误建空记录。 Clear 清空整张表,Count 返回 m_headers.Size()——这两个在循环发请求前很有用:比如先 Clear 再 Add 固定头,用 Count 确认实际发出几条。外汇与贵金属接口调用属高风险操作,头错可能导致请求被拒或返回脏数据。
class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpHeader::~CHttpHeader(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adds a new header to the HTTP request or updates it if it already| class=class="str">"cmt">//| exists | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpHeader::Add(class="type">class="kw">string key,class="type">class="kw">string value) { m_headers[key] = value; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns the value of a specific header, given its name. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CHttpHeader::Get(class="type">class="kw">string key) { class="kw">return(m_headers[key].ToString()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Removes a specific header. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpHeader::Remove(class="type">class="kw">string key) { m_headers.Remove(key); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Checks whether a header with the specified key is present. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpHeader::Has(class="type">class="kw">string key) { class="kw">return(m_headers.FindKey(key) != NULL); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Removes all headers from the request. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpHeader::Clear(class="type">void) { m_headers.Clear(); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns the number of headers. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CHttpHeader::Count(class="type">void) { class="kw">return(m_headers.Size()); }
「用 CHttpHeader 把请求头压成可发字符串」
在 MT5 里做 HTTP 交互时,请求头不能散着传,得聚成一个类来管理。CHttpHeader 不继承任何基类,内部只持有一个 CJson 实例 m_headers,专门存 key-value 形式的头字段。 它的 Serialize() 方法负责把内部头拼成发送格式。逻辑很直白:先取 this.Count() 得到头数量 size,从 i=0 循环到 size-1,每次把 m_headers[i].m_key 和对应值用「: 」连起来;只要不是最后一条,就在末尾补一个换行符 "\n"。最终返回拼好的整段字符串,可直接塞进 WebRequest 的 headers 参数。 Parse(string headers) 则走反向路径,把响应里收到的头字符串拆回类内能用的结构。下面这段是 Serialize 的原文实现,逐行看一遍就能照抄: class CHttpHeader 私有成员只有一个 CJson m_headers,公有接口暴露构造、析构、Serialize 和 Parse。Serialize 开头声明结果串 headers,用 int size = this.Count() 拿头总数;for 循环里 headers += m_headers[i].m_key + ": " + m_headers[i].ToString() 做单行拼接,if(i != size-1) 时补 headers += "\n",最后 return(headers)。 开 MT5 新建个脚本把这段类丢进去,构造后手动 m_headers 加两条(比如 "Accept: application/json"),调 Serialize 打印,验证输出是否正好是两行带换行、末行无多余 \n。外汇与贵金属行情接口调用波动大、高风险,头格式错一处就可能拿到空响应。
class=class="str">"cmt">//| Name : CHttpHeader | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : Responsible for organizing and storing the headers | class=class="str">"cmt">//| of a request. | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpHeader { class="kw">private: CJson m_headers; class="kw">public: CHttpHeader(class="type">void); ~CHttpHeader(class="type">void); class=class="str">"cmt">//--- Auxiliary methods class="type">class="kw">string Serialize(class="type">void); class=class="str">"cmt">// Returns all headers in class="type">class="kw">string format, ready to be sent in an HTTP request. class="type">bool Parse(class="type">class="kw">string headers); class=class="str">"cmt">// Converts a class="type">class="kw">string containing headers(usually received in an HTTP response) into a format usable by the class. }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Returns all headers in class="type">class="kw">string format, ready to be sent in an HTTP| class=class="str">"cmt">//| request. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CHttpHeader::Serialize(class="type">void) { class=class="str">"cmt">//--- String with the result class="type">class="kw">string headers; class=class="str">"cmt">//--- Get size class="type">int size = this.Count(); for(class="type">int i=class="num">0;i<size;i++) { class=class="str">"cmt">//--- Adds the header to the class="type">class="kw">string in the format: "key: value" headers += m_headers[i].m_key + ": " + m_headers[i].ToString(); class=class="str">"cmt">//--- If it&class="macro">#x27;s not the last time it adds "\n" at the end of the class="type">class="kw">string if(i != size -class="num">1) { headers += "\n"; } } class=class="str">"cmt">//--- Return result class="kw">return(headers); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Converts a class="type">class="kw">string containing headers(usually received in an HTTP| class=class="str">"cmt">//| response) into a format usable by the class. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpHeader::Parse(class="type">class="kw">string headers) { class=class="str">"cmt">//--- Array to store the key value sets
◍ 把换行头文件拆成键值对
在 MT5 里解析外部传入的多行头信息时,常见做法是按换行符切分,再逐行找冒号位置提取键与值。下面这段逻辑把 headers 字符串用 \n 拆成数组,循环里用 StringFind 定位 : ,成功才继续。
切出来的 value 尾部常带空格,直接 StringTrimRight 清掉再写进 JSON 对象,能避免后续比对失效。实际跑下来,若头有 6 行且都带 : ,循环 6 次即可建完映射,少一次判断就可能丢字段。
别把正态当圣经:这种解析只认 : 双字符分隔,若上游头用了单冒号或全角冒号,pos 会返回 -1,整行被静默跳过,调参前先确认源格式。
class="type">class="kw">string params[]; class=class="str">"cmt">//--- Separate the class="type">class="kw">string, class="kw">using the "\n" character as a separator class="type">int size = StringSplit(headers,StringGetCharacter("\n",class="num">0),params); for(class="type">int i=class="num">0;i<size;i++) { class=class="str">"cmt">//--- With the header separated class="kw">using ": " class="type">int pos = StringFind(params[i],": "); if(pos >= class="num">0) { class=class="str">"cmt">//--- Get key and value class="type">class="kw">string key = StringSubstr(params[i],class="num">0,pos); class="type">class="kw">string value = StringSubstr(params[i],pos+class="num">2); class=class="str">"cmt">//--- Clear value StringTrimRight(value); class=class="str">"cmt">//--- Add in json this.Add(key,value); } } class="kw">return(true); }
用 httpbin 验证 HttpHeader 序列化
要确认 CHttpHeader 类的封装是否靠谱,直接拿文章开头那个 TestHeader.mq5 改一改就能跑。引入 HttpHeader 模块后建一个实例,把 User-Agent 和 Content-Type 塞进去,再调 Serialize() 转成字符串交给 WebRequest,比手写头文件省事得多。 下面这段在 MT5 终端里实跑,目标地址用 httpbin.org/post 做回显。注意 WebRequest 的超时设了 5000 毫秒,空 body 发 POST,返回里能看见服务端确实收到了我们加的两行头:"Content-Type": "application/json" 和 "User-Agent": "Connexus/1.0 (MetaTrader 5 Terminal)"。 回显数据里 X-Amzn-Trace-Id 是亚马逊边缘节点给的,每次请求都变;origin 字段打出的是客户端出口 IP(示例里是 189.74.63.39),说明请求真从本地终端出去了,不是模拟。外汇或贵金属 EA 若依赖外部 API 拉报价,这类头校验失败会导致 WebRequest 被静默拒收,风险在于连接层而非行情本身。
class="macro">#include <Connexus/Data/Json.mqh> class="macro">#include <Connexus/URL/URL.mqh> class="macro">#include <Connexus/Header/HttpHeader.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- URL CURL url; url.Parse("https:class=class="str">"cmt">//httpbin.org"); url.Path("post"); class=class="str">"cmt">//--- Data to be sent class="type">class="kw">string method = "POST"; class="type">char body_send[]; class=class="str">"cmt">//--- Headers CHttpHeader headers_send; headers_send.Add("User-Agent","Connexus/class="num">1.0 (MetaTrader class="num">5 Terminal)"); headers_send.Add("Content-Type","application/json"); class=class="str">"cmt">//--- Data that will be received class="type">char body_receive[]; class="type">class="kw">string headers_receive; class=class="str">"cmt">//--- Send request class="type">int status_code = WebRequest(method,url.FullUrl(),headers_send.Serialize(),class="num">5000,body_send,body_receive,headers_receive); class=class="str">"cmt">//--- Show response Print("Respose: ",CharArrayToString(body_receive)); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ Respose: { "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt,en;q=class="num">0.5", "Content-Length": "class="num">0", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "Connexus/class="num">1.0 (MetaTrader class="num">5 Terminal)", "X-Amzn-Trace-Id": "Root=class="num">1-66fed891-0d6adb5334becd71123795c9" }, "json": null, "origin": "class="num">189.74.class="num">63.39", "url": "https:class=class="str">"cmt">//httpbin.org/post" }
「最后说句实在话」
把 HTTP 请求头想成点单时写清楚的杯型、糖度和奶种,服务器才不至于给你端错东西;认证、内容类型、缓存指令都靠这几行字段兜住,缺了它们 MT5 向外发请求基本就是盲发。 下一阶段真正有意思的是请求正文——也就是你实际要塞进 POST 里的 payload。在 MT5 里用 WebRequest 发带 body 的请求前,先拿本篇的 header 封装类把 Content-Type 和鉴权头调通,否则服务端大概率直接拒收。 外汇与贵金属接口调用涉及实时行情和杠杆敞口,属于高风险操作,任何封装方案都先在策略测试器里跑通再上实盘。