在 MQL5 中使用 WinInet(二):POST 请求与文件上传
一、回顾:第一课的 GET 与本文的任务
在 WinInet 第一课里,我们用 wininet.dll 加载库、打开会话、用 GET 请求去拉取网页或行情快照。GET 把参数直接拼在 URL 后面,简单够用,但有两个硬限制:一是参数暴露在地址栏、长度受 URL 限制;二是它只能传文本,塞不进二进制。本文要补上的,正是这两个能力缺口——用 POST 把数据藏进请求体,用 multipart/form-data 把文件(截图、CSV 报表)真正发到服务器。作者沿用一个 CMqlNet 类来封装整套 HTTP 客户端逻辑,配合 Charles 本地代理,能肉眼看到每一次请求和响应的原文,是调试网络代码不可替代的工具。
延续第一课的约定,我们仍然围绕一个核心类 CMqlNet 工作。不同的是,这一课引入了 tagRequest 结构,把方法、路径、请求头、数据、文件开关统一打包,再交给 Request 方法执行。理解了这个结构,POST 和文件上传就只是同一个开关的不同档位。
二、POST 请求的基础:tagRequest 结构与 CMqlNet::Request
Request 方法内部第一件事就是检查终端是否允许 DLL:TERMINAL_DLLS_ALLOWED 和 MQL5_DLLS_ALLOWED 任一为 false 都会直接返回失败。这意味着你的 EA/脚本要跑通网络,必须在 MT5 设置里勾选「允许 DLL 导入」。接着它会把请求数据读进 uchar 数组——如果 fromFile=true,就调用 FileToArray 把磁盘文件读进来;否则用 StringToCharArray 把字符串转成字节。随后 HttpOpenRequestW 建立请求句柄,HttpSendRequestW 真正发出去,最后 ReadPage 把响应写进变量或文件,并逐个关闭句柄。整个流程对 GET/POST/文件复用同一套代码,区别只在前面的 tagRequest 怎么填。
tagRequest 的字段含义必须记牢:stVerb 填 GET/POST;stObject 是相对域名的路径,比如 /mt5swap/metaswap.php;stHead 是请求头(例如 Content-Type 那一行);stData 是附加数据;fromFile 标记 stData 是不是文件名;stOut 是接收响应的去处;toFile 标记 stOut 是不是文件名。Init 函数一次性初始化这七个字段,调用方只改参数、不动内部细节,这就是封装带来的可读性。
bool MqlNet::Request(tagRequest &req) {
if(!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) { Print("-DLL not allowed"); return false; }
if(!MQL5InfoInteger(MQL5_DLLS_ALLOWED)) { Print("-DLL not allowed"); return false; }
uchar data[]; int hRequest,hSend; string Vers="HTTP/1.1";
if(req.fromFile) { if(FileToArray(req.stData,data)<0) return false; }
else StringToCharArray(req.stData,data);
hRequest=HttpOpenRequestW(hConnect,req.stVerb,req.stObject,Vers,"",0,INTERNET_FLAG_KEEP_CONNECTION,0);
hSend=HttpSendRequestW(hRequest,req.stHead,StringLen(req.stHead),data,ArraySize(data));
if(hSend>0) ReadPage(hRequest,req.stOut,req.toFile);
InternetCloseHandle(hRequest); return (hSend>0);
}
三、application/x-www-form-urlencoded:把数据藏进请求体
把 GET 改成 POST,最简单的形式就是「表单提交」。请求头设为 Content-Type: application/x-www-form-urlencoded,数据按 参数名=值 用 & 连接,例如 server=MetaQuotes-Demo&pair=EURUSD&long=1.02&short=-0.05。关键差异在于:GET 把这段字符串拼到路径后面(/metaswap.php?...),而 POST 把它放进 lpOptional(也就是 tagRequest.stData)字节数组里,路径本身保持干净。HttpSendRequest 的第四个参数正是这块 uchar 数据,专供 POST/PUT 使用。
文中 MetaSwap 脚本同时演示了两种写法:GET 模式 req.Init("GET", Path+"?"+Data, Head, "", false, file, true);POST 模式 req.Init("POST", Path, Head, Data, false, file, true)。注意 GET 时 stData 传空串、数据进了路径;POST 时路径不含数据、数据进 stData。这种对称写法让你一比就懂二者的本质区别。实际做行情共享、信号上报时,POST 更稳妥,因为数据不会被浏览器历史、代理日志明文留存。
四、multipart/form-data:为什么只有 POST 才能发文件
POST 真正的杀手锏不是「模拟 GET」,而是能发二进制。URL 编码只认有限字符集,二进制数据(比如一张 GIF 截图)一旦经过 URL 编码就会失真,连很小的 gif 都发不出去。multipart/form-data 解决了这个问题:它把请求体切成若干「部分」(part),每部分可以有自己的 Content-Type。请求头写成 Content-Type: multipart/form-data; boundary=SEPARATOR,其中 SEPARATOR 是一串随机、全局唯一的字符串(PHP 里常用时间 MD5),用来当分隔符。
分隔符的放置规则要记准:每个部分前放 --SEPARATOR,最后一个部分后放 --SEPARATOR-- 收尾。每一部分内部先写 Content-Disposition(form-data 或 file,后者带 filename),再写具体的 Content-Type(如 image/gif、application/octet-stream),空一行后才是正文。这样一次请求就能同时塞文本字段和二进制文件。例如一个 EA 平仓时既传 ExpertName 文本,又传 screen.gif 截图和 statement.csv 报表——服务器侧用 PHP 的 $_POST 收文本、$_FILES 收文件,几句代码就能落盘。
--SEPARATOR Content-Disposition: form-data; name="ExpertName" MACD_Sample --SEPARATOR Content-Disposition: file; name="screen"; filename="screen.gif" Content-Type: image/gif Content-Transfer-Encoding: binary ......content of the gif file..... --SEPARATOR--
五、实战:用 ChartScreenShot 把图表快照发到服务器
文中给了 ScreenPost 脚本的完整思路:先 INet.Open 建会话,用 ChartScreenShot 截一张 800x600 的图存成 gif,FileOpen+FileReadArray 把文件读进 uchar 数组,再用上面讲的 multipart 结构把图作为 file 部分发出去。这里有个细节——文件内容要当成二进制塞进请求体,所以 Content-Transfer-Encoding 写 binary,不能用文本编码。服务器端配套的 screen.php 通过 $_FILES['data']['tmp_name'] 取到临时文件、用 fopen/fwrite 写到以 EA 名命名的目录里。整套链路打通后,你的 EA 就能自动把每天的交易图表归档到远程,做复盘素材再也不用人工截图。
六、用 Cookie 维持登录会话
很多 Web 服务要先登录才能读数据。流程是:第一次 POST 账号密码,从响应的 Set-Cookie 头里取出会话标识,存进后续请求的 stHead,服务器据此认出你是同一个人。在 CMqlNet 体系里,Cookie 也是普通请求头文本的一部分,拼进 req.stHead 即可。需要多步交互(登录→查数据→登出)时,就靠这个 Cookie 把几次独立 HTTP 调用串成一条会话。
配合 Charles 代理,你能看到登录响应里 Set-Cookie 的确切格式,再原样回传,调试效率极高。注意 Cookie 可能带过期时间和 domain 限制,长时间运行的 EA 要考虑刷新逻辑,否则半夜会话失效会导致数据上报中断。
七、DLL 许可、Charles 代理与跨平台限制
WinInet 是 Windows 系统自带的库,Request 函数一上来就查 TERMINAL_DLLS_ALLOWED 和 MQL5_DLLS_ALLOWED,二者任一关闭就直接退出。这意味着:在 Linux/macOS 上运行的 MT5(含部分云 VPS 环境)拿不到 wininet.dll,这段网络代码根本编译运行不了。如果你的 EA 要跨平台,应改用 MQL5 内置的 WebRequest,或者把外部交互交给一个独立的中间服务。
另外,作者强烈建议本地起 Charles 代理。WinInet 的报错信息很吝啬(常常是 -Err SendRequest 加一个错误码),没有代理你根本看不到请求体到底发了什么、服务器回了什么。Charles 把每一次 HTTP 交互原文摊开,header、body、boundary 一眼可见,是定位 400/401/编码错位的必备工具。实盘环境则用正式域名的 HTTPS 端口,别把调试代理地址写死进生产代码。
八、小结
CMqlNet 加 tagRequest 让 MQL5 具备了完整的 HTTP 客户端能力:GET/POST 任意切换、文本表单用 x-www-form-urlencoded、二进制文件用 multipart/form-data、Cookie 维护会话。记住三件事——POST 的价值在发二进制而非模拟 GET;boundary 分隔符必须全局唯一且正确收尾;WinInet 仅限 Windows,跨平台请换 WebRequest。配合 Charles 代理调试,你就能让 EA 真正与外部 Web 服务对话,做行情共享、信号上报、图表归档都不在话下。