Connexus客户端(第七部分):添加客户端层·进阶篇
(2/3)·直接调WebRequest常被超时与状态码搞崩,封装一层才能让请求可复用、可测试
「用 MT5 原生类打通一次 HTTP 请求」
想在 MT5 里把行情外的数据拉进来,第一步是确认终端真能出网。下面这段用 Connexus 封装的 HttpClient 在 OnInit 里发了一个 GET 到 httpbin.org,回测环境之外实盘终端才能跑通。
class="macro">#include <Connexus/Core/HttpClient.mqh> class="type">int OnInit() { CHttpRequest request; request.Method() = HTTP_METHOD_GET; request.Url().Parse("https:class=class="str">"cmt">//httpbin.org/get"); CHttpResponse response; CHttpClient client; client.Send(request,response); Print(response.FormatString()); class="kw">return(INIT_SUCCEEDED); }
class="macro">#include <Connexus/Core/HttpClient.mqh> class="type">int OnInit() { CHttpRequest request; request.Method() = HTTP_METHOD_GET; request.Url().Parse("https:class=class="str">"cmt">//httpbin.org/get"); CHttpResponse response; CHttpClient client; client.Send(request,response); Print(response.FormatString()); class="kw">return(INIT_SUCCEEDED); }
用 WebRequest 与 MQL5 HTTP 类各跑一次 GET
MQL5 里发 HTTP 请求有两条路:直接调 WebRequest 函数,或用标准库里的 CHttpRequest / CHttpClient 封装类。两者都能在 EA 初始化阶段把远端响应打进日志,方便你当场核对接口联通性。 下面这段直接函数调用,先 ArrayRemove 把 data 数组末尾一项剔除,再发请求。status_code 拿到 HTTP 状态码,result 是字节数组,必须用 CharArrayToString 转成字符串才能肉眼看响应体。 ArrayRemove(data,ArraySize(data)-1); int status_code = WebRequest(method,url,headers,timeout,data,result,result_headers); Print("Status code: ",status_code); Print("Response: ",CharArrayToString(result)); 另一条路用类封装:CHttpRequest 设方法为 GET、Url 解析到 httpbin.org/get、Body 加一段 JSON;CHttpResponse 接返回;CHttpClient.Send 发出后直接 Print(response.FormatString()) 看整包。 开 MT5 新建 EA,把这两段分别塞进 OnInit,编译挂到图表,看终端日志里是否打出 Status code: 200 与响应原文。外汇与贵金属行情接口若走外部 HTTP,需注意网络延迟与断连风险,回测环境默认禁网。
ArrayRemove(data,ArraySize(data)-class="num">1); class=class="str">"cmt">//--- Calling the function and getting the status_code class="type">int status_code = WebRequest(method,url,headers,timeout,data,result,result_headers); class=class="str">"cmt">//--- Print the data Print("Status code: ",status_code); Print("Response: ",CharArrayToString(result)); class=class="str">"cmt">// We use CharArrayToString to display the response in class="type">class="kw">string form. class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">int OnInit() { class=class="str">"cmt">//--- Request object CHttpRequest request; request.Method() = HTTP_METHOD_GET; request.Url().Parse("https:class=class="str">"cmt">//httpbin.org/get"); request.Body().AddString("{\"key\":\"value\"}"); class=class="str">"cmt">//--- Response object CHttpResponse response; class=class="str">"cmt">//--- Client http object CHttpClient client; client.Send(request,response); Print(response.FormatString()); class=class="str">"cmt">//--- Initialize class="kw">return(INIT_SUCCEEDED); }
◍ 把 HttpClient 从 WebRequest 里拔出来
现在 CHttpClient 里直接写死了 WebRequest 调用,Send() 和底层传输绑死,这就是典型的高耦合。改一个函数签名,或者想换一种传输方式,就得动整块类,后期加功能会越改越乱。 解决思路是接口加依赖注入。先定义一个 IHttpTransport 接口,只规定“能发 HTTP 请求”这件事;CHttpClient 不再认 WebRequest,只认 IHttpTransport。具体用真 WebRequest 还是测试用的 FakeWebRequest,由外部传进去。 这样做有三个实在好处:维护时像换汽车零件,不动发动机就能换火花塞;复用时代码块能直接搬去别的项目;测试时把 WebRequest 换成模拟对象,不联网也能跑通逻辑,顺便引出后面的 Mock 技术。外汇和贵金属 EA 开发里这类解耦能显著降低实盘调试风险,但任何自动交易仍属高风险,需充分回测。
「用模拟对象绕开真实网络依赖」
把 CHttpClient 和 WebRequest 通过接口解耦后,就能塞进一个满足同样契约的模拟版本。它不真正发 HTTP 请求,只在受控环境里返回你预设的响应,用来验证类在各种场景下的行为。 直接让 CHttpClient 调 WebRequest 时,测试被服务器连通性和返回内容绑死,本地跑一次用例都得看远端脸色。注入模拟对象后,断网也能测:正常 200、超时、JSON 畸形、空 body 都能手动构造。 这类模拟对容错验证是关键——真实接口偶尔抽风返回非预期结构,你总不能等线上炸了才发现解析分支有漏。下一段我们看具体怎么把接口和模拟对象落进代码,让 CHttpRequest 的可测性直接上一个台阶。
用接口把 WebRequest 从库里摘出来
做 MT5 网络请求封装时,直接让业务类调 WebRequest 会把代码焊死:想换传输实现或做离线测试都麻烦。办法是在调用方和函数之间插一层接口,只定契约不定实现,类再去落地。 接口就是一张合同,只写方法签名——返回类型、名字、参数。下面这段是 IHttpTransport 的声明,两个 Request 重载都只给头部,编译器知道长什么样,但不管里面怎么跑。
interface IHttpTransport
{
class="type">int Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string cookie,class="kw">const class="type">class="kw">string referer,class="type">int timeout,class="kw">const class="type">char &data[],class="type">int data_size,class="type">char &result[],class="type">class="kw">string &result_headers);
class="type">int Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string headers,class="type">int timeout,class="kw">const class="type">char &data[],class="type">char &result[],class="type">class="kw">string &result_headers);
};
|--- Connexus |--- |--- Core |--- |--- |--- HttpTransport.mqh |--- |--- Interface |--- |--- |--- IHttpTransport.mqh <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| transport interface |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">interface</span> IHttpTransport { <span class="keyword">class="type">int</span> Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string cookie,class="kw">const class="type">class="kw">string referer,class="type">int timeout,class="kw">const class="type">char &data[],class="type">int data_size,class="type">char &result[],class="type">class="kw">string &result_headers); <span class="keyword">class="type">int</span> Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string headers,class="type">int timeout,class="kw">const class="type">char &data[],class="type">char &result[],class="type">class="kw">string &result_headers); }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Include files |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="preprocessor">class="macro">#include </span><span class="class="type">class="kw">string">"../Interface/IHttpTransport.mqh"</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| class : CHttpTransport |</span> <span class="comment">class=class="str">"cmt">//| |</span> <span class="comment">class=class="str">"cmt">//| [PROPERTY] |</span> <span class="comment">class=class="str">"cmt">//| Name : CHttpTransport |</span> <span class="comment">class=class="str">"cmt">//| Heritage : IHttpTransport |</span>
◍ 给 WebRequest 套一层可替换的传输类
把直接调用 WebRequest 的逻辑包进 CHttpTransport 类,等于在请求发起和底层函数之间加了一道中间层。这样做的好处是:上层代码只依赖 IHttpTransport 接口,哪天要换代理、加签名或改超时策略,只动这一层即可,不用碰业务主体。 类里提供了两个重载的 Request 方法。第一个带 cookie、referer 和 data_size 参数,适合带会话态和正文体的 REST 调用;第二个省略了 cookie 与 referer,由调用方把自定义头塞进 headers 字符串,更适合简单鉴权头透传。 构造函数与析构函数都是空实现,说明当前这层只做纯转发,不持有连接资源。下面这段就是类定义与第一个重载的实现,注意它最终直接 return WebRequest(...) 把参数原样下沉。 在 MT5 里实测时,timeout 传 5000 表示 5 秒,若远端黄金报价接口延迟抖动,这个数值会直接影响 EA 在极端行情下的数据更新概率,外汇与贵金属品种本身杠杆高、滑点风险大,参数须按实盘网络环境调。
class CHttpTransport : class="kw">public IHttpTransport { class="kw">public: CHttpTransport(class="type">void); ~CHttpTransport(class="type">void); class="type">int Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string cookie,class="kw">const class="type">class="kw">string referer,class="type">int timeout,class="kw">const class="type">char &data[],class="type">int data_size,class="type">char &result[],class="type">class="kw">string &result_headers); class="type">int Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string headers,class="type">int timeout,class="kw">const class="type">char &data[],class="type">char &result[],class="type">class="kw">string &result_headers); }; CHttpTransport::CHttpTransport(class="type">void) { } CHttpTransport::~CHttpTransport(class="type">void) { } class="type">int CHttpTransport::Request(class="kw">const class="type">class="kw">string method,class="kw">const class="type">class="kw">string url,class="kw">const class="type">class="kw">string cookie,class="kw">const class="type">class="kw">string referer,class="type">int timeout,class="kw">const class="type">char &data[],class="type">int data_size,class="type">char &result[],class="type">class="kw">string &result_headers) { class="kw">return(WebRequest(method,url,cookie,referer,timeout,data,data_size,result,result_headers)); }