Connexus请求解析(第六部分):创建HTTP请求与响应·进阶篇
(2/3)·把URL、方法、头、体、超时拼成完整请求,再给响应包上状态码与耗时,EA通信才可控
把 HTTP 请求拆成可操控的对象
在 MT5 里做 AIGC 行情分析,第一步往往是把对外接口的调用封装清楚。下面这个类把一次 HTTP 请求拆成了 url、body、header、method、timeout 五个独立对象,谁要改哪块直接拿指针改,不用动整体结构。 类声明里公开了五类入口:Url() 返回 CURL 指针管地址,Body() 和 Header() 分别返回各自的封装对象,Method() 拿请求方法对象;超时用重载的 Timeout(int) 设、Timeout(void) 读,默认不写就走系统底层的等待逻辑。 Clear() 负责把已填数据重置,FormatString() 把各部件拼成可发送的字符串——这两个在循环拉取多周期数据时会频繁用到。外汇与贵金属接口调用受网络波动影响大,超时设太短可能拿不到推理结果,设太长又拖慢盯盘节奏,建议先在回测环境用 1000~3000 毫秒区间试。 第一段实现很简单,Url() 只是把成员 m_url 用 GetPointer 抛出去,外部拿到就能链式改地址。
class=class="str">"cmt">//| | class=class="str">"cmt">//| [PROPERTY] | class=class="str">"cmt">//| Name : CHttpRequest | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : Gathers elements of an http request such as url, | class=class="str">"cmt">//| body, header, method and timeout | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpRequest { class="kw">public: class=class="str">"cmt">//--- HTTP CURL *Url(class="type">void); class=class="str">"cmt">// Get url object CHttpBody *Body(class="type">void); class=class="str">"cmt">// Get body object CHttpHeader *Header(class="type">void); class=class="str">"cmt">// Get header object CHttpMethod *Method(class="type">void); class=class="str">"cmt">// Get method object class=class="str">"cmt">//--- Timeout CHttpRequest *Timeout(class="type">int timeout); class=class="str">"cmt">// Set timeout class="type">int Timeout(class="type">void); class=class="str">"cmt">// Get timeout class=class="str">"cmt">//--- Auxiliary methods class="type">void Clear(class="type">void); class=class="str">"cmt">// Reset data class="type">class="kw">string FormatString(class="type">void); class=class="str">"cmt">// Format data }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get url object | class=class="str">"cmt">//+------------------------------------------------------------------+ CURL *CHttpRequest::Url(class="type">void) { class="kw">return(GetPointer(m_url)); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 拆开 HTTP 请求类的内部存取逻辑
在 MT5 里自己封装 REST 调用时,CHttpRequest 这类包装类最容易被忽略的是它怎么把内部对象暴露出来。下面这段实现给出了最直接的做法:用 GetPointer 把私有成员(body、header、method)的指针返出去,调用方拿到就能继续链式操作。 Timeout 方法做了重载:带 int 参数的版本用于设置超时并返回自身指针,方便连续配置;无参版本单纯读回 m_timeout 当前值。注意默认超时在 Clear() 里被硬重置为 5000 毫秒,如果你之前设过更长的值,调 Clear 后得重新设。 Clear 不只清 URL、body、headers,还会把超时拨回 5000。实盘跑 EA 若依赖自定义超时(比如外网 API 波动大设 15000),每次重置请求对象都要补一句 Timeout(15000),否则可能卡在默认 5 秒断连。 FormatString 则是把整笔请求拼成可读文本:方法描述、完整 URL、序列化后的 headers 都用分隔线隔开。调试阶段直接 Print(FormatString()),能在日志里看清发去了什么,省得抓包。
class=class="str">"cmt">//| Get body object | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpBody *CHttpRequest::Body(class="type">void) { class="kw">return(GetPointer(m_body)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get header object | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpHeader *CHttpRequest::Header(class="type">void) { class="kw">return(GetPointer(m_headers)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get method object | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpMethod *CHttpRequest::Method(class="type">void) { class="kw">return(GetPointer(m_method)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set timeout | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpRequest *CHttpRequest::Timeout(class="type">int timeout) { m_timeout = timeout; class="kw">return(GetPointer(this)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get timeout | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CHttpRequest::Timeout(class="type">void) { class="kw">return(m_timeout); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Reset data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpRequest::Clear(class="type">void) { m_url.Clear(); m_body.Clear(); m_headers.Clear(); m_timeout = class="num">5000; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Format data | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CHttpRequest::FormatString(class="type">void) { class="kw">return( "HTTP Request:"+ "\n---------------"+ "\nMethod: "+m_method.GetMethodDescription()+ "\nURL: "+m_url.FullUrl()+ "\n"+ "\n---------------"+ "\nHeaders:"+ "\n"+m_headers.Serialize()+ "\n"+ "\n---------------"+
「把指标文本塞进推送主体的拼接写法」
上面这段 MQL5 片段展示了如何把指标运算结果以纯文本形式拼进通知主体,而不是只发一个裸数值。 具体做法是先用 m_body.GetAsString() 取出指标状态字符串,再在前后各加换行与分隔线,拼成一段可读的 Body。这样在 MT5 手机端弹窗里,你能直接看到指标结论而不是一堆代号。 开 MT5 新建一个 EA 把这段接在 SendNotification 前,把 m_body 换成你自己的指标缓冲区对象,推送内容就会从「EURUSD:1」变成带空行和横线分隔的多行报文。外汇与贵金属行情跳空频繁,这类文本推送仅作辅助提醒,信号失效概率随品种波动率上升而增大。
Body:"+ "\n"+m_body.GetAsString()+ "\n---------------" ); } class=class="str">"cmt">//+------------------------------------------------------------------+
把服务器回包收进一个对象里
做 HTTP 交互时,服务器丢回来的东西散落在头、体、状态码和耗时里。与其每次手动拼字段,不如建一个响应类,把这些都捆成一个对象,后面读数据只跟这个对象打交道。 响应类要装的四个东西很直接:请求头(元数据)、响应体(真正想要的数据)、三位数的状态码(成功还是报错,第五部分再细拆)、以及 ulong 类型的耗时(毫秒)。在 MT5 里建文件 Include/Connexus/Core/HttpResponse.mqh,引入 CHttpHeader、CHttpBody、CHttpStatusCode 各一个实例,再挂一个私有 ulong 存时长即可。 给类补的方法也极简:Header()、Body()、StatusCode() 返回对应指针;Duration(ulong) 写耗时,Duration() 读耗时。另外照请求类那样配 Clear() 清数据、FormatString() 拼全部内容的字符串。这类封装目标只有一个——把响应元素聚起来,当单个对象处理。 实际用的时候,优势在读取侧:拿到响应对象后,直接取 Body() 指针就能以字符串、JSON 或二进制访问体内容,不用管底层怎么拆包。下面这段是类骨架代码,跑通后你调 Body()->某方法 就能验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//| HttpResponse.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 : CHttpResponse | class=class="str">"cmt">//| | class=class="str">"cmt">//| [PROPERTY] | class=class="str">"cmt">//| Name : CHttpResponse | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : gathers elements of an http response such as body,| class=class="str">"cmt">//| header, status code and duration | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpResponse { class="kw">public: CHttpResponse(class="type">void); ~CHttpResponse(class="type">void); };
◍ 响应容器的空壳与装配依赖
在 MT5 里封装 HTTP 响应,第一步往往是先立一个不继承任何父类的原生类 CHttpResponse,把构造函数和析构函数留空,仅作为后续装填数据的容器骨架。 下方代码给出了这个类最薄的一层:构造与析构都是空实现,真正的字段(body、header、status code、duration)尚未在片段中声明,只是预留了聚合位置。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpResponse::CHttpResponse(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpResponse::~CHttpResponse(class="type">void) { }
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpResponse::CHttpResponse(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpResponse::~CHttpResponse(class="type">void) { }
「响应对象的析构与一次真实回包」
把 HTTP 响应封装成类,核心是把头部、正文、状态码和耗时分开管。下面的 CHttpResponse 在构造时把三个子对象 new 出来,析构时逐一 delete,避免 MT5 脚本跑长连接时内存泄漏。
class CHttpResponse { class="kw">private: CHttpHeader *m_header; CHttpBody *m_body; CHttpStatusCodes *m_status_code; class="type">ulong m_duration; class="kw">public: CHttpResponse(class="type">void); ~CHttpResponse(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpResponse::CHttpResponse(class="type">void) { m_header = new CHttpHeader(); m_body = new CHttpBody(); m_status_code = new CHttpStatusCodes(); m_duration = class="num">0; class=class="str">"cmt">// Reset duration } CHttpResponse::~CHttpResponse(class="type">void) { class="kw">delete m_header; class="kw">delete m_body; class="kw">delete m_status_code; }
class CHttpResponse { class="kw">private: CHttpHeader *m_header; CHttpBody *m_body; CHttpStatusCodes *m_status_code; class="type">ulong m_duration; class="kw">public: CHttpResponse(class="type">void); ~CHttpResponse(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpResponse::CHttpResponse(class="type">void) { m_header = new CHttpHeader(); m_body = new CHttpBody(); m_status_code = new CHttpStatusCodes(); m_duration = class="num">0; class=class="str">"cmt">// Reset duration } CHttpResponse::~CHttpResponse(class="type">void) { class="kw">delete m_header; class="kw">delete m_body; class="kw">delete m_status_code; }