引言
本文聚焦 DouPHP 仓库中与“HTTP 客户端流式下载”相关的实现,覆盖两类典型场景:
- 面向浏览器或外部调用方的“远端二进制流式输出”,即服务端作为代理,从上游镜像拉取 ZIP 等二进制包并直接流式写出到响应。
- 面向后台安装流程的“大体积安装包流式落盘”,即通过 HTTP 客户端将远端包直接写入本地临时文件,再进行完整性校验与重试。
这些能力由轻量 HTTP 客户端、上游二进制拉流工具类以及多个业务 Service 共同组成,既保证大文件不占用 PHP 进程内存,又提供鉴权、镜像选择、错误分类和重试等工程化保障。
项目结构定位
与 HTTP 客户端流式下载相关的关键代码分布在以下位置:
- 通用 HTTP 客户端:
core/web/http/Client.php - 云服务 API 上游二进制拉流工具:
_'.api/lib/UpstreamHttp.php - 系统安装包与扩展包流式下载服务:
_'.api/service/SystemPackageService.php、_'.api/service/ZipStreamDownloadService.php - V1 冻结入口专用下载服务:
_'.api/v1/service/DownloadService.php - V1 下载入口脚本:
_'.api/v1/dou.php、_'.api/v1/onedou.php - 后台安装流程中的流式落盘与完整性校验:
admin/service/cloud/InstallService.php
graph TB
Client["Dou\\Core\\Web\\Http\\Client<br/>轻量 HTTP 客户端"]
UpstreamHttp["Api\\Lib\\UpstreamHttp<br/>上游二进制拉流"]
SystemPackage["Api\\Service\\SystemPackageService<br/>系统安装包流式下载"]
ZipStream["Api\\Service\\ZipStreamDownloadService<br/>扩展/包流式下载共享实现"]
DownloadV1["Api\\V1\\Service\\DownloadService<br/>V1 冻结入口下载服务"]
DouEntry["_'.api/v1/dou.php<br/>已购版本下载入口"]
OnedouEntry["_'.api/v1/onedou.php<br/>免登下载入口"]
InstallService["admin/service/cloud/InstallService<br/>后台安装流式落盘"]
Client --> InstallService
UpstreamHttp --> SystemPackage
UpstreamHttp --> ZipStream
UpstreamHttp --> DownloadV1
DouEntry --> DownloadV1
OnedouEntry --> DownloadV1
SystemPackage --> UpstreamHttp
ZipStream --> UpstreamHttp
图表来源
- core/web/http/Client.php:21-67
- _'/.api/lib/UpstreamHttp.php:17-26
- _'/.api/service/SystemPackageService.php:24-38
- _'/.api/service/ZipStreamDownloadService.php:29-50
- _'/.api/v1/service/DownloadService.php:31-46
- _'/.api/v1/dou.php:1-49
- _'/.api/v1/onedou.php:1-24
- admin/service/cloud/InstallService.php:1235-1249
章节来源
- core/web/http/Client.php:21-67
- _'/.api/lib/UpstreamHttp.php:17-26
- _'/.api/service/SystemPackageService.php:24-38
- _'/.api/service/ZipStreamDownloadService.php:29-50
- _'/.api/v1/service/DownloadService.php:31-46
- _'/.api/v1/dou.php:1-49
- _'/.api/v1/onedou.php:1-24
- admin/service/cloud/InstallService.php:1235-1249
核心组件总览
- 轻量 HTTP 客户端
Client:封装 cURL,支持 GET/POST、SSL 校验控制、最大字节限制、DNS 解析绑定,以及最重要的“流式落盘”选项stream_to。 - 上游二进制拉流工具
UpstreamHttp:以 GET 方式拉取上游二进制,在确认 HTTP 2xx 后设置下载头并逐块写出,避免整包进入 PHP 内存。 - 系统安装包服务
SystemPackageService:校验 mode 与 id,拼接官方镜像 URL,并通过UpstreamHttp流式输出。 - 扩展包流式下载服务
ZipStreamDownloadService:复用扩展授权、站点匹配、免费/付费上游选择逻辑,最终同样委托UpstreamHttp流式输出。 - V1 下载服务
DownloadService:为_local_api/v1/extend.php、dou.php、onedou.php提供兼容旧实现的鉴权与流式输出。 - 后台安装服务
InstallService:使用Client::request的stream_to将大体积升级包直接写入本地缓存文件,并进行 ZIP 签名、Content-Length 比对、ZipArchive 可读性检查与自动重试。
章节来源
- core/web/http/Client.php:21-67
- _'/.api/lib/UpstreamHttp.php:17-26
- _'/.api/service/SystemPackageService.php:24-38
- _'/.api/service/ZipStreamDownloadService.php:29-50
- _'/.api/v1/service/DownloadService.php:31-46
- admin/service/cloud/InstallService.php:1235-1249
架构总览
整体架构分为三层:
- 接入层:V1 入口脚本与新一代控制器/路由触发具体 Service。
- 业务层:Service 负责参数校验、权限判断、上游 URL 拼装、错误码映射。
- 传输层:
Client与UpstreamHttp分别承担“本地流式落盘”和“远端二进制流式输出”。
sequenceDiagram
participant Browser as "浏览器或调用方"
participant Entry as "V1 入口脚本"
participant Service as "下载 Service"
participant Upstream as "UpstreamHttp"
participant Mirror as "上游镜像服务器"
Browser->>Entry : "请求 dou/onedou 或 system-package 下载"
Entry->>Service : "鉴权、解析包标识、选择上游"
Service->>Upstream : "streamBinaryResponse(源URL, 文件名)"
Upstream->>Mirror : "GET 二进制流"
Mirror-->>Upstream : "HTTP 2xx + 二进制数据块"
Upstream-->>Browser : "设置下载头并流式写出"
图表来源
- _'/.api/v1/dou.php:1-49
- _'/.api/v1/onedou.php:1-24
- _'/.api/service/SystemPackageService.php:38-73
- _'/.api/service/ZipStreamDownloadService.php:167-201
- _'/.api/lib/UpstreamHttp.php:26-117
详细组件分析
轻量 HTTP 客户端 Client
Client 是 DouPHP 主站侧的轻量 HTTP 客户端,核心职责包括:
- 统一封装 GET/POST 与通用 request。
- 支持超时、连接超时、SSL 校验开关、CA 证书包路径。
- 支持最大响应体大小限制
max_bytes,用于拦截超大响应。 - 支持 DNS 解析绑定
resolve,增强安全与稳定性。 - 支持“流式落盘”
stream_to,将响应体直接写入文件句柄,避免整包进入 PHP 字符串内存。
关键行为要点:
- 当启用
stream_to时,cURL 使用CURLOPT_FILE直接写入文件,而不是累积到$responseBody。 - 返回元数据模式
return_meta下,会附带http_code、errno、content_type、too_large、streamed、size、content_length等字段。 - 对 PHP 8.0 以下版本显式关闭 cURL 句柄,避免资源泄漏。
flowchart TD
Start["进入 Client::request"] --> ParseOptions["解析 options<br/>timeout / connect_timeout / verify_ssl / max_bytes / stream_to"]
ParseOptions --> CurlInit["初始化 cURL"]
CurlInit --> SetUrl["设置 URL、方法、请求头"]
SetUrl --> SslConfig["配置 SSL 校验与 CA 证书"]
SslConfig --> StreamMode{"是否启用 stream_to?"}
StreamMode --> |是| FileWrite["打开文件句柄<br/>CURLOPT_FILE 流式落盘"]
StreamMode --> |否| MaxBytesCheck{"是否启用 max_bytes?"}
MaxBytesCheck --> |是| WriteFunction["WRITEFUNCTION 累计 body 并限制大小"]
MaxBytesCheck --> |否| DefaultReturn["默认返回完整 body"]
FileWrite --> Exec["执行 curl_exec"]
WriteFunction --> Exec
DefaultReturn --> Exec
Exec --> MetaBuild["构建返回结果或元数据"]
MetaBuild --> End["结束"]
图表来源
- core/web/http/Client.php:67-187
- core/web/http/Client.php:189-227
章节来源
- core/web/http/Client.php:21-67
- core/web/http/Client.php:67-187
- core/web/http/Client.php:189-227
上游二进制拉流工具 UpstreamHttp
UpstreamHttp 专为云服务 API 设计,用于从官方镜像拉取 ZIP 等二进制文件,并以 HTTP 响应形式流式写出。其特点包括:
- 使用 cURL GET 拉流,禁用 SSL 对端校验,与主站
Client保持一致策略。 - 通过
HEADERFUNCTION捕获初始状态码,通过WRITEFUNCTION分块写出。 - 仅在确认 HTTP 2xx 后才发送
Content-Type: application/octet-stream与Content-Disposition下载头。 - 对非 2xx、cURL 错误、空响应等情况返回结构化结果,便于上层 Service 映射为 404/502。
flowchart TD
UStart["进入 streamBinaryResponse"] --> Validate["校验 URL 与 curl_init"]
Validate --> CurlSetup["设置 GET、跟随重定向、SSL 关闭、User-Agent"]
CurlSetup --> HeaderCallback["HEADERFUNCTION 捕获状态码"]
HeaderCallback --> WriteCallback["WRITEFUNCTION 分块写出"]
WriteCallback --> CheckCode{"HTTP 是否为 2xx?"}
CheckCode --> |否| Abort["标记中止并返回失败"]
CheckCode --> |是| SendHeaders["发送下载头"]
SendHeaders --> EchoChunk["echo 数据块"]
EchoChunk --> ExecDone["curl_exec 完成"]
ExecDone --> Finalize["根据最终状态码与 errno 返回 ok/upstream_http_code/curl_errno"]
图表来源
- _'/.api/lib/UpstreamHttp.php:26-117
章节来源
- _'/.api/lib/UpstreamHttp.php:17-26
- _'/.api/lib/UpstreamHttp.php:26-117
系统安装包服务 SystemPackageService
SystemPackageService 负责系统级安装包(install/update/patch)的流式下载:
- 校验 mode 是否在允许集合中。
- 校验 id 是否为唯一标识。
- 读取
install_download.upstream_free_base配置。 - 使用
InstallMirrorZipUrl::systemZip生成上游 ZIP URL。 - 调用
UpstreamHttp::streamBinaryResponse流式输出,并将上游 404/502 映射为对应 HTTP 状态。
classDiagram
class SystemPackageService {
+MODES : array
+streamMirrorByModeAndId(mode, id) void
}
class InstallMirrorZipUrl {
+systemZip(base, mode, id) string
}
class UpstreamHttp {
+streamBinaryResponse(url, filename) array
}
SystemPackageService --> InstallMirrorZipUrl : "生成上游 ZIP URL"
SystemPackageService --> UpstreamHttp : "流式输出二进制"
图表来源
- _'/.api/service/SystemPackageService.php:24-73
章节来源
- _'/.api/service/SystemPackageService.php:24-73
扩展包流式下载服务 ZipStreamDownloadService
ZipStreamDownloadService 提供扩展、dou、onedou 包的共享流式下载逻辑:
resolveExtendSource:根据扩展 slug、用户凭据、价格与授权判定免费或付费上游。resolveAuthorizedBundle:校验用户登录、站点授权、bucket 类型,返回 dou/onedou 包的上游 URL。streamOnedouZip:免登下载,仅校验 id 字母格式。stream:统一委托UpstreamHttp::streamBinaryResponse流式输出。
classDiagram
class ZipStreamDownloadService {
-db
-userService
-extendService
+resolveExtendSource(slug, user, password) array
+resolveAuthorizedBundle(cloudId, user, password, url, bucket) array
+streamOnedouZip(id) void
+stream(sourceUrl, filename) void
-upstreamBase(tier) string
}
class UpstreamHttp {
+streamBinaryResponse(url, filename) array
}
ZipStreamDownloadService --> UpstreamHttp : "流式输出二进制"
图表来源
- _'/.api/service/ZipStreamDownloadService.php:29-50
- _'/.api/service/ZipStreamDownloadService.php:61-212
章节来源
- _'/.api/service/ZipStreamDownloadService.php:29-50
- _'/.api/service/ZipStreamDownloadService.php:61-212
V1 下载服务 DownloadService
DownloadService 是 V1 冻结入口专用的下载服务,职责包括:
- 账号校验、购买/授权/VIP 权益校验。
- 按上游配置选择真实文件 URL。
- 流式输出远端文件到客户端。
- 对 onedou 提供免登直放能力。
sequenceDiagram
participant DouEntry as "dou.php"
participant OnedouEntry as "onedou.php"
participant DownloadService as "DownloadService"
participant UpstreamHttp as "UpstreamHttp"
DouEntry->>DownloadService : "resolveAuthorizedBundle(...)"
DownloadService-->>DouEntry : "{ok, url, filename}"
DouEntry->>DownloadService : "stream(url, filename)"
OnedouEntry->>DownloadService : "streamOnedouZip(id)"
DownloadService->>UpstreamHttp : "streamBinaryResponse(url, filename)"
图表来源
- _'/.api/v1/dou.php:1-49
- _'/.api/v1/onedou.php:1-24
- _'/.api/v1/service/DownloadService.php:163-203
章节来源
- _'/.api/v1/service/DownloadService.php:1-46
- _'/.api/v1/service/DownloadService.php:163-203
- _'/.api/v1/dou.php:1-49
- _'/.api/v1/onedou.php:1-24
后台安装流式落盘 InstallService
InstallService 的 downloadFile 与 attemptStreamDownload 展示了“本地流式落盘 + 完整性校验 + 自动重试”的完整流程:
- 使用
Client::request的stream_to将远端包直接写入本地缓存文件。 - 通过
return_meta获取http_code、size、content_length。 - 先判断是否为 ZIP 归档签名,再比对 Content-Length,最后用 ZipArchive 检查尾部中央目录是否完整。
- 对瞬时网络错误、截断、空响应进行最多三次重试,每次退避 0.4 秒。
flowchart TD
IStart["进入 downloadFile"] --> Prepare["清理旧包、构造 POST 数据"]
Prepare --> Loop["循环尝试下载"]
Loop --> Attempt["attemptStreamDownload"]
Attempt --> ClientReq["Client::request(stream_to=saveFile)"]
ClientReq --> MetaCheck{"meta 是否有效?"}
MetaCheck --> |否| CurlError["返回 curl_error"]
MetaCheck --> |是| HttpCheck{"HTTP 是否为 2xx?"}
HttpCheck --> |否| Classify["分类上游错误"]
HttpCheck --> |是| ZipHead["ZIP 头部签名判定"]
ZipHead --> SizeCheck{"Content-Length 是否一致?"}
SizeCheck --> |否| Truncated["truncated_package"]
SizeCheck --> |是| ZipReadable["ZipArchive 可读性检查"]
ZipReadable --> Readable{"是否可读?"}
Readable --> |否| Truncated
Readable --> |是| Success["返回保存路径"]
Classify --> RetryCheck{"是否可重试?"}
RetryCheck --> |是| Backoff["退避重试"]
RetryCheck --> |否| Fail["返回 false"]
图表来源
- admin/service/cloud/InstallService.php:1235-1357
- admin/service/cloud/InstallService.php:1360-1434
章节来源
- admin/service/cloud/InstallService.php:1235-1357
- admin/service/cloud/InstallService.php:1360-1434
依赖关系分析
Client是底层传输抽象,被InstallService直接调用,用于本地流式落盘。UpstreamHttp是云服务 API 侧的二进制拉流工具,被SystemPackageService、ZipStreamDownloadService、DownloadService共同复用。- 各 Service 之间保持解耦:V1 的
DownloadService与新一代ZipStreamDownloadService互不引用,避免历史代码与新代码耦合。 - 上游镜像 URL 由
InstallMirrorZipUrl统一拼装,Service 只关心 mode/bucket/slug 与 tier。
graph LR
Client["Client"] --> InstallService["InstallService"]
UpstreamHttp["UpstreamHttp"] --> SystemPackage["SystemPackageService"]
UpstreamHttp --> ZipStream["ZipStreamDownloadService"]
UpstreamHttp --> DownloadV1["DownloadService"]
InstallMirrorZipUrl["InstallMirrorZipUrl"] --> SystemPackage
InstallMirrorZipUrl --> ZipStream
图表来源
- core/web/http/Client.php:21-67
- _'/.api/lib/UpstreamHttp.php:17-26
- _'/.api/service/SystemPackageService.php:24-73
- _'/.api/service/ZipStreamDownloadService.php:29-212
- _'/.api/v1/service/DownloadService.php:31-203
- admin/service/cloud/InstallService.php:1235-1357
章节来源
- core/web/http/Client.php:21-67
- _'/.api/lib/UpstreamHttp.php:17-26
- _'/.api/service/SystemPackageService.php:24-73
- _'/.api/service/ZipStreamDownloadService.php:29-212
- _'/.api/v1/service/DownloadService.php:31-203
- admin/service/cloud/InstallService.php:1235-1357
性能与内存特性
- 避免整包进入 PHP 内存:
Client的stream_to与UpstreamHttp的 WRITEFUNCTION 都采用流式写入,适合大体积 ZIP 包。 - 可控超时与连接超时:
Client支持timeout与connect_timeout;UpstreamHttp设置较长超时以适应镜像拉流。 - 最大响应体限制:
Client支持max_bytes,可在需要时提前中断过大响应。 - 完整性校验前置:
InstallService在下载后立即进行 ZIP 头部签名、Content-Length 比对与 ZipArchive 可读性检查,避免后续解压阶段才暴露问题。 - 自动重试与退避:对瞬时网络错误、截断、空响应进行有限次重试,降低偶发失败影响。
错误处理与故障诊断
常见错误与处理方式:
- 上游 404:
UpstreamHttp返回失败,Service 映射为 404,提示upstream_not_found。 - 上游不可用:Service 映射为 502,提示
upstream_unavailable。 - 上游未配置:Service 返回 500,提示
upstream_misconfigured。 - cURL 错误:
Client返回errno与error,InstallService将其归类为curl_error并决定是否重试。 - 压缩包截断:
InstallService通过 Content-Length 与 ZipArchive 可读性检查识别truncated_package。 - 无效参数:mode/id/slug 校验失败时直接返回 400 或业务错误码。
建议排查步骤:
- 确认上游镜像域名是否正确配置。
- 检查 HTTP 状态码是否为 2xx。
- 查看
Client或UpstreamHttp返回的errno、error、upstream_http_code。 - 对于后台安装失败,检查落盘文件是否存在、文件大小是否与 Content-Length 一致、ZipArchive 是否能打开。
- 观察是否触发重试逻辑,以及最后一次失败详情。
章节来源
- _'/.api/lib/UpstreamHttp.php:90-117
- _'/.api/service/SystemPackageService.php:55-73
- _'/.api/service/ZipStreamDownloadService.php:189-201
- _'/.api/v1/service/DownloadService.php:191-203
- admin/service/cloud/InstallService.php:1310-1357
结论
DouPHP 的 HTTP 客户端流式下载能力围绕两个核心目标展开:
- 对外流式输出:通过
UpstreamHttp将上游镜像的二进制包直接流式写出到客户端,避免中间缓存与内存压力。 - 对内流式落盘:通过
Client的stream_to将大体积安装包写入本地文件,并结合 ZIP 签名、Content-Length 与 ZipArchive 校验,配合自动重试提升可靠性。
这种分层设计使得不同入口(V1 冻结入口、新一代 API、后台安装流程)可以复用统一的流式传输能力,同时保持业务逻辑清晰、错误处理明确、性能表现稳定。