文档目录
HTTP客户端流式下载功能

引言

本文聚焦 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 或业务错误码。

建议排查步骤:

  1. 确认上游镜像域名是否正确配置。
  2. 检查 HTTP 状态码是否为 2xx。
  3. 查看 Client 或 UpstreamHttp 返回的 errno、error、upstream_http_code。
  4. 对于后台安装失败,检查落盘文件是否存在、文件大小是否与 Content-Length 一致、ZipArchive 是否能打开。
  5. 观察是否触发重试逻辑,以及最后一次失败详情。

章节来源

  • _'/.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、后台安装流程)可以复用统一的流式传输能力,同时保持业务逻辑清晰、错误处理明确、性能表现稳定。

添加日期:2026-10-05