马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:用 uo_httpclient 做大文件下载并显示实时进度
阅读说明
1. 适用版本:PBIDEA 1.x(基于 PB 12.5 运行库;uo_httpclient 由 PbIdea.dll 提供,需安装 PBIDEA 运行库)。本文示例依赖 PBIDEA 运行库,经典 PB(未装 PBIDEA)无此对象。
2. 支持数据库:不涉及数据库(本文为 HTTP 文件下载,与 DBMS 无关,故不罗列)。
3. 操作系统与环境要求:Windows 7+,已安装 PBIDEA 运行库(PbIdea.dll 等);工程中需导入含 uo_httpclient 的 pbl(websuite.pbl)。
4. 难度系数:★★★☆☆
5. 其它阅读说明:建议先了解 Day4《uo_httpclient 发请求》的 GET/POST 基础;进度示例需在窗口上放置进度条控件 p_1 与多行编辑框 mle_1(仅进度示例需要,基础下载示例不需要任何控件)。
一、核心概念与原理
在 PBIDEA 里做 HTTP 下载,主力对象是 uo_httpclient(位于 websuite.pbl,底层基于 Windows 的 WinHTTP)。它把"下载到磁盘"和"下载到内存"两套能力都封装好了,而且内置了进度回调事件,让我们在下载大文件时能把实时进度显示给用户,而不是卡在一个没有反馈的界面上。
下载这件事有两个最关键的落点:
- 落盘还是落内存:DownLoadFile(...) 直接把远程文件写到本机磁盘;DownLoad(...) 把内容读进 blob,由你自己决定怎么处理。大文件一般直接落盘,体积小或需要 Immediate 加工的内容才用 blob。
- 同步 + 事件回调:DownLoadFile / DownLoad 本身是同步调用——函数不返回就代表还没下完。但在下载过程中,它会周期性触发 ondownload 事件,把"总字节数 / 已下字节数 / 百分比 / 当前文件名"四个参数传进来;下载结束时再触发 onfinish 事件。也就是说,界面反馈靠事件,控制流靠同步返回。
百分比参数 ai_percent 是 integer(0~100),两个字节数参数是 double(因为大文件字节数会超过 long 上限,用 double 兜底)。ondownload 返回 TRUE 表示继续下载,FALSE 表示中止下载——这正是"取消按钮"的实现入口。
二、uo_httpclient 下载相关 API 全貌
下载主题相关的接口、事件、常量清单如下(均来自 websuite.pbl/uo_httpclient.sru 真实导出):
下载函数(PbIdea.dll 原生)
- boolean DownLoadFile(readonly string strUrl) —— 下载到当前目录下的 .\download\ 子目录(目录需存在)。
- boolean DownLoadFile(readonly string strUrl, readonly string strSavePath) —— 下载并保存到指定完整路径(含文件名)。
- boolean DownLoadFile(readonly string strUrl, readonly string strSavePath, boolean bTriggerEvent) —— 同上,第三个参数 TRUE 时才触发 ondownload / onfinish 事件。
- blob DownLoad(readonly string strUrl) —— 下载到内存 blob(不落盘)。
- blob DownLoad(readonly string strUrl, boolean bTriggerEvent) —— 同上,第二个参数控制是否触发进度事件。
下载相关事件
- event type boolean ondownload(double ad_totalsize, double ad_downsize, integer ai_percent, string as_file) —— 进度回调,返回 TRUE 继续、FALSE 中止。
- event onfinish(boolean success, string info) —— 下载结束回调,success 表示成败。
常用配套设置(下载前调用)
- SetProxy(readonly string ip, int port) —— 设置代理。
- SetTimeout(ulong nTimeout) 或 SetTimeout(ulong nConn, ulong nSend, ulong nRecv) —— 超时(单位毫秒)。
- SetBasicAuth(username, password) / SetJWTAuth(token) —— 鉴权(私有仓库 / 内网服务常用)。
- SetHeader(key, value) / SetHeader(uo_map) —— 自定义请求头(例如带 token、设 Range)。
动作常量(Request 用,下载一般用不到但列出来备查)
- HttpGet=1 HttpPost=2 HttpPut=3 HttpPatch=4 HttpDelete=5 ……HttpHead=7 等。
注意:uo_httpclient 是 global uo_httpclient uo_httpclient(自动实例化全局变量),所以你也可以直接写 uo_httpclient.DownLoadFile(...)。但如果要写 ondownload 事件代码,必须新建一个继承自 uo_httpclient 的子类 NVO,把代码写在子类的事件里——全局自动实例无法挂自定义事件体。
三、典型用法示例
示例 1:最基础——直接下载落盘
前置:工程中已导入 websuite.pbl(含 uo_httpclient),运行环境已装 PBIDEA 运行库。无界面控件要求,可在按钮 clicked 或任意脚本处运行。
步骤:把下方代码贴入按钮 clicked 事件,运行并点击按钮,即可在 MessageBox 看到下载成败。 - // 示例输入:远程文件地址
- string ls_url
- ls_url = 'https://example.com/release/bigfile.zip'
- // 示例输入:本机保存的完整路径(目录必须已存在)
- string ls_save
- ls_save = 'C:\temp\bigfile.zip'
- // HTTP 客户端对象
- uo_httpclient hc
- hc = create uo_httpclient
- // 下载结果
- boolean lb_ok
- lb_ok = false
- // 两参重载:不触发进度事件,直接落盘
- lb_ok = hc.DownLoadFile(ls_url, ls_save)
- if lb_ok then
- MessageBox('下载完成', '文件已保存到:' + ls_save)
- else
- MessageBox('下载失败', '请检查 URL 是否可达、保存目录是否存在')
- end if
- destroy hc
复制代码
示例 2:带实时进度——用子类挂 ondownload 事件
前置:新建一个 NVO(如 uo_httpclient_prog)继承自 uo_httpclient,在其 ondownload 事件写进度刷新代码(见下方"事件体");窗口 w_demo 上放进度条控件 p_1 与多行编辑框 mle_1,并把 p_1 的 minposition=0、maxposition=100。
步骤:1) 建子类并写事件体;2) 在窗口按钮 clicked 贴入"调用代码";3) 运行窗口点击按钮,即可看到进度条与文字实时刷新。
事件体(写在 uo_httpclient_prog 的 ondownload 事件中):事件四个入参为 ad_totalsize(总字节数,double)、ad_downsize(已下字节数,double)、ai_percent(百分比 0~100,integer)、as_file(当前文件名);返回 TRUE 继续下载、FALSE 中止。下面把进度刷新到窗口控件 p_1(进度条)与 mle_1(多行编辑框):
- p_1.position = ai_percent
- mle_1.text = '已下载 ' + String(ad_downsize) + ' / ' + String(ad_totalsize) + ' 字节(' + String(ai_percent) + '%)'
- return TRUE
复制代码
调用代码(窗口按钮 clicked 事件):
- // 示例输入:远程文件地址
- string ls_url
- ls_url = 'https://example.com/release/bigfile.zip'
- // 示例输入:本机保存的完整路径
- string ls_save
- ls_save = 'C:\temp\bigfile.zip'
- // 继承了 ondownload 事件的子类实例
- uo_httpclient_prog hc
- hc = create uo_httpclient_prog
- // 三参重载:第三个参数 TRUE 才会触发 ondownload / onfinish 事件
- hc.DownLoadFile(ls_url, ls_save, TRUE)
- // 走到这里下载已经结束(期间进度条已实时刷新)
- MessageBox('提示', '下载已结束,结束时会触发 onfinish 事件')
- destroy hc
复制代码
示例 3:下载到内存 blob 再落盘
前置:同示例 1,需 websuite.pbl 与 PBIDEA 运行库;本例额外用到 uo_file.BlobToFile(websuite.pbl,真实存在)把 blob 写盘。
步骤:把下方代码贴入按钮 clicked 事件,运行后可在 MessageBox 看到下载字节数。 - // 示例输入:远程文件地址(这里以下载一个 PDF 为例)
- string ls_url
- ls_url = 'https://example.com/docs/report.pdf'
- // 示例输入:本机保存路径
- string ls_save
- ls_save = 'C:\temp\report.pdf'
- // 内存中的文件内容
- blob lbl_data
- // HTTP 客户端对象
- uo_httpclient hc
- hc = create uo_httpclient
- // 文件工具对象(PBIDEA 自带,BlobToFile 真实存在)
- uo_file uf
- uf = create uo_file
- // 写盘结果
- boolean lb_ok
- lb_ok = false
- // 一参(URL)+ 二参(TRUE 触发进度事件):内容读进 blob,不落盘
- lbl_data = hc.DownLoad(ls_url, TRUE)
- if Len(lbl_data) > 0 then
- // 把 blob 写成文件
- lb_ok = uf.BlobToFile(lbl_data, ls_save)
- MessageBox('下载完成', '字节数:' + String(Len(lbl_data)))
- destroy uf
- else
- MessageBox('下载失败', '返回内容为空,请检查 URL 与网络')
- end if
- destroy hc
复制代码
四、边界情况与易错点
| 易错点 | 现象 / 后果 | 正确处理 | | bTriggerEvent 没传 TRUE | 下载能成功,但 ondownload / onfinish 永不触发,进度条不动 | 用三参 DownLoadFile(url, path, TRUE) 或两参 DownLoad(url, TRUE) | | 保存路径目录不存在 | DownLoadFile 返回 FALSE,文件没生成 | 先 uo_dir / DirectoryExists 建好目录再下载 | | 单参 DownLoadFile(url) | 落到当前目录 .\download\,路径不在你预期的位置 | 明确传第二参完整路径,别依赖默认目录 | | 在 UI 线程直接下载大文件 | 函数同步阻塞,下载期间窗口"假死"、进度条不重绘 | 把下载丢到 uo_thread 后台线程(见第六节),或至少提示用户不要操作 | | ondownload 返回 FALSE 忘了处理 | 下载被中止却没提示,用户以为卡住 | onfinish 的 success 参数能反映是否成功,用它统一收尾 | | 忘了 destroy hc | 每次下载都留一个 WinHTTP 句柄,长期运行句柄泄漏 | 下载结束后 destroy hc(示例均已包含) | | 代理 / 超时没设 | 内网下载超时无反馈、或走不通代理 | 下载前调用 SetProxy / SetTimeout(毫秒) |
额外提醒:两个字节数参数是 double,显示时用 String(...) 即可;百分比 ai_percent 是 integer,直接赋给进度条 position 就行,无需换算。
五、与其它下载方案对比
| 方案 | 对象 | 特点 | 何时选 | | HTTP 下载(本文) | uo_httpclient | 原生 WinHTTP,支持进度事件、代理、鉴权,最通用 | 绝大多数从 http/https 拉文件 | | 带进度对话框的下载器 | uo_downloader(继承 uo_curl) | 自带 uo_progress 悬浮进度窗 + 异步消息(onfilestart/onprogressdown/onfileend),开箱即用 | 想少写代码、要现成进度 UI | | 底层可定制下载 | uo_curl | uo_downloader 的父类,能力最全但需自己接事件 | 需要 curl 级控制(断点、限速等) | | FTP / SFTP 文件传输 | uo_ftp / uo_sftp | 走 FTP/SFTP 协议,不是 HTTP | 服务器只提供 FTP/SFTP 时 |
个人经验:如果只是"下一个文件并显示进度",uo_httpclient 三参 DownLoadFile + 子类事件已经足够;如果连进度对话框都不想自己画,uo_downloader.download(title, url, path) 一行搞定(它内部用 uo_progress 弹窗 + 异步消息,WaitAsyncMessageFinish 等下载完成)。但要记住 uo_downloader 底层是 uo_curl,与 uo_httpclient 不是同一个对象,不要混用 API。
六、进阶扩展
1. 后台下载,界面不卡(推荐做法)
DownLoadFile 是同步的,大文件会阻塞 UI 线程。把下载丢进 uo_thread 后台线程,主线程照常响应;进度通过线程安全的全局变量 / 消息回传主线程刷新进度条。要点:
- 下载函数体放在一个 NVO 的方法里,用 uo_thread_factory.StartThread("uo_downloader_job", "of_download", ...) 启动;
- 跨线程只传标量(url、路径、百分比),不要跨线程直接操作窗口控件;
- 主线程用 Timer 或自定义消息定时读取"已下字节数"刷新进度条。
2. 取消下载
在 ondownload 事件里判断一个实例变量(如 ib_cancel),用户点"取消"按钮时把它置 TRUE,事件返回 FALSE 即可中止当前下载。
3. 带鉴权 / 自定义头
下载私有仓库或内网接口前,先 hc.SetBasicAuth('user','pass') 或 hc.SetHeader('Authorization', 'Bearer ' + ls_token),再调 DownLoadFile。头要在下载函数之前设置好。
4. 关于断点续传
uo_httpclient 的公开 API 里没有暴露"断点续传 / Range 续传"接口。真要做续传,只能自己 SetHeader('Range', 'bytes=' + 已下字节 + '-') 并用 DownLoad 取剩余片段再拼接——但服务器是否支持 Range、PbIdea 是否透传该头都需要实测,不保证可用,生产环境慎用。需要稳定的断点续传,建议改用 uo_curl 体系或自己用 uo_file 分块读写实现。
小结:uo_httpclient 做大文件下载的核心是"三参 DownLoadFile(url, path, TRUE) + 子类 ondownload 事件":事件给进度、同步给结果、返回 FALSE 给取消。记住设好保存目录、及时 destroy、大文件放后台线程,下载功能就稳了。 |