Dagger GraphQL API 详解:http 查询如何把远程 URL 变成可缓存的文件对象
【免费下载链接】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
Dagger 的 GraphQL API 中,http是一个把远程 HTTP(S) URL 直接拉取为 Dagger 文件对象(File)的顶层查询。本文以 GraphQL API 参考文档中的标准示例查询docs/docs-graphql/data/examples/queries/http/gql.md为主体,结合 schema 定义、核心实现 与 集成测试,完整讲解该查询的字段、全部可选参数(url、name、permissions、checksum、authHeader、experimentalServiceHost)、底层"快照缓存 + ETag/Last-Modified 增量校验"机制,以及它在构建流水线中的实际用法。读完后你既能读懂这条 GraphQL 查询,也能理解 Dagger 引擎是如何保证下载可缓存、可校验、可复用的。
标准示例查询:一次请求,拿到文件与内容
GraphQL API 参考文档中为http查询提供的标准示例如下(来自 示例文件,该文件由 spectaql 示例加载脚本 按查询名data/examples/queries/http/gql.md读取并渲染到 API 参考站点中):
query { http(url: "http://dagger.io") { size contents } }这条查询做三件事:
- 从
Query根对象调用http字段,传入url参数"http://dagger.io"; - 得到返回的
File对象(Dagger 中表示单个文件的对象类型); - 选出该文件的两个标量字段:
size:文件字节数(Int);contents:文件全文内容(String)。
在 schema 定义 中,http字段的官方文档描述是"Returns a file containing an http remote url content."(返回一个包含远程 HTTP URL 内容的文件),注册方式是标准的 dagql 节点字段:
dagql.Fields[*core.Query]{ dagql.NodeFunc("http", s.http). WithInput(dagql.PerSessionInput). Doc(`Returns a file containing an http remote url content.`). Args( dagql.Arg("url").Doc(`HTTP url to get the content from (e.g., "https://docs.dagger.io").`), dagql.Arg("name").Doc(`File name to use for the file. Defaults to the last part of the URL.`), dagql.Arg("permissions").Doc(`Permissions to set on the file.`), dagql.Arg("checksum").Doc(`Expected digest of the downloaded content (e.g., "sha256:...").`), dagql.Arg("authHeader").Doc(`Secret used to populate the Authorization HTTP header`), dagql.Arg("experimentalServiceHost").Doc(`A service which must be started before the URL is fetched.`), ), }.Install(srv)注意WithInput(dagql.PerSessionInput):http的输入是按会话(session)划分的,这直接对应了后文的"每个会话独立缓存"行为。
返回对象:File上可以选哪些字段
示例中选了size和contents,但http返回的是完整的File对象,因此你还可以继续选择其他常用字段:
query { http(url: "https://raw.githubusercontent.com/dagger/dagger/main/LICENSE") { name # 文件名(默认取 URL 路径的最后一部分) size # 字节数 contents # 文本内容 directory # 父目录对象,可用于放入容器/目录 checksum # 文件内容摘要 id # 持久化对象 ID } }其中name字段的取值规则在 httpPath 函数 中有明确实现:
- 若显式传了
name参数,直接使用; - 否则解析 URL,取
url.Parse后路径的filepath.Base; - 若解析出来是空串、
.或/(例如 URL 以/结尾),则回退为"index"。
集成测试 TestHTTPName 验证了这一点:拉取.../README.md时文件名为README.md,而传HTTPOpts{Name: "FooBar.md"}后文件名为FooBar.md。
完整参数表与行为说明
结合 schema 注册代码 与 httpArgs 结构体,http的全部参数如下:
| 参数 | 类型 | 说明 | 默认值 / 备注 |
|---|---|---|---|
url | String! | 要拉取的 HTTP(S) URL | 必填 |
name | String | 文件的名称 | 默认取 URL 路径最后一段(见上节规则) |
permissions | Int | 设置到文件上的权限 | 缺省为0600,见 默认值代码 |
checksum | String | 期望的内容摘要,如sha256:... | 可选;不匹配时报http checksum mismatch错误 |
authHeader | SecretID | 用于填充Authorization请求头的 Secret | 可选 |
experimentalServiceHost | ServiceID | 在拉取 URL 之前必须先启动的服务 | 可选;用于访问引擎内服务网络 |
几点值得注意的实现细节:
- 默认权限 0600:http 解析器 中
permissions := int(args.Permissions.GetOr(dagql.Int(0600))),即不指定时文件以0600权限落盘。TestHTTPPermissions 用stat -c %a验证了显式传入0765/0764后权限确实生效。 - checksum 是 OpenContainers digest 语法:参数经 parseChecksumArg 解析,即必须是
sha256:...这类合法 digest 格式,否则报错invalid checksum "..."(单测见 TestParseChecksumArg)。内容实际摘要在 核心实现 中用sha256流式计算(io.MultiWriter(f, h)),不匹配时返回http checksum mismatch: expected ..., got ...。 - 版本可见性:
checksum与内部_httpState字段在 schema 上标注了View(AfterVersion("v0.21.0")),即客户端需要 v0.21.0 及以上版本才能看到/使用这些能力(见 schema 安装代码)。 - authHeader 走 Secret 而非明文:
AuthHeader的类型是core.SecretID,解析器会先加载 Secret 再取其Plaintext填入请求头(resolveHTTPSessionContext),避免把凭据写进查询文本。TestHTTPAuth 展示了典型场景:服务端要求 Basic Auth,不带authHeader时返回401 Unauthorized,带c.SetSecret(...)之后才能取到内容。
底层机制一:普通拉取路径(FetchHTTPFile)
当不涉及authHeader/experimentalServiceHost时,请求会走引擎内部的 HTTP 状态缓存路径(见下节);而当传了这两个参数时,http 解析器 会切换到直接拉取路径core.FetchHTTPFile。该函数(core/http.go)的流程是:
- 构造
GET请求,强制Accept-Encoding: identity(禁用压缩,便于按字节流落盘并计算摘要);若提供AuthorizationHeader则设置Authorization头; - 通过
doHTTPClientRequest发起请求——它使用基于引擎 DNS 配置的传输层(netconfhttp.NewTransport+DNSConfig),并对非 2xx/3xx 状态码直接报invalid response status ...错误(状态码校验); - 在快照(snapshot)中创建一个临时文件,把响应体一边写入文件、一边喂给 sha256 哈希;
- 文件时间戳处理:若响应带
Last-Modified头,则把它解析后写为文件 mtime(os.Chtimes),没有则回退到 Unix epoch(时间戳代码)。TestHTTPTimestamp 专门验证了"文件 mtime = Last-Modified 头"这一行为; - 提交快照(
bkref.Commit),校验 checksum,最终包装成File对象(持有快照引用与文件名),返回给调用方。
由于File是 Dagger 的一等 DAG 对象,http的结果可以直接参与后续操作,例如放进容器:
query { container { from(address: "alpine:latest") { withFile( path: "/etc/motd" file: {http(url: "http://dagger.io") { id }} ) { stdout: { # ... 后续构建步骤 } } } } }(Go SDK 对应写法为client.Container().From("alpine:latest").WithFile("/etc/motd", client.HTTP(url)),与 TestHTTP 中c.HTTP(url).Contents(ctx)的调用形态一致。)
底层机制二:HTTPState——按 URL 的持久化缓存与增量校验
这是当前仓库实现中http最值得展开的部分:普通 URL 拉取并不每次都真的下载,而是走一个内部持久对象HTTPState。
从 schema 安装代码 可以看到,http解析器实际是两段选择器串联:
Query._httpState(url)—— 返回一个只由 URL 决定的HTTPState对象(类型定义:URL+ETag+LastModified+ContentDigest+ 快照引用),它实现了dagql.PersistedObject,可以在会话间持久化、按需恢复;HTTPState._resolve(checksum, permissions, name)—— 真正执行条件请求并产出File。
HTTPState.Resolve(核心实现)的关键逻辑:
- 条件请求:如果状态里有
ETag,发送If-None-Match;否则若有LastModified,发送If-Modified-Since(请求头构造); - 304 命中:服务端返回
HTTP 304 Not Modified时不产生新快照,直接复用已缓存的快照,更新 ETag/Last-Modified 并做 checksum 比对后返回(304 分支)。源码注释特别提到:304 重校验不产生任何下载进度输出; - 200 更新:下载新内容,通过
MountRef把响应体写入快照中的contents文件(写快照),同时计算 sha256 摘要并记录 ETag/Last-Modified;若内容摘要与缓存一致则丢弃新快照,不一致则替换旧快照(快照替换); - 会话级隔离:
WithInput(dagql.PerSessionInput)意味着缓存按会话划分。TestHTTPCachePerSessions 用一个每次请求返回不同计数值的服务验证:不同会话各自得到自己的缓存副本,互不串扰。
持久化方面,HTTPState编码时只序列化url/etag/lastModified/contentDigest(persistedHTTPStatePayload),快照以snapshot角色的快照链接挂接,解码时按需重新打开快照(DecodePersistedObject)。这使得跨进程/跨重启恢复会话时,能继续利用 ETag 做增量校验而不是重新全量下载。
另外,最终File结果的内容摘要在 newHTTPFileResult 中由文件路径 + 权限 + 内容摘要 + LastModified + checksum 参数共同哈希得出——也就是说,改权限或改期望摘要都会得到一个不同的 Dagger 对象,从而正确参与构建缓存的判定。
访问引擎内服务:experimentalServiceHost
experimentalServiceHost参数解决的是"URL 指向 Dagger 引擎内部服务网络中的服务"这一场景(例如http://<service-hostname>:8080/...)。解析流程(resolveHTTPSessionContext):
- 加载
ServiceID,取得其内容摘要与Hostname; - 构造
ServiceBinding{Service, Hostname}并调用query.Services().StartBindings(...),把该服务启动并绑定到会话,返回一个detach函数; - 完成拉取后
defer detach()释放绑定。
测试侧的覆盖相当完整(core/integration/http_test.go):
- TestHTTPService:起一个服务并以其 hostname 构造 URL,
http查询成功取回内容; - TestHTTPChecksum / TestHTTPChecksumMismatch / TestHTTPChecksumInvalid:checksum 匹配、不匹配(
http checksum mismatch)、格式非法(invalid checksum)三种路径; - TestHTTPAuth:结合
authHeader(Secret)完成 Basic Auth 拉取; - TestHTTPTimestamp:验证
Last-Modified落到文件 mtime。
小结:把 http 查询放进你的流水线
把上面的内容落到实践中,可以掌握这些能力:
- 最小用法:
http(url: "...") { size contents }即可把任意公开 URL 变成可查询的文件对象(标准示例); - 可控命名与权限:
name决定文件在后续withFile等步骤中的名字,permissions控制落盘权限(默认0600); - 完整性保障:
checksum: "sha256:..."在摘要不符时让构建快速失败,适合下载固定版本的可执行文件或补丁文件; - 私有资源:
authHeader以 Secret 形式注入Authorization头,拉取私有仓库、制品库资源; - 服务内网资源:
experimentalServiceHost先启动指定服务再拉取其 hostname 上的 URL; - 缓存语义:同一 URL 的拉取被缓存为可持久化的
HTTPState,后续会话通过 ETag/Last-Modified做 304 增量校验,缓存按会话隔离——这保证了重复构建时的下载开销最小化。
以上所有行为均可在仓库中对应验证:schema 与参数定义见 core/schema/http.go,核心拉取/缓存/持久化实现见 core/http.go,单元与集成测试分别见 core/schema/http_test.go 和 core/integration/http_test.go。
【免费下载链接】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),仅供参考