Connexus请求解析(第六部分):创建HTTP请求与响应·进阶篇
🔌

Connexus请求解析(第六部分):创建HTTP请求与响应·进阶篇

(2/3)·把URL、方法、头、体、超时拼成完整请求,再给响应包上状态码与耗时,EA通信才可控

实战向 第 2/3 篇
不少EA在调用WebRequest时把参数散落在各处,一旦超时或状态码异常就难以定位。把请求组件拆开却从不封装,后续维护基本靠翻代码猜意图。本篇用类把请求和响应收拢,先避开这个坑。

把 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 抛出去,外部拿到就能链式改地址。

MQL5 / C++
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()),能在日志里看清发去了什么,省得抓包。

MQL5 / C++
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」变成带空行和横线分隔的多行报文。外汇与贵金属行情跳空频繁,这类文本推送仅作辅助提醒,信号失效概率随品种波动率上升而增大。

MQL5 / C++
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()->某方法 就能验证。

MQL5 / C++
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)尚未在片段中声明,只是预留了聚合位置。

MQL5 / C++
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)
  {
  }
类文件头部通过 #property 标注了版权与链接信息,并借助三条 #include 把状态码常量、响应体、响应头三个独立头文件拉进编译单元:../Constants/HttpStatusCode.mqh、../Header/HttpBody.mqh、../Header/HttpHeader.mqh。 这种拆法意味着:响应对象本身不存逻辑,只负责把分散的片段聚起来。你在 EA 里若想测一次 REST 请求耗时,直接 new 这个空响应类、再往里塞 Header 和 Body,就能在 OnTimer 里读出 duration 字段做统计——外汇与贵金属行情接口普遍波动剧烈,实盘调用第三方 API 存在断连与滑点高风险,建议先在策略测试器用历史 tick 验证吞吐。

MQL5 / C++
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 脚本跑长连接时内存泄漏。

MQL5 / C++
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;
  }
逐行看:m_header / m_body / m_status_code 是三个指针成员,构造里分别 new 对应的处理类;m_duration 用 ulong 记响应耗时,初始化清 0。析构函数里三个 delete 必须配对,否则 EA 反复发请求会慢慢吃满内存。 实际跑一次返回的包长这样:状态码 200,耗时 120ms,Server 头是 Apache/2.4.41 (Ubuntu),Body 里 Content-Length 为 256 字节,JSON 结构含 id=123、name="Sample Item"、status="active"。你在 MT5 里接 REST 信号时,可用 m_duration 这个 120ms 量级的基准判断网关是否突然变慢。 外汇与贵金属杠杆高,任何外部 API 延迟都可能让你在下单窗口错过价格,建议把超时阈值设在 300ms 以上并本地缓存上一次成功响应。

MQL5 / C++
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;
  }
交给小布盯盘看接口耗时
这些诊断小布盯盘的AIGC已内置,打开对应品种页即可看到请求响应时长的异常波动,你专注策略逻辑而非底层通信。

常见问题

仅URL和HTTP方法为必需,其余如请求头、请求体、超时均为可选,但生产环境建议显式设超时避免EA阻塞。
类可封装状态码判断、耗时计算与体解析,调用方直接取语义化结果,比裸结构体更不易误用。
可以,小布盯盘的AIGC模块能记录品种页上的接口异常与超时频率,把这些重复劳动交给小布,你专注决策。
多数服务端5秒足够;若对接慢速网关可上调,但过长会让EA在等待中错过行情,需按实盘权衡。
上篇只解析状态码含义,本篇CHttpResponse将其作为成员存储并关联耗时,完整讨论见《Connexus请求解析·实战篇》。