Connexus助手(第五部分):HTTP方法和状态码·进阶篇
(2/3)· 多数EA在WebRequest里硬写GET/POST,遇到401或500就哑火,本篇补齐动词与状态码的工程化封装
「HTTP 方法类的骨架与字符串映射」
在 MT5 里封装 REST 调用,先得有个方法枚举的承载类。下面这段 CHttpMethod 只做一件事:把内部枚举和对外字符串互相打通,构造/析构都是空壳,真正的状态落在 m_method 私有成员上。 类里留了两个布尔接口 IsOption / IsTrace 的声明,但本段未给实现,说明这个类只是方法调度的前半截,后面才会补上判定逻辑。 Set(string) 的重载值得注意:它先把入参转全大写再比对,支持 CONNECT、DELETE、GET、HEAD、OPTIONS、PATCH、POST 共 7 种字面量,匹配成功才写 m_method 并返回 true。这意味着你从 JSON 配置里读到的请求动作,只要大小写不统一也能容错进枚举。 开 MT5 新建 EA 把这段贴进 .mqh,试着用 Set("post") 调用,观察 m_method 是否被置为 HTTP_METHOD_POST,能验证大小写归一是否生效。
class="type">bool IsOption(class="type">void); class="type">bool IsTrace(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpMethod::CHttpMethod(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpMethod::~CHttpMethod(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Defines the http method | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpMethod::class="kw">operator=(ENUM_HTTP_METHOD method) { m_method = method; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Defines the http method | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CHttpMethod::Set(ENUM_HTTP_METHOD method) { m_method = method; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Defines the http method | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::Set(class="type">class="kw">string method) { class="type">class="kw">string method_upper = method; StringToUpper(method_upper); if(method_upper == "CONNECT") { m_method = HTTP_METHOD_CONNECT; class="kw">return(true); } else if(method_upper == "DELETE") { m_method = HTTP_METHOD_DELETE; class="kw">return(true); } else if(method_upper == "GET") { m_method = HTTP_METHOD_GET; class="kw">return(true); } else if(method_upper == "HEAD") { m_method = HTTP_METHOD_HEAD; class="kw">return(true); } else if(method_upper == "OPTIONS") { m_method = HTTP_METHOD_OPTION; class="kw">return(true); } else if(method_upper == "PATCH") { m_method = HTTP_METHOD_PATCH; class="kw">return(true); } else if(method_upper == "POST") {
HTTP 方法类的取值与判定实现
这段 CHttpMethod 类的后半段给出了方法枚举的读取与字符串映射逻辑。Set 方法中遇到 POST、PUT、TRACE 等大写字符串会分别写入 m_method 并返回 true,不匹配任何已知方法时返回 false,说明外部传参必须事先转成大写。 Get 直接返回内部成员 m_method,属于零开销的取值器;GetAsString 则用 switch 覆盖全部 10 种枚举(NULL/CONNECT/DELETE/GET/HEAD/OPTIONS/PATCH/POST/PUT/TRACE),default 分支回吐 "Unknown HTTP Method" 方便调试时肉眼排错。 IsConnect 与 IsGet 是两个轻量布尔判定,仅比较 m_method 是否等于对应枚举值。若你要在 EA 里向自建行情网关发请求,直接复用 IsGet() 判断走查询分支,比手写字符串比较更不容易拼错。 外汇与贵金属接口调用涉及实时报价,网络异常可能导致滑点扩大,实盘前务必在 MT5 策略测试器用模拟地址跑通这套方法映射。
m_method = HTTP_METHOD_POST; class="kw">return(true); } else if(method_upper == "PUT") { m_method = HTTP_METHOD_PUT; class="kw">return(true); } else if(method_upper == "TRACE") { m_method = HTTP_METHOD_TRACE; class="kw">return(true); } class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get http method | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_HTTP_METHOD CHttpMethod::Get(class="type">void) { class="kw">return(m_method); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Get the description of the selected http method | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string CHttpMethod::GetAsString(class="type">void) { class="kw">switch(m_method) { case HTTP_METHOD_NULL: class="kw">return "NULL"; case HTTP_METHOD_CONNECT: class="kw">return "CONNECT"; case HTTP_METHOD_DELETE: class="kw">return "DELETE"; case HTTP_METHOD_GET: class="kw">return "GET"; case HTTP_METHOD_HEAD: class="kw">return "HEAD"; case HTTP_METHOD_OPTION: class="kw">return "OPTIONS"; case HTTP_METHOD_PATCH: class="kw">return "PATCH"; case HTTP_METHOD_POST: class="kw">return "POST"; case HTTP_METHOD_PUT: class="kw">return "PUT"; case HTTP_METHOD_TRACE: class="kw">return "TRACE"; class="kw">default: class="kw">return "Unknown HTTP Method"; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is connect | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsConnect(class="type">void) { class="kw">return(m_method == HTTP_METHOD_CONNECT); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is get | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsGet(class="type">void) { class="kw">return(m_method == HTTP_METHOD_GET); }
◍ 给 HTTP 方法做类型判定的几个内联函数
在 MT5 里用 C++ 风格的封装去碰 REST 接口时,CHttpMethod 类把每种请求动词都做成了一个返回 bool 的短函数。它们不干别的事,只比对内部成员变量 m_method 是否等于对应的宏常量。 这种写法看着啰嗦,但在 EA 里动态拼请求很有用:比如你根据账户净值变化决定用 POST 发订单还是用 DELETE 撤单,直接 if(IsPost()) 比手写字符串比较更不容易拼错。 下面这段是类里实际的那组判定函数,宏名如 HTTP_METHOD_POST 一般在 WebRequest 相关头文件里定义,值就是 "POST" 这类字符串。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is post | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsPost(class="type">void) { class="kw">return(m_method == HTTP_METHOD_POST); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is put | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsPut(class="type">void) { class="kw">return(m_method == HTTP_METHOD_PUT); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is class="kw">delete | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsDelete(class="type">void) { class="kw">return(m_method == HTTP_METHOD_DELETE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is patch | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsPatch(class="type">void) { class="kw">return(m_method == HTTP_METHOD_PATCH); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is head | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsHead(class="type">void) { class="kw">return(m_method == HTTP_METHOD_HEAD); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is option | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsOption(class="type">void) { class="kw">return(m_method == HTTP_METHOD_OPTION); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if method is trace | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CHttpMethod::IsTrace(class="type">void) { class="kw">return(m_method == HTTP_METHOD_TRACE); } class=class="str">"cmt">//+------------------------------------------------------------------+
「把 HTTP 状态码拆成五段数值区间」
做 MT5 WebRequest 对接外部信号源时,服务器回的不只是 JSON,第一行就藏着三位数字的状态码。第一位直接定类:1xx 是“还在处理”,2xx 是“成了”,3xx 是“去别处取”,4xx 是“你请求有问题”,5xx 是“服务器自己挂了”。 实际盯盘脚本里,200 OK 和 204 No Content 最常见——前者带数据回来,后者确认收到但不返_body,适合心跳包。404 和 500 则要分开处理:404 多半是 URL 拼错或资源被删,500 是远端崩了,重试逻辑得区分对待。 下面这张区间表值得存进你的开发笔记:100-199 极少碰,属临时响应;200-299 全成功;300-399 重定向(301 永久、302 临时、304 用缓存);400-499 客户端错(401 要鉴权、403 拒绝、405 方法不对);500-599 服务端错(502 网关坏、503 过载、504 上游超时)。 若想让 EA 兼容任意合法码,最好封装一个类统一解析,而不是在每次 WebRequest 后写死判断。外汇与贵金属接口调用受网络与平台限制,失败概率不低,任何重试都只是降低漏单可能,不保证送达。
把状态码收进一个可识别类别的类
在 MT5 里做 HTTP 请求,光拿到 WebRequest 的返回值不够,最好把状态码和描述、分类都封装起来。新建 Includes/Connexus/Constants/HttpStatusCode.mqh,先放一个 ENUM_HTTP_STATUS 枚举,把标准码和几个库自定义的码都列进去。 自定义码里,HTTP_STATUS_URL_NOT_ALLOWED 对应终端没把 URL 加进允许列表;HTTP_STATUS_URL_NOT_DEFINED 是请求没带 URL;HTTP_STATUS_METHOD_NOT_DEFINED 是没给合法方法。当 WebRequest 返回 -1,按文档就是 URL 未授权,库应自动转成 HTTP_STATUS_URL_NOT_ALLOWED。 类内部用私有变量 m_status 存 ENUM_HTTP_STATUS,再给赋值和读取的口子:operator=(int) 和 operator=(ENUM_HTTP_STATUS) 让等号直接接整数或枚举,Set(ENUM_HTTP_STATUS) 走显式设置,Get() 拿码、GetMessage() 拿对应文案。 分类判断也得内置,靠数值区间:IsInformational 看 100–199,IsSuccess 看 200–299,IsRedirection 看 300–399,IsClientError 看 400–499,IsServerError 看 500–599。外汇或贵金属 EA 调远程行情接口时,用 IsServerError 快速跳过 5xx 比硬判断数字更稳。 下面这段是类的骨架,先只声明了构造和析构,后续方法往里补即可在 MT5 的 MetaEditor 直接编译验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| HttpStatusCode.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 : CHttpStatusCodes | class=class="str">"cmt">//| | class=class="str">"cmt">//| [PROPERTY] | class=class="str">"cmt">//| Name : CHttpStatusCodes | class=class="str">"cmt">//| Heritage : No heritage | class=class="str">"cmt">//| Description : Saved http status code. | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------+ class CHttpStatusCodes { class="kw">public: CHttpStatusCodes(class="type">void); ~CHttpStatusCodes(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+
◍ HTTP 状态枚举的收口定义
把前面的类骨架补齐,剩下就是状态枚举本身。CHttpStatusCodes 的构造和析构都是空实现,真正干活的是 ENUM_HTTP_STATUS 里那一大张码表。 从 -1 的 URL_NOT_ALLOWED 到 503 的 SERVICE_UNAVAILABLE,枚举覆盖了 MQL 内部错误、1xx 通知、2xx 成功、3xx 重定向、4xx 客户端错、5xx 服务端错共六段。你在 EA 里调 WebRequest 时,拿返回码比对这张表,就能直接分辨是地址没配好(-1/1/2)还是远端限流(429)或者网关挂了(502)。 外汇与贵金属行情接口走 HTTP 时延迟和拒绝率都不稳定,这类状态判断只能帮你定位故障环节,不预示任何报价走向;实盘前请在策略测试器里把非常规返回(如 425、451)也打日志跑一遍。
class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpStatusCodes::CHttpStatusCodes(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CHttpStatusCodes::~CHttpStatusCodes(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_HTTP_STATUS { class=class="str">"cmt">//--- Mql error HTTP_STATUS_URL_NOT_ALLOWED = -class="num">1, HTTP_STATUS_URL_NOT_DEFINED = class="num">1, HTTP_STATUS_METHOD_NOT_DEFINED = class="num">2, class=class="str">"cmt">//--- Informational HTTP_STATUS_CONTINUE = class="num">100, HTTP_STATUS_SWITCHING_PROTOCOLS = class="num">101, HTTP_STATUS_PROCESSING = class="num">102, HTTP_STATUS_EARLY_HINTS = class="num">103, class=class="str">"cmt">//--- Successul HTTP_STATUS_OK = class="num">200, HTTP_STATUS_CREATED = class="num">201, HTTP_STATUS_ACCEPTED = class="num">202, HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = class="num">203, HTTP_STATUS_NO_CONTENT = class="num">204, HTTP_STATUS_RESET_CONTENT = class="num">205, HTTP_STATUS_PARTIAL_CONTENT = class="num">206, HTTP_STATUS_MULTI_STATUS = class="num">207, HTTP_STATUS_ALREADY_REPORTED = class="num">208, class=class="str">"cmt">//--- Redirection messages HTTP_STATUS_MULTIPLE_CHOICES = class="num">300, HTTP_STATUS_MOVED_PERMANENTLY = class="num">301, HTTP_STATUS_FOUND = class="num">302, HTTP_STATUS_SEE_OTHER = class="num">303, HTTP_STATUS_NOT_MODIFIED = class="num">304, HTTP_STATUS_USE_PROXY = class="num">305, HTTP_STATUS_SWITCH_PROXY = class="num">306, HTTP_STATUS_TEMPORARY_REDIRECT = class="num">307, HTTP_STATUS_PERMANENT_REDIRECT = class="num">308, class=class="str">"cmt">//--- Client error HTTP_STATUS_BAD_REQUEST = class="num">400, HTTP_STATUS_UNAUTHORIZED = class="num">401, HTTP_STATUS_PAYMENT_REQUIRED = class="num">402, HTTP_STATUS_FORBIDDEN = class="num">403, HTTP_STATUS_NOT_FOUND = class="num">404, HTTP_STATUS_METHOD_NOT_ALLOWED = class="num">405, HTTP_STATUS_NOT_ACCEPTABLE = class="num">406, HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = class="num">407, HTTP_STATUS_REQUEST_TIMEOUT = class="num">408, HTTP_STATUS_CONFLICT = class="num">409, HTTP_STATUS_GONE = class="num">410, HTTP_STATUS_LENGTH_REQUIRED = class="num">411, HTTP_STATUS_PRECONDITION_FAILED = class="num">412, HTTP_STATUS_PAYLOAD_TOO_LARGE = class="num">413, HTTP_STATUS_URI_TOO_LONG = class="num">414, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = class="num">415, HTTP_STATUS_RANGE_NOT_SATISFIABLE = class="num">416, HTTP_STATUS_EXPECTATION_FAILED = class="num">417, HTTP_STATUS_MISDIRECTED_REQUEST = class="num">421, HTTP_STATUS_UNPROCESSABLE_ENTITY = class="num">422, HTTP_STATUS_LOCKED = class="num">423, HTTP_STATUS_FAILED_DEPENDENCY = class="num">424, HTTP_STATUS_TOO_EARLY = class="num">425, HTTP_STATUS_UPGRADE_REQUIRED = class="num">426, HTTP_STATUS_PRECONDITION_REQUIRED = class="num">428, HTTP_STATUS_TOO_MANY_REQUESTS = class="num">429, HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = class="num">431, HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = class="num">451, class=class="str">"cmt">//--- Server error HTTP_STATUS_INTERNAL_SERVER_ERROR = class="num">500, HTTP_STATUS_NOT_IMPLEMENTED = class="num">501, HTTP_STATUS_BAD_GATEWAY = class="num">502, HTTP_STATUS_SERVICE_UNAVAILABLE = class="num">503,