Skip to content

fix(client,retry,paginate,upload,repos): issue #4-#10 全量修复与加固 - #11

Merged
zy84338719 merged 2 commits into
masterfrom
fix/issues-4-10-hardening
Nov 10, 2026
Merged

zy84338719 merged 2 commits into
masterfrom
fix/issues-4-10-hardening

Conversation

@zy84338719

Copy link
Copy Markdown
Member

概述

issue 清零战车道 A3 第一批:go-gitcode 7 单全量修复。每单修根因、配 table-driven 测试(hardening_test.go),门禁 go test -race ./... 全绿、gofmt -s -l 空、go vet 过。

逐单

Retry-After 解析缺三道闸(#4)

  • 新 retryAfterDuration:delay-seconds 与 HTTP-date(http.ParseTime)双形式;非正值(负秒/零/过去日期)与不可解析值回落指数退避(不再产负时长致忙等);解析结果与指数退避共用 MaxBackoff 钳制(Retry-After: 3600 不再等 1 小时);超大秒数在 Duration 转换前封顶防溢出。
  • 零值 InitialBackoff/MaxBackoff 按文档默认(1s/30s)归一——此前零 MaxBackoff 会把所有等待钳成 0。
  • 测试:TestRetryAfterDuration(9 例表)、TestGetRetryWaitTimeRetryAfter(11 例表)、TestRetryAfterHeaderHonoredOn429(真实 429→200 交换断言等待)。

Paginate 无页数上限(#5)

  • maxPaginatePages = 50(与 go-gongfeng 对齐),触顶 yield 哨兵 ErrTooManyPages(errors.Is 可判定)——「封顶截断」与「取完」可区分。
  • 说明:未加「与上一页相同即停」短路——合法数据里跨页全同(如 200 条同名项)与服务端忽略分页参数在客户端不可区分(仓内既有 TestCollectAll 即是该形态),误杀面大于收益;封顶已把损害有界化(300ms 12 万请求 → 最多 50 次)。
  • 测试:TestPaginatePageCap(恒满页 → 恰好 50 页 + 哨兵错误)、TestPaginateEndsNormally。

383 处路径裸拼接零 PathEscape(#6)

  • 新 path.go:pathf(format, args...)——路径侧(? 前)字符串实参一律 url.PathEscape;query 侧实参不动;%d 原样;pathSegs 逐段转义保留 / 分隔符(仓库文件路径、archive format),以 escapedPath 标记防二次转义。
  • 脚本批量收编 47 个文件 383 处(fmt.Sprintf("/... → pathf("/...),逐处可审:纯机械替换 + 6 处文件路径手包 pathSegs + query 裸拼值(search 的 q/过滤参数、events year/next、namespace path、archive ref/format、contents/raw 的 ref)改 QueryEscape/url.Values。
  • 测试:TestPathfEscaping(10 例表)、TestEndpointPathIsolation(httptest 断言 RequestURI:evil/../admin → evil%2F..%2Fadmin、org?x=1 → org%3Fx=1)、TestRawFilePathSegments。

响应体无 LimitReader(#7)

  • readBodyLimited(io.LimitReader(limit+1) 判超限)收口 client/upload/oauth 全部 9 处 io.ReadAll;分层默认:JSON 16 MiB、二进制下载(raw/release 资产/artifact/日志归档)1 GiB;SetMaxResponseBodySize 全局覆盖(<=0 恢复默认)。
  • 超限报 ResponseBodyTooLargeError(IsResponseBodyTooLarge 判定)。
  • 测试:TestResponseBodyLimit(4 例表:json/raw/upload 超限 + 达限通过)、TestReadBodyLimited、TestMaxResponseBodySizeSetter。

RWMutex 只护写不护读(#8)

  • 新 requestSnapshot(RLock 下取 baseURL/httpClient),收编 doRequest/doRawRequest/doFormRequest/doRawBodyRequest/retry 循环/upload 全部裸读点,与仓内 retryPolicy/hooks 既有持锁读法对齐。
  • 测试:TestConcurrentSettersAndRequests(100 轮 SetHTTPClient/SetBaseURL 并发 GetCurrentUser,-race 干净)。

upload 绕过 hook 与 retry(#9)

  • UploadFileReader/UploadImageReader 构造 multipart body([]byte,可重放)后走共享核心 doBufferedRequestAt:鉴权 → request hook → 重试 → response hook → 有界读体,与 CHANGELOG「All HTTP request methods now run request/response hooks and support retry policy」承诺对齐。
  • 连带修复潜伏缺陷:重试第 2+ 次尝试复用已耗尽的 body reader(实际发空 body)——现在经 req.GetBody 重放,JSON POST 与 multipart 上传重试均真正可重入。
  • 测试:TestUploadRunsHooksAndRetries(500×2 后 200:attempts=3、hook 各 1 次、终次 body 完整)、TestUploadImageRunsHooks、TestJSONBodyReplayOnRetry(三次 body 逐字一致)。

ListRepositories 静默丢 Type/Sort(#10)

  • ListRepositoriesOptions 自有 toQuery(遮蔽嵌入 ListOptions.toQuery)把 type/sort(经 QueryEscape)一并上 wire;ListRepositories(含 Owner 分支)与 ListCurrentUserRepositories 收敛到同一 helper,同端点同结构体不再分叉。
  • 测试:TestListRepositoriesFiltersOnWire(3 入口 × 断言 query 全参数)、TestListRepositoriesOptionsNoFilters。

附带

  • 请求执行核心合并:五份复制粘贴管线收敛为 doBufferedRequestAt + doRawRequestWith(行为差异仅一处收敛:form 空 body 从 unmarshal 报错改为跳过解码)。
  • 地板提升:go 指令 1.26→1.27,CI matrix/lint/fmt/integration/release 与 .golangci.yml 同步;本仓零第三方依赖,go get -u ./... 无可升项;删除手写 min 用内建,for range n/slices.Clone 就位。
  • README:Retry-After 语义与 Paginate 封顶行为补文档;CHANGELOG 新增 v0.16.0 段。

Closes #4
Closes #5
Closes #6
Closes #7
Closes #8
Closes #9
Closes #10

Murphy-Yi added 2 commits November 11, 2026 02:26
- #4 Retry-After 三道闸:统一 retryAfterDuration——非正值/不可解析回
  落指数退避、delay-seconds 与 HTTP-date 双形式、与指数退避共用
  MaxBackoff 钳制、超大秒数防溢出;零值退避字段按文档默认归一
- #5 Paginate 50 页封顶(与 go-gongfeng 对齐)+ ErrTooManyPages 哨兵,
  截断与取完可区分;服务端恒返满页不再无限打请求
- #6 新增 pathf/pathSeg/pathSegs:383 处端点路径从裸 Sprintf 收编到
  统一段转义(/ ? # 等被隔离在自身段内);文件路径走 pathSegs 保留
  分隔符;search/events/namespace/archive/contents 六处裸拼 query 值
  改 QueryEscape/url.Values
- #7 响应体读取全量收口 readBodyLimited:JSON 16MiB / 下载 1GiB 分层
  上限,SetMaxResponseBodySize 可覆盖,超限报 ResponseBodyTooLargeError
- #8 baseURL/httpClient 读侧统一 requestSnapshot(RLock),并发
  SetHTTPClient/SetBaseURL 与请求不再 data race(-race 验证)
- #9 upload 构造 multipart 后走 doBufferedRequestAt 共享管线:审计
  hook 触发、429/5xx 重试生效;顺带修复重试复用已耗尽 body 的潜伏
  缺陷(req.GetBody 重放,三次尝试 body 逐字一致有测)
- #10 ListRepositoriesOptions 自有 toQuery(遮蔽嵌入方法)收敛
  Type/Sort 上 wire,ListRepositories 两分支与
  ListCurrentUserRepositories 共用,杜绝再分叉

请求执行核心合并为 doBufferedRequestAt/doRawRequestWith 单管线;
删除手写 min 用内建;地板提升 go 1.27(CI matrix/lint/fmt/release 与
.golangci.yml 同步);hardening_test.go 逐 issue table-driven 测试;
门禁 go test -race ./... 全绿、gofmt -s -l 空、go vet 过
@zy84338719
zy84338719 merged commit 6e2a49c into master Nov 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment