Dagger TypeScript SDK 的 ClientHttpOpts 类型详解:用 client.http 安全高效地抓取远程文件
【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger
导读
ClientHttpOpts是 Dagger TypeScript SDK 中client.http()方法的可选参数类型,用于定制 HTTP 远程文件下载行为。通过它可以控制下载文件的文件名与权限、注入Authorization认证头、以及在下载前自动启动依赖的 Service。本文以 docs/versioned_docs/version-0.19/reference/typescript/api/client.gen/type-aliases/ClientHttpOpts.md 为骨架,结合仓库源码(sdk/typescript/src/api/client.gen.ts、core/schema/http.go、core/http.go)与集成测试(core/integration/http_test.go),完整讲解每个选项的语义、默认值与底层实现,并给出可直接运行的实战示例。
一、ClientHttpOpts 是什么
在 Dagger 中,client.http(url, opts?)是 Client 上的一个快捷方法,返回一个File对象,该对象封装了从远程 URL 下载的内容(见 sdk/typescript/src/api/client.gen.ts):
http = (url: string, opts?: ClientHttpOpts): File => { const ctx = this._ctx.select("http", { url, ...opts }) return new File(ctx) }ClientHttpOpts就是传给该方法的opts参数的类型别名,定义为object。它的实际用途不止“下载一个文件”——它还承担了认证、文件名控制、权限设置,以及面向私有网络/动态端口的 Service 前置启动等职责。
字段一览(version-0.19 文档)
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
authHeader | Secret(当前版本相对路径见 docs/versioned_docs/version-0.19/reference/typescript/api/client.gen/classes/Secret.md) | 否 | 用于填充 HTTPAuthorization请求头的 Secret |
experimentalServiceHost | Service(见 docs/versioned_docs/version-0.19/reference/typescript/api/client.gen/classes/Service.md) | 否 | 在抓取 URL 之前必须启动的一个 Service |
name | string | 否 | 下载文件的文件名,默认取 URL 的最后一段路径 |
permissions | number | 否 | 设置到下载文件上的权限位 |
说明:当前仓库的
ClientHttpOpts类型(sdk/typescript/src/api/client.gen.ts)在以上四个字段之外还包含一个checksum字段(期望内容摘要,如"sha256:..."),这是文档化之外的后续版本扩展;本文以 version-0.19 文档为基线展开,checksum相关内容会以“从源码可见”的方式单独标注。
二、name:控制下载文件的文件名
语义与默认值
name?: string指定下载内容在引擎文件系统中落盘时的文件名。默认为 URL 的最后一段路径。
默认值的推导逻辑在 core/schema/http.go 的httpPath中:
func (s *httpSchema) httpPath(args httpArgs) (string, error) { if args.Name.Valid { return string(args.Name.Value), nil } parsed, err := url.Parse(args.URL) if err != nil { return "", err } filename := filepath.Base(parsed.Path) if filename == "" || filename == "." || filename == "/" { filename = "index" } return filename, nil }这里有几个值得注意的行为:
- 文件名取自URL 的路径部分(path),不包含查询参数(query string)。例如
https://example.com/pkg/foo.tar.gz?download=1会得到foo.tar.gz。 - 如果 URL 路径为空或以
/结尾(无法解析出文件名),则回退为index。 - 一旦显式传入
name,则完全覆盖上述推导结果。
实战示例
import { connect } from "@dagger.io/dagger" connect(async (client) => { // 默认名:取 URL 最后一段路径,即 README.md const f1 = client.http("https://raw.githubusercontent.com/dagger/dagger/main/README.md") console.log(await f1.name()) // => "README.md" // 显式指定文件名,覆盖默认值 const f2 = client.http("https://raw.githubusercontent.com/dagger/dagger/main/README.md", { name: "LICENSE-NOTICE.md", }) console.log(await f2.name()) // => "LICENSE-NOTICE.md" // 路径以斜杠结尾时,默认回退为 index const f3 = client.http("https://example.com/some/dir/") console.log(await f3.name()) // => "index" })对应的集成测试见 core/integration/http_test.go:测试覆盖了默认文件名、显式Name: "FooBar.md"、以及Name: "FooBar.md.x"三种场景,均与预期一致。
三、permissions:为下载文件设置权限位
语义与默认值
permissions?: number指定最终文件的 Unix 权限位(mode bits)。默认值为0600(仅属主可读写),这一默认值在两个地方得到印证:
- Schema 层:core/schema/http.go 中
permissions := int(args.Permissions.GetOr(dagql.Int(0600))); - 核心层:core/http.go 中
httpStateCanonicalPermissions = 0o600用于内部规范快照写入。
底层实现
权限是在下载内容写入快照之后、提交之前通过os.Chmod应用的,见 core/http.go:
if err := os.Chmod(dst, os.FileMode(permissions)); err != nil { return TrimErrPathPrefix(err, root) }这意味着权限位会被忠实写入最终文件,并体现在后续所有消费该File的步骤中(例如复制进容器、作为构建输入等)。
实战示例
import { connect } from "@dagger.io/dagger" connect(async (client) => { const url = "https://raw.githubusercontent.com/dagger/dagger/main/README.md" // 权限 0765:属主 rwx、组 rw-、其他 r-x const execFile = client.http(url, { permissions: 0o765 }) // 权限 0764:属主 rwx、组 rw-、其他 r-- const readFile = client.http(url, { permissions: 0o764 }) // 复制进容器后验证权限位 const stat = await client.container() .from("alpine") .withFile("/target", execFile) .withExec(["stat", "-c", "%a", "/target"]) .stdout() console.log(stat.trim()) // => "765" })core/integration/http_test.go 的TestHTTPPermissions用同样方式验证了0765与0764两种权限在容器内的实际落地结果。如果你后续要把文件作为可执行脚本运行,请务必通过本参数显式授予执行位(例如0o755),否则默认0600会让脚本无法执行。
四、authHeader:用 Secret 注入 Authorization 头
语义
authHeader?: Secret指定一个 DaggerSecret,其明文会被写入 HTTP 请求的Authorization头。典型场景是下载需要鉴权的资源,例如私有仓库的构建产物、带 token 的制品下载链接等。
为什么是 Secret 而不是字符串
类型要求是Secret而非普通字符串,这是刻意的安全设计:
- 调用方通过
client.setSecret(name, value)创建 Secret,引擎以 Secret 对象传递,避免在 DAG 日志、trace 与缓存键中泄露明文; - 服务端只有到真正发起 HTTP 请求时才读取其明文,见 core/schema/http.go:
if args.AuthHeader.Valid { secret, err := args.AuthHeader.Value.Load(ctx, srv) // ... authHeaderRaw, err := secret.Self().Plaintext(ctx) authHeader = string(authHeaderRaw) }明文随后被放入FetchHTTPRequestOpts.AuthorizationHeader,最终写入请求头(core/http.go):
if opts.AuthorizationHeader != "" { req.Header.Set("Authorization", opts.AuthorizationHeader) }实战示例
import { connect } from "@dagger.io/dagger" connect(async (client) => { const token = client.setSecret("AUTH_TOKEN", "personalsecret") // 构造 Basic 认证头 const basic = "Basic " + Buffer.from("x-access-token:personalsecret").toString("base64") const authHeader = client.setSecret("AUTH_HEADER", basic) const file = client.http("https://private.example.com/artifacts/release.tar.gz", { authHeader, name: "release.tar.gz", }) // 以挂载方式让容器消费该文件 const out = await client.container() .from("alpine") .withFile("/downloads/release.tar.gz", file) .withExec(["tar", "-tzf", "/downloads/release.tar.gz"]) .stdout() console.log(out) })core/integration/http_test.go 的TestHTTPAuth完整演示了这一流程:不带认证头访问受保护端点返回401 Unauthorized,带上由c.SetSecret构造的 Basic 认证头后成功取得内容。
提示:虽然你可以把 URL 中的用户信息(
user:pass@host)拼进链接,但用authHeader更安全可控,因为它不把凭据暴露在 URL 中(URL 会进入缓存键与日志)。
五、experimentalServiceHost:抓取前先启动一个 Service
语义
experimentalServiceHost?: Service指定一个在发起 HTTP 抓取之前必须启动的 Service。它解决的是“URL 指向当前会话内动态创建的临时服务”这一场景——例如测试中起的一个本地 HTTP 服务器,其端口由引擎动态分配,只有先通过Host().Service()或容器asService()把它纳入引擎的服务网络,URL 才能被解析。
底层调用链
该选项触发的是与普通抓取不同的执行路径:
- Schema 层检测到
ExperimentalServiceHost有效后,进入resolveHTTPSessionContext(core/schema/http.go):解析 Service 内容摘要、获取其Hostname,构造ServiceBinding并调用svcs.StartBindings启动服务并绑定主机名; - 随后调用
core.FetchHTTPFile(core/http.go)完成实际下载,此时请求就能命中该 Service 提供的主机名; - 请求完成后通过
detach释放绑定。
实战示例(动态端口的本地服务)
import { connect } from "@dagger.io/dagger" connect(async (client) => { // 1. 启动一个容器化的 HTTP 服务(内部监听 8080) const svc = client.container() .from("python:3.12-slim") .withExec(["python", "-m", "http.server", "8080"]) .withExposedPort(8080) .asService() // 2. 从该服务抓取文件;此时主机名/端口是引擎动态分配的, // 必须通过 experimentalServiceHost 先启动它 const file = client.http("http://localhost:8080/hello.txt", { experimentalServiceHost: svc, }) console.log(await file.contents()) // => 服务返回的文本内容 })对应测试见 core/integration/http_test.go(TestHTTPService)与TestHTTPChecksum:它们先构造一个返回固定内容的测试 HTTP 服务,再把 Service 传给HTTPOpts.ExperimentalServiceHost完成抓取。此外 TestHTTPCachePerSessions 还利用该选项配合计数器服务验证了“同一会话内只请求一次、跨会话重新请求”的缓存行为。
六、组合使用与引擎侧缓存机制
组合示例
四个选项可以任意组合,典型的生产用法如下:
import { connect } from "@dagger.io/dagger" connect(async (client) => { const svc = client.container() .from("my-ci-image") .withExposedPort(8443) .asService() const file = client.http("https://svc.internal:8443/binaries/cli", { experimentalServiceHost: svc, // 先启动内部服务 authHeader: client.setSecret("TOKEN", "s3cr3t"), // 带上认证 name: "cli", // 重命名 permissions: 0o755, // 授予可执行权限 }) await file.export("/tmp/cli") })抓取与缓存如何工作(理解这些选项的前提)
从 core/http.go 的实现可以看到,Dagger 的 HTTP 抓取并非简单的“每次重下”:
- 条件请求:
Resolve在发起 GET 时会携带上次会话保存的If-None-Match(ETag)或If-Modified-Since(Last-Modified),服务端返回304 Not Modified时直接复用已缓存快照(core/http.go); - 内容摘要:下载内容以 SHA-256 摘要记录在
HTTPState.ContentDigest中,并参与结果对象的内容摘要计算(core/schema/http.go),从而让下游缓存正确失效; - 文件时间戳:写入快照时会把
Last-Modified解析为文件 mtime(core/http.go),保证产物可复现。
理解这一机制有助于正确使用本文的选项:例如name与permissions都参与结果内容摘要(filePath、permissions被哈希进outputDigest),因此改变它们会改变结果标识,但不会重复下载网络内容(只要内容摘要与 ETag 未变,仍走缓存快照路径)。
七、小结
ClientHttpOpts是 Dagger TypeScript SDK 中client.http()的四个(当前源码中为五个)可选配置项的类型化入口:
- name:控制文件名,默认取 URL 路径最后一段,空路径回退为
index; - permissions:控制文件权限,默认
0600,写文件后由os.Chmod落地; - authHeader:以 Secret 形式注入
Authorization头,避免凭据进日志与缓存; - experimentalServiceHost:抓取前先启动指定 Service,适配动态端口/私有网络内的 HTTP 源;
- (当前源码扩展)checksum:校验下载内容摘要,不匹配即报错中止。
这些选项在 core/schema/http.go 的 GraphQL Schema 与 core/http.go 的核心实现中均有对应的参数与逻辑,且每个行为都由 core/integration/http_test.go 中的集成测试覆盖验证。需要继续深入时,可以对照阅读 docs/versioned_docs/version-0.19/reference/typescript/api/client.gen/classes/Client.md 中的http方法签名,以及Secret、Service两个相关类的文档。
【免费下载链接】daggerAutomation engine to build, test and ship any codebase. Runs locally, in CI, or directly in the cloud项目地址: https://gitcode.com/GitHub_Trending/da/dagger
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考