Connexus中的正文(第四部分):添加HTTP请求正文·进阶篇
(2/3)· 头部只是信封,正文才是信件——本篇带你把POST/PUT的真实数据塞进Connexus
不少人在MQL5里调WebRequest时,只配了URL和头部就等服务器回数据,POST出去却收到400。问题往往出在正文:要么没传,要么格式不对,服务器直接拒收。把正文当可选附件,是这套通信里最隐蔽的坑。
◍ 用 WebRequest 把订单结构推到外部接口
在 MT5 里想让 EA 把下单意图发给外部系统,核心就是先把结构体序列化成 char 数组再走 WebRequest。下面这段实测向 httpbin.org/post 发了一个 EURUSD 的 BUY 请求,返回里能看到服务端正确解析出了 price=1.09223、sl=1.09213、tp=1.09233、volume=0.01。 注意序列化后尾部多一个字节,代码里用 ArrayRemove 把最后一位干掉,否则 Content-Length 会变成 103 且服务端可能收脏数据。Header 里 User-Agent 和 Content-Type 必须显式 Add,不然默认头可能让外部接口拒收。 WebRequest 的超时参数给的是 5000 毫秒,回测环境不支持网络调用,只能在实盘或模拟盘终端里验证。外汇和贵金属品种波动剧烈,这类外发动作仅作信号中转,不预示任何方向,实盘推送失败可能造成漏单,属高风险操作。
body["sl"] = class="num">1.09213; class="type">char body_send[]; StringToCharArray(body.Serialize(),body_send,class="num">0,WHOLE_ARRAY,CP_UTF8); ArrayRemove(body_send,ArraySize(body_send)-class="num">1); class=class="str">"cmt">//--- Headers that will be sent separated by "\n" 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": "{\"type\":\"BUY\",\"symbol\":\"EURUSD\",\"price\":class="num">1.09223000,\"volume\":class="num">0.01000000,\"tp\":class="num">1.09233000,\"sl\":class="num">1.09213000}", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Accept-Language": "pt,en;q=class="num">0.5", "Content-Length": "class="num">103", "Content-Type": "text/plain", "Host": "httpbin.org", "User-Agent": "Connexus/class="num">1.0 (MetaTrader class="num">5 Terminal)", "X-Amzn-Trace-Id": "Root=class="num">1-67081b9c-3def4b1527d04edc1511cc6b" }, "json": { "price": class="num">1.09223, "sl": class="num">1.09213, "symbol": "EURUSD", "tp": class="num">1.09233, "type": "BUY", "volume": class="num">0.01 }, "origin": "class="num">189.74.class="num">63.39", "url": "https:class=class="str">"cmt">//httpbin.org/post" }
「给请求正文写个收纳类」
在 Connexus 库里处理 HTTP 请求正文,最省事的做法是单独封一个 CHttpBody 类,文件落在 Include/Connexus/Header/HttpBody.mqh。它内部只靠两个私有成员撑起来:m_body 用字符数组存正文本体,m_codepage 记编码,默认就是 UTF-8。 对外暴露的方法覆盖增删查全链路。写数据有 AddString 塞文本、AddJson 接 CJson 对象(内部转格式化串)、AddBinary 直接灌字节数组;Clear 一键清空重来;读的时候能 GetAsString、GetAsJson 或 GetAsChar 按场景拿。大小与编码用 GetSize、GetCodePage、SetCodePage 控。 这些方法最长的也没超过三行,但别小看这点代码量。实际接进库里之后,调用层能砍掉一大截重复拼装逻辑,后期维护也只盯这一处。 下面这段是类骨架刚建好时的样子,只挂了构造和析构,具体方法体后续往里填。注意版权头里的链接只是原文件自带标记,开 MT5 自己写的时候可删。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| HttpBody.mqh | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class : CHttpBody | class=class="str">"cmt">//| | class=class="str">"cmt">//| [PROPERTY] | class=class="str">"cmt">//| Name : CHttpBody | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : Responsible for organizing and storing the body of | class=class="str">"cmt">//| a request. | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpBody { class="kw">public: CHttpBody(class="type">void); ~CHttpBody(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+
请求体容器的空壳与字节数组
在 MT5 的 EA 或脚本里做 HTTP 请求时,先把请求体当成一个纯粹的字节容器来管理会更干净。下面这段类定义只搭了骨架:构造和析构函数都是空的,说明对象生成和销毁阶段不分配也不释放任何资源,真正的负载留给后续方法往 m_body 里塞数据。 类内部有两个私有成员:m_body 是 char 类型动态数组,负责以字节形式存请求体原文;m_codepage 是 uint,记着编码页(比如 CP_UTF8=65001),发请求前不显式设编码,服务端解析就可能乱码。 它用 #include "../Data/Json.mqh" 把 JSON 辅助类拉进来,倾向说明这个 body 主要装序列化后的 JSON 字符串。外汇和贵金属接口调用大多要求严格 UTF-8,实操前务必在 MT5 里确认 codepage 默认值,否则报文的 body 可能直接被网关拒掉。
class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpBody::CHttpBody(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpBody::~CHttpBody(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include the file CJson class | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "../Data/Json.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class : CHttpBody | class=class="str">"cmt">//| | class=class="str">"cmt">//| [PROPERTY] | class=class="str">"cmt">//| Name : CHttpBody | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : Responsible for organizing and storing the body of | class=class="str">"cmt">//| a request. | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpBody { class="kw">private: class="type">char m_body[]; class=class="str">"cmt">// Will store the request body as a class="type">char array class="type">uint m_codepage; class=class="str">"cmt">// Used to store the defined encoding };; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ HTTP 请求体的本地封装思路
在 MT5 里拼外接 API 请求时,请求体(body)不能随手用 string 硬塞,尤其涉及中文注释或 JSON 载荷,编码错乱会直接让服务端拒收。下面这个类把 body 统一收成 char 数组,并在构造时把码页锁死为 UTF-8(宏 CP_UTF8),从根上避开多字节乱码。 私有成员只有两个:m_body[] 负责存原始字节流,m_codepage 记当前编码。公开接口给了三种灌数据的方式——AddString 吃文本、AddJson 吃 CJson 引用、AddBinary 吃 char 数组引用,覆盖表单、JSON-RPC、以及图片/压缩包这类二进制场景。 实际写 EA 调用时,若你往 body 里丢含中文的订单备注,没显式走 UTF-8 通道,MT5 终端默认 ANSI 可能把字截成问号;用这套封装后,服务端收到字节流解码失败的概率会明显下降。外汇与贵金属接口对接属高风险操作,参数错一处就可能发错单,务必先在策略测试器里用模拟域名跑通。
class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpBody::CHttpBody(class="type">void) { m_codepage = CP_UTF8; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class : CHttpBody | class=class="str">"cmt">//| | class=class="str">"cmt">//| [PROPERTY] | class=class="str">"cmt">//| Name : CHttpBody | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : Responsible for organizing and storing the body of | class=class="str">"cmt">//| a request. | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpBody { class="kw">private: class="type">char m_body[]; class=class="str">"cmt">// Will store the request body as a class="type">char array class="type">uint m_codepage; class=class="str">"cmt">// Used to store the defined encoding class="kw">public: CHttpBody(class="type">void); ~CHttpBody(class="type">void); class=class="str">"cmt">//--- Add data to the body class="type">void AddString(class="type">class="kw">string data); class=class="str">"cmt">// Adds a text class="type">class="kw">string to the request body class="type">void AddJson(CJson &data); class=class="str">"cmt">// Adds data in JSON format to the body class="type">void AddBinary(class="type">char &data[]); class=class="str">"cmt">// Allows you to add binary data class=class="str">"cmt">//--- Clear the body
「HTTP 请求体的底层封装与编码细节」
在 MT5 里自己封装 HTTP 请求体时,最容易被忽略的是字节数组的结尾处理。下面这段 CHttpBody 实现把字符串、JSON、二进制三种载荷统一收进 char 数组,默认编码直接锁死 UTF-8(m_codepage = CP_UTF8),对外汇 EA 调 REST 接口能少踩一半乱码坑。 AddString 里先用 StringToCharArray 把文本按当前 codepage 写进 m_body,偏移量取 GetSize()-1;随后立刻 ArrayRemove 掉末尾那个位置。实测这一行决定你 POST 出去的 body 不会多带一个 0 终止符,Broker 侧解析失败率会明显下降。 GetAsBinary 返回 char[] 引用,适合直接塞入 MT5 的 WebRequest 二进制参数;GetAsJson 则把 body 反序列成 CJson 对象,做 AIGC 信号回传时比手搓字符串解析稳。 真要验证,开 MT5 新建一个 C++ 风格类文件粘进去,用 AddString("XAUUSD=2050.30") 然后 GetSize() 打印,长度应正好等于字符数而非字符数+1。贵金属与外汇接口调用本身属高风险操作,返回结构异常可能直接让持仓逻辑失效。
class="type">void Clear(class="type">void); class=class="str">"cmt">// Remove all body content class=class="str">"cmt">//--- Gets the body content class="type">class="kw">string GetAsString(class="type">void); class=class="str">"cmt">// Returns the request body as a class="type">class="kw">string CJson GetAsJson(class="type">void); class=class="str">"cmt">// Converts the request body into a JSON object, useful when the body contains structured data class="type">void GetAsBinary(class="type">char &body[]); class=class="str">"cmt">// Returns the body as an array of bytes, useful for working with binary data class=class="str">"cmt">//--- Size in bytes class="type">int GetSize(class="type">void); class=class="str">"cmt">// Returns the size of the request body, usually in bytes class=class="str">"cmt">//--- Codepage class="type">uint GetCodePage(class="type">void); class=class="str">"cmt">// Returns the defined codepage class="type">void SetCodePage(class="type">uint codepage); class=class="str">"cmt">// Defines the codepage to be used }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpBody::CHttpBody(class="type">void) { m_codepage = CP_UTF8; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpBody::~CHttpBody(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adds a text class="type">class="kw">string to the request body | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpBody::AddString(class="type">class="kw">string data) { StringToCharArray(data,m_body,this.GetSize()-class="num">1,WHOLE_ARRAY,m_codepage); ArrayRemove(m_body,this.GetSize()-class="num">1); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Adds data in JSON format to the body | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpBody::AddJson(CJson &data) { this.AddString(data.Serialize()); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Allows you to add binary data |