init
This commit is contained in:
34
specs/002-fiber-single-proxy/checklists/requirements.md
Normal file
34
specs/002-fiber-single-proxy/checklists/requirements.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: HTTP 服务与单仓代理
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2025-11-13
|
||||
**Feature**: [/home/rogee/Projects/any-hub/specs/002-fiber-single-proxy/spec.md](../spec.md)
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [x] No implementation details (languages, frameworks, APIs)
|
||||
- [x] Focused on user value and business needs
|
||||
- [x] Written for non-technical stakeholders
|
||||
- [x] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [x] No [NEEDS CLARIFICATION] markers remain
|
||||
- [x] Requirements are testable and unambiguous
|
||||
- [x] Success criteria are measurable
|
||||
- [x] Success criteria are technology-agnostic (no implementation details)
|
||||
- [x] All acceptance scenarios are defined
|
||||
- [x] Edge cases are identified
|
||||
- [x] Scope is clearly bounded
|
||||
- [x] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [x] All functional requirements have clear acceptance criteria
|
||||
- [x] User scenarios cover primary flows
|
||||
- [x] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [x] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
34
specs/002-fiber-single-proxy/contracts/http-proxy.md
Normal file
34
specs/002-fiber-single-proxy/contracts/http-proxy.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Contract: Host 路由 + 缓存代理
|
||||
|
||||
## Request Flow
|
||||
1. Client sends HTTP request to `http://<listen-host>:<port>/<path>` with `Host: <hub-domain>`.
|
||||
2. Fiber middleware resolves HubRoute; if missing → 404 JSON `{ "error": "host_unmapped" }`.
|
||||
3. Proxy handler checks disk cache key = `<hub-name>/<path>`.
|
||||
4. Cache hit (Valid): stream file to client with stored headers.
|
||||
5. Cache stale/miss: build upstream request `Upstream + path`, attach `If-None-Match`/`If-Modified-Since` when available, stream response back; on 200 store body to cache.
|
||||
|
||||
## Required Headers
|
||||
- Host (required) → determines Hub.
|
||||
- `X-Any-Hub-Upstream` (response) – actual upstream URL (debugging).
|
||||
- `X-Any-Hub-Cache-Hit` (response) – `true/false`.
|
||||
- `X-Request-ID` (response) – correlation id for logs.
|
||||
|
||||
## Error Codes
|
||||
| Scenario | Status | Body |
|
||||
|----------|--------|------|
|
||||
| Host 未配置 | 404 | `{ "error": "host_unmapped" }` |
|
||||
| 上游 4xx/5xx | mirror | 上游 body 原样透传 |
|
||||
| 缓存写入失败 | 502 | `{ "error": "cache_write_failed" }` |
|
||||
|
||||
## Logging Fields
|
||||
```
|
||||
{
|
||||
"action": "proxy",
|
||||
"hub": "docker",
|
||||
"domain": "docker.hub.local",
|
||||
"cache_hit": true,
|
||||
"upstream_status": 200,
|
||||
"elapsed_ms": 123,
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
33
specs/002-fiber-single-proxy/data-model.md
Normal file
33
specs/002-fiber-single-proxy/data-model.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Data Model: HTTP 服务与单仓代理
|
||||
|
||||
## Entities
|
||||
|
||||
### HubRoute
|
||||
- **Description**: Host/端口到上游仓库的映射,供 Fiber 路由和 Proxy handler 使用。
|
||||
- **Fields**: `Name` (string, unique), `Domain` (string, FQDN), `Port` (int, 1-65535), `Upstream` (URL), `Proxy` (URL, optional), `CacheTTL` (duration override), `EnableHeadCheck` (bool).
|
||||
- **Validation**: Name 唯一;Domain 不含协议/路径;Upstream 必须 http/https。
|
||||
- **Relationships**: 由 config 加载到 `HubRegistry`;与 CacheEntry、ProxyRequest 通过 `Name` 关联。
|
||||
|
||||
### HubRegistry
|
||||
- **Description**: 运行期内存结构,按 (Port, Host) 查找 HubRoute。
|
||||
- **Fields**: `routes map[key]HubRoute`, key = `port:host`;支持默认端口匹配。
|
||||
- **Operations**: `Lookup(host, port)`, `List()`;返回结果提供给 Fiber 中间件。
|
||||
|
||||
### CacheEntry
|
||||
- **Description**: 表示磁盘缓存的一个对象(正文 + 元数据)。
|
||||
- **Fields**: `HubName`, `Path`, `FilePath`, `MetaPath`, `ETag`, `LastModified`, `StoredAt`, `TTL`, `Size`, `Checksum`。
|
||||
- **State**:
|
||||
1. `Empty`: 未缓存
|
||||
2. `Valid`: TTL 内、可直接返回
|
||||
3. `Stale`: TTL 过期,需 revalidate
|
||||
4. `Invalid`: 写入失败或上游错误,需删除
|
||||
|
||||
### ProxyRequest
|
||||
- **Description**: 一次请求生命周期的可观测性数据。
|
||||
- **Fields**: `ID`, `HubName`, `Host`, `Path`, `Method`, `CacheHit` (bool), `UpstreamStatus`, `LatencyMs`, `Error`。
|
||||
- **Usage**: 记录日志/metrics,未来可扩展 tracing。
|
||||
|
||||
### SampleConfig
|
||||
- **Description**: 示例配置集(Docker/NPM)。
|
||||
- **Fields**: `Type` (docker|npm), `Domain`, `Port`, `Upstream`, `Proxy`, `Notes`。
|
||||
- **Purpose**: quickstart & integration tests复用,证明真实配置长什么样。
|
||||
69
specs/002-fiber-single-proxy/plan.md
Normal file
69
specs/002-fiber-single-proxy/plan.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Implementation Plan: HTTP 服务与单仓代理
|
||||
|
||||
**Branch**: `002-fiber-single-proxy` | **Date**: 2025-11-13 | **Spec**: [spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/002-fiber-single-proxy/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver a Phase 1-ready proxy: Fiber HTTP server routing by Host→Hub, disk cache with TTL + conditional revalidation, and runnable Docker/NPM samples backed by integration tests. Core work spans server/router scaffolding, cache/proxy pipeline, observability, and documentation to prove the feature end to end.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Go 1.25+ (静态链接,单二进制交付)
|
||||
**Primary Dependencies**: Fiber v3(HTTP 服务)、Viper(配置)、Logrus + Lumberjack(结构化日志 & 滚动)、标准库 `net/http`/`io`
|
||||
**Storage**: 本地文件系统缓存目录 `StoragePath/<Hub>/<path>` + `.meta` 元数据
|
||||
**Testing**: `go test ./...`,配合 `httptest`、fake upstream server、临时目录验证路由/缓存/集成路径
|
||||
**Target Platform**: Linux/Unix CLI 进程,由 systemd/supervisor 管理,匿名下游客户端
|
||||
**Project Type**: 单 Go 项目(`cmd/` 入口 + `internal/*` 包)
|
||||
**Performance Goals**: 缓存命中路径需低延迟(相对首个请求减少 ≥70%),回源路径流式传输并限制常驻内存 <256MB,日志可追踪命中/回源/错误
|
||||
**Constraints**: 禁止 Web/UI、账号体系、额外配置源;必须通过 `config.toml` 驱动 Host、端口、缓存策略;请求匿名;不可新增未审核依赖
|
||||
**Scale/Scope**: Phase 1 仅实现单 Hub 路由 + Docker/NPM 示例;后续多 Hub、多协议将基于此扩展
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- 方案遵守“轻量 CLI 代理”定位,不引入 UI/账号/外部控制面。
|
||||
- 继续使用 Go + Fiber/Viper/Logrus/Lumberjack + 标准库,Helm/DB/Web 依赖均不在 scope。
|
||||
- 仅依赖 `config.toml` 说明 Host/端口/缓存字段;新增字段需写入配置 schema 与文档。
|
||||
- 缓存策略保持“命中即返回、未命中流式回源”,并要求结构化日志记录 `cache_hit`、上游状态等字段。
|
||||
- 计划包含配置解析、Host Header 路由、缓存逻辑、示例及测试,满足宪法强制测试/文档 Gate。
|
||||
|
||||
**Post-design re-check (Phase 1)**: 设计阶段将审查 server/cache/示例文档,确认未引入新依赖且 quickstart/contract 体现在 spec/plan/task 中;若出现额外库或多配置源,需重新申请豁免。
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/002-fiber-single-proxy/
|
||||
├── plan.md # 当前文件
|
||||
├── research.md # Phase 0 研究决策
|
||||
├── data-model.md # 实体/关系/约束
|
||||
├── quickstart.md # 演练 Docker/NPM 示例步骤
|
||||
├── contracts/ # CLI/Fiber/缓存交付合同(md 或 OpenAPI)
|
||||
└── tasks.md # Phase 2 工作拆解(/speckit.tasks 输出)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
```text
|
||||
cmd/any-hub/ # CLI 入口,启动 server/cache
|
||||
internal/server/ # Fiber app、Host Registry、路由/中间件
|
||||
internal/cache/ # 磁盘缓存/元数据/TTL
|
||||
internal/proxy/ # 回源/条件请求/流式复制
|
||||
internal/logging/ # 结构化日志字段
|
||||
configs/ # 示例 config (docker|npm)
|
||||
tests/integration/ # upstream stub + e2e 测试
|
||||
```
|
||||
|
||||
**Structure Decision**: 仍按单仓库单二进制;新增 `internal/server`, `internal/cache`, `tests/integration` 目录必须保持与 `internal/proxy` 清晰边界,所有引用通过包级接口注入,避免循环依赖。
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| _None_ | — | — |
|
||||
52
specs/002-fiber-single-proxy/quickstart.md
Normal file
52
specs/002-fiber-single-proxy/quickstart.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Quickstart: HTTP 服务与单仓代理
|
||||
|
||||
## 前置条件
|
||||
- Go 1.25+,可运行 `go run` 与 `go test`。
|
||||
- 访问 Docker Hub 或准备本地 fake upstream(见 tests/integration)。
|
||||
- 端口 5000 可用。
|
||||
|
||||
## 步骤
|
||||
1. **准备配置**
|
||||
```bash
|
||||
cp configs/docker.sample.toml config.toml
|
||||
```
|
||||
修改 `[[Hub]]` 中的 `Domain`(例如 `docker.hub.local`),并确保 `/etc/hosts` 映射到 `127.0.0.1`。
|
||||
2. **启动代理**
|
||||
```bash
|
||||
go run ./cmd/any-hub --config ./config.toml
|
||||
```
|
||||
终端将打印 `action=startup` 日志。
|
||||
3. **发起请求并观察路由**
|
||||
```bash
|
||||
curl -H "Host: docker.hub.local" http://127.0.0.1:5000/v2/
|
||||
```
|
||||
响应中会包含 `X-Any-Hub-Upstream`、`X-Any-Hub-Cache-Hit: false` 与 `X-Request-ID`,日志记录 `action=proxy`、`hub=docker`、`upstream_status=200`。
|
||||
4. **验证 Host 未配置时的 404**
|
||||
```bash
|
||||
curl -i -H "Host: unknown.hub.local" http://127.0.0.1:5000/v2/
|
||||
```
|
||||
代理会返回 `404 {"error":"host_unmapped"}`,日志显示 `action=host_lookup`。
|
||||
5. **切换 NPM 示例**
|
||||
```bash
|
||||
cp configs/npm.sample.toml config.toml
|
||||
npm config set registry http://127.0.0.1:5000 --global
|
||||
npm view lodash --registry http://127.0.0.1:5000 --fetch-timeout=60000
|
||||
```
|
||||
6. **运行路由集成测试**
|
||||
```bash
|
||||
go test ./tests/integration -run HostRouting -v
|
||||
```
|
||||
7. **使用示例配置快速体验**
|
||||
```bash
|
||||
# Docker Hub 示例
|
||||
./scripts/demo-proxy.sh docker
|
||||
|
||||
# NPM 示例 (监听 5001)
|
||||
./scripts/demo-proxy.sh npm
|
||||
```
|
||||
脚本会直接引用 `configs/docker.sample.toml` / `configs/npm.sample.toml` 并启动代理,确保对应域名已指向本机。
|
||||
|
||||
## 故障排查
|
||||
- `host_unmapped`: 检查 Host 头与 `config.toml` 中 Domain 是否一致。
|
||||
- `cache_write_failed`: 确认 `StoragePath` 可写,并有足够磁盘空间。
|
||||
- 上游 401/403: 公共仓库可能需要额外匿名访问 header;可在配置中新增自定义 header(future work)。
|
||||
26
specs/002-fiber-single-proxy/research.md
Normal file
26
specs/002-fiber-single-proxy/research.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Research: HTTP 服务与单仓代理
|
||||
|
||||
## Host → Hub 路由策略
|
||||
- **Decision**: 使用 Fiber 自定义中间件读取 `Host` Header 与监听端口,查询预构建的 `HubRegistry`,命中才放行,未命中返回 404 并记录 `host_unmapped`。
|
||||
- **Rationale**: 保持单一入口、避免误路由带来安全风险,同时兼容将来多 Hub 扩展。
|
||||
- **Alternatives considered**: 依赖 Fiber 原生多 App(导致重复配置/资源浪费);将未知 Host 回退到默认 Hub(难以追踪错误)。
|
||||
|
||||
## 缓存与条件回源
|
||||
- **Decision**: 采用磁盘文件 + `.meta` 元数据,写入时使用临时文件 + rename;记录 `ETag/Last-Modified/StoredAt`,并在重验证时携带 `If-None-Match`/`If-Modified-Since`。
|
||||
- **Rationale**: 磁盘缓存简单可靠,临时文件避免半写入;条件请求可显著降低带宽,满足 SC-001/SC-004。
|
||||
- **Alternatives considered**: 内存缓存(受限于 256MB);数据库存储(超出 Phase 1 范畴)。
|
||||
|
||||
## 上游 HTTP 客户端
|
||||
- **Decision**: 共享 `http.Client`,启用 `Transport` 连接复用、超时/Proxy/Retry 设置来自 config;在 `internal/proxy/upstream.go` 中封装请求构造。
|
||||
- **Rationale**: 避免为每个请求重建 client,提高吞吐并可统一超时;复用 config 中 Proxy 设定。
|
||||
- **Alternatives considered**: 每 Hub 创建独立 client(增加内存占用)、第三方 HTTP 库(违背依赖约束)。
|
||||
|
||||
## 示例仓库选择
|
||||
- **Decision**: 默认提供 Docker Hub 示例(manifest + layer)和 NPM 示例(package metadata);在 quickstart 中支持切换到本地 fake upstream(tests/integration/stub)。
|
||||
- **Rationale**: Docker/NPM 覆盖二进制/JSON 两类典型仓库;fake upstream 便于离线/CI;满足 spec 对示例的要求。
|
||||
- **Alternatives considered**: 只做 Docker(覆盖面不足)、引入真实 registry 模拟器(复杂度高)。
|
||||
|
||||
## 观测性与性能指标
|
||||
- **Decision**: 在 proxy handler 中记录 `hub`, `domain`, `cache_hit`, `upstream_status`, `elapsed_ms`; 通过 `logrus.WithFields` 输出 JSON;同时统计请求计数(后续可暴露 metrics)。
|
||||
- **Rationale**: 满足 FR-006 / SC-001/002;方便后续扩展 metrics;JSON 便于日志采集。
|
||||
- **Alternatives considered**: 文本日志(难以机器解析);延迟到未来阶段再实现(不符成功标准)。
|
||||
108
specs/002-fiber-single-proxy/spec.md
Normal file
108
specs/002-fiber-single-proxy/spec.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Feature Specification: HTTP 服务与单仓代理
|
||||
|
||||
**Feature Branch**: `002-fiber-single-proxy`
|
||||
**Created**: 2025-11-13
|
||||
**Status**: Draft
|
||||
**Input**: User description: "HTTP 服务与单仓代理 - 使用 Fiber 搭建 HTTP 服务,支持基于 Host 的路由到单一 Hub。 - 实现文件缓存模块(读写、TTL 检查),完成命中/回源流程。 - 提供 Docker Hub/NPM 任一仓库的最小可用代理,并通过集成测试验证。"
|
||||
|
||||
> 宪法对齐(v1.0.0):
|
||||
> - 保持“轻量、匿名、CLI 多仓代理”定位:不得引入 Web UI、账号体系或与代理无关的范围。
|
||||
> - 方案必须基于 Go 1.25+ 单二进制,依赖仅限 Fiber、Viper、Logrus/Lumberjack 及必要标准库。
|
||||
> - 所有行为由单一 `config.toml` 控制;若需新配置项,需在规范中说明字段、默认值与迁移策略。
|
||||
> - 设计需维护缓存优先 + 流式传输路径,并描述命中/回源/失败时的日志与观测需求。
|
||||
> - 验收必须包含配置解析、缓存读写、Host Header 绑定等测试与中文注释交付约束。
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Host 路由下的单仓访问 (Priority: P1)
|
||||
|
||||
企业内开发者希望通过 `docker.hub.local` 或 `npm.hub.local` 这样的 Host 头访问本地代理,系统需要根据 Host 定位唯一 Hub,并把请求透明转发至上游。
|
||||
|
||||
**Why this priority**: 没有稳定的 HTTP 入口和 Host 路由,就无法承载任何代理能力,是 Phase 1 的核心目标。
|
||||
|
||||
**Independent Test**: 启动 any-hub,准备含单一 Hub 的配置,使用 `curl -H "Host: docker.hub.local" http://127.0.0.1:5000/v2/_catalog`,验证请求进入正确的 Handler 并记录结构化日志。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 配置声明 Hub `docker` 监听端口 5000,**When** 客户端携带 `Host: docker.hub.local` 访问,**Then** Fiber 将请求路由到 docker Hub,并构造正确的上游 URL。
|
||||
2. **Given** 未声明的 Host,**When** 客户端请求,**Then** 立即返回 404 并在日志中标记 `host_unmapped`。
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - 磁盘缓存与回源流程 (Priority: P1)
|
||||
|
||||
CI/CD 任务需要重复下载相同镜像或包,期望 any-hub 能在本地缓存结果:命中时直接返回,过期或未命中时回源并刷新缓存,同时保持流式传输。
|
||||
|
||||
**Why this priority**: 缓存是代理节省带宽与加速的唯一方式;缺失会让 Phase 1 成为普通转发层。
|
||||
|
||||
**Independent Test**: 使用集成测试模拟上游服务器,第一次请求写入缓存,第二次命中缓存并快速返回;设置 TTL 过期后触发 revalidate。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 缓存文件存在且未过期,**When** 再次请求相同路径,**Then** 直接从磁盘流式返回,并记录 `cache_hit=true`。
|
||||
2. **Given** 缓存过期,**When** 新请求到达,**Then** 先向上游发送带条件的请求;若上游 304,则回退本地缓存,若 200 则边回源边写磁盘与客户端。
|
||||
3. **Given** 回源失败或磁盘写入错误,**Then** 系统返回合理的 5xx 并记录 `cache_hit=false` 与错误原因。
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - 最小 Docker/NPM 代理样例 (Priority: P2)
|
||||
|
||||
平台运维需要一份可运行的示例,让团队快速验证 Docker 或 NPM 仓库能通过 any-hub 获取常见资源,并在 CI 中运行端到端测试确保回源逻辑可靠。
|
||||
|
||||
**Why this priority**: 实际仓库样例可以验证配置、日志、缓存整体流程,也为后续多仓扩展提供可复制模板。
|
||||
|
||||
**Independent Test**: 提供 `configs/docker.sample.toml` 或 `configs/npm.sample.toml`,在集成测试中启动临时上游(可模拟 docker registry 或 npm registry),通过 HTTP 调用完成一次拉取并校验缓存目录生成。
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** 示例配置启用 Docker Hub,**When** 运行 quickstart 脚本,**Then** 可以从真实或模拟上游下载一个 manifest 并写入缓存目录。
|
||||
2. **Given** 示例配置选择 NPM,**When** 执行 `npm view foo` 指向代理,**Then** CLI 能收到正确响应,日志显示命中/回源信息。
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- 配置监听端口但 Host 头缺失或大小写异常:必须直接返回 404,并在日志中记录 `host_unmapped` 字段,禁止回退默认 Hub。
|
||||
- 大文件下载中途中断:需要保证写入临时文件并在失败时清理,避免污染缓存。
|
||||
- TTL 设为 0(永远回源)或非常大:需要解释行为并防止整数溢出。
|
||||
- 上游返回 4xx/5xx:缓存不得写入,同时应透传状态码。
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: 系统必须提供基于 Fiber 的 HTTP Server,监听配置声明的端口,并按照 `Host` → `Hub` 的映射路由所有请求。
|
||||
- **FR-002**: 对于匹配的 Hub,请求路径、查询、方法、Headers 必须重新组装为上游 URL,并保持匿名代理(不注入用户标识)。
|
||||
- **FR-003**: 缓存模块必须在磁盘上以 `StoragePath/<Hub>/<path>` 结构保存内容,并为每个条目维护元数据(TTL、ETag/Last-Modified、写入时间)。
|
||||
- **FR-004**: 命中缓存时必须流式读取磁盘并返回;未命中或过期时需边回源边写入磁盘,并在完成前向客户端持续输出,避免全量加载内存。
|
||||
- **FR-005**: 系统必须支持条件请求:若缓存存在 ETag/Last-Modified,回源时附带 `If-None-Match`/`If-Modified-Since`,收到 304 时回退缓存。
|
||||
- **FR-006**: 任一请求都要记录结构化日志字段(hub、domain、cache_hit、upstream_status、elapsed_ms),并在错误时附带原因。
|
||||
- **FR-007**: 提供至少一个 Docker 或 NPM 的示例配置与 quickstart 说明,包含端到端集成测试脚本,证明可从代理获取真实或模拟数据。
|
||||
- **FR-008**: 所有配置项(端口、Host、TTL、缓存目录)必须在 `config.toml` 中声明,CLI 不引入新的隐式参数。
|
||||
|
||||
### Key Entities *(include if feature involves data)*
|
||||
|
||||
- **HubRoute**: 映射 Host/端口到具体上游信息,字段包括 `Name`, `Domain`, `Port`, `Upstream`, `Proxy`, `CacheTTL`。
|
||||
- **CacheEntry**: 表示磁盘缓存文件与 `.meta` 元数据(ETag、Last-Modified、StoredAt、Size、Checksum)。
|
||||
- **ProxyRequest**: 记录一次代理请求生命周期(原始 URL、Host、缓存命中状态、上游响应码、耗时)。
|
||||
- **SampleConfig**: 示例配置集合,用于定义 Docker/NPM Hub 所需的字段和默认值。
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: 针对同一资源的第二次请求延迟较首次下降 ≥70%,且日志显示 `cache_hit=true`。
|
||||
- **SC-002**: Host 路由能在 100% 测试用例中将请求映射到正确 Hub,未配置的 Host 返回 404,误路由率为 0。
|
||||
- **SC-003**: 在示例配置下,端到端集成测试成功率达到 100%,并能在 2 分钟内完成一次 docker 或 npm 包的完整拉取。
|
||||
- **SC-004**: 缓存目录在异常情况下不产生损坏文件,测试覆盖包括中断写入、上游错误、TTL 过期等至少 5 个边界场景。
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Phase 1 仅需支持单一 Hub 路由;多 Hub 并行将在后续阶段扩展。
|
||||
- 上游仓库需支持 HTTP/HTTPS GET/HEAD,暂不支持 WebSocket、Chunked 上传等复杂协议。
|
||||
- 示例代理默认指向公共 Docker Hub;若网络受限,可在 quickstart 中改为模拟上游。
|
||||
- 磁盘缓存容量和清理策略仍沿用全局配置,不在本次迭代扩展淘汰算法。
|
||||
|
||||
## Clarifications
|
||||
|
||||
### Session 2025-11-13
|
||||
|
||||
- Q: Host 头缺失或未匹配时应如何处理? → A: 一律返回 404 并记录 `host_unmapped`,不允许回退默认 Hub。
|
||||
149
specs/002-fiber-single-proxy/tasks.md
Normal file
149
specs/002-fiber-single-proxy/tasks.md
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
|
||||
description: "Task list for HTTP 服务与单仓代理"
|
||||
---
|
||||
|
||||
# Tasks: HTTP 服务与单仓代理
|
||||
|
||||
**Input**: Design documents from `/specs/002-fiber-single-proxy/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: 宪法 v1.0.0 要求覆盖配置解析、Host Header 路由、缓存读写、条件回源与示例集成测试,本任务清单默认包含相应测试项。
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: 准备文档与目录结构,确保团队对 Phase 1 范围有统一认知。
|
||||
|
||||
- [X] T001 更新 `DEVELOPMENT.md` 的 Phase 1 章节,描述 HTTP 服务/缓存迭代目标
|
||||
- [X] T002 在 `README.md` 添加 “单仓代理 (Phase 1)” 小节并链接到 spec/plan
|
||||
- [X] T003 [P] 创建 `internal/server/` 与 `internal/cache/` 目录说明文件(`doc.go`)概述职责
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: 构建 Host→Hub 注册表、HTTP 客户端和缓存存储基础,所有用户故事依赖这些能力。
|
||||
|
||||
- [X] T004 实现 `internal/server/hub_registry.go`,从配置构建 Host/端口→Hub 映射并提供查询 API
|
||||
- [X] T005 [P] 在 `internal/server/http_client.go` 创建共享上游 HTTP 客户端(含超时、Proxy、Header 透传)
|
||||
- [X] T006 设计 `internal/cache/store.go` 接口 + 文件布局(`StoragePath/<hub>/<path>` 与 `.meta`)
|
||||
- [X] T007 [P] 在 `tests/integration/upstream_stub_test.go` 搭建可复用的模拟上游服务器(Docker/NPM 两种路径)
|
||||
|
||||
**Checkpoint**: Registry、HTTP 客户端、缓存存储与测试桩可用,方可进入用户故事实现。
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Host 路由下的单仓访问 (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Fiber 服务可根据 Host/端口匹配到唯一 Hub,并将请求透明转发。
|
||||
|
||||
**Independent Test**: 使用 `curl -H "Host: docker.hub.local"` 对本地服务发起请求,结合 httptest 断言正确 Hub、上游 URL 和日志字段。
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
- [X] T008 [P] [US1] 编写路由单元测试:`internal/server/router_test.go` 覆盖 Host 命中/未配置/默认逻辑
|
||||
- [X] T009 [P] [US1] 添加集成测试:`tests/integration/host_routing_test.go` 验证端口+Host 组合处理
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [X] T010 [US1] 构建 Fiber App & 中间件(请求日志、错误捕获)于 `internal/server/router.go`
|
||||
- [X] T011 [US1] 在 `cmd/any-hub/main.go` 接线 server 启动逻辑,传入 registry + HTTP 客户端
|
||||
- [X] T012 [US1] 为 Host 未命中添加 404 响应与日志字段(`internal/server/router.go`)
|
||||
- [X] T013 [US1] 更新 `quickstart.md`,记录如何使用 Host 头访问单仓代理
|
||||
|
||||
**Checkpoint**: CLI 可启动 HTTP 服务并完成 Host→Hub 路由,日志含 action/host 字段。
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - 磁盘缓存与回源流程 (Priority: P1)
|
||||
|
||||
**Goal**: 在磁盘上缓存上游响应,支持 TTL、条件请求与流式读写。
|
||||
|
||||
**Independent Test**: 使用模拟上游写入缓存后再次请求,观察命中与 304 流程;覆盖写入失败、上游错误等场景。
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
- [X] T014 [P] [US2] 为 `internal/cache/store_test.go` 添加命中/未命中/TTL 过期测试
|
||||
- [X] T015 [P] [US2] 在 `tests/integration/cache_flow_test.go` 编写端到端测试(首次写入、304 回退、上游失败)
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [X] T016 [US2] 在 `internal/cache/store.go` 实现读/写/元数据 API,并处理并发写入/临时文件
|
||||
- [X] T017 [US2] 在 `internal/proxy/handler.go` 中实现缓存流程(命中→读磁盘,未命中→回源→写缓存→流式返回)
|
||||
- [X] T018 [US2] 支持条件请求 Header(`If-None-Match`/`If-Modified-Since`)并处理 304(`internal/proxy/upstream.go`)
|
||||
- [X] T019 [US2] 扩展日志字段,记录 `cache_hit`、`upstream_status`、`elapsed_ms`(`internal/logging/fields.go` + `internal/proxy/handler.go`)
|
||||
- [X] T020 [US2] 在 `config.example.toml` 增加缓存路径/TTL 示例,并更新 `DEVELOPMENT.md` 的缓存调优段落
|
||||
|
||||
**Checkpoint**: 缓存命中率可在日志中观察;回源路径流式返回并具备条件请求能力。
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - 最小 Docker/NPM 代理样例 (Priority: P2)
|
||||
|
||||
**Goal**: 提供可复制的示例配置与 quickstart 脚本,验证 Docker 或 NPM 仓库的代理行为。
|
||||
|
||||
**Independent Test**: 运行文档中的 quickstart,对真实或模拟上游完成一次包/镜像拉取,并验证缓存目录写入。
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
- [X] T021 [P] [US3] 编写示例集成测试 `tests/integration/docker_sample_test.go`(或 `npm_sample_test.go`)验证端到端流程
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [X] T022 [US3] 添加 `configs/docker.sample.toml` 与 `configs/npm.sample.toml`,注释必要字段
|
||||
- [X] T023 [US3] 在 `quickstart.md` 新增示例步骤(Docker/NPM)、常见问题与日志示例
|
||||
- [X] T024 [US3] 准备脚本或 Make 目标 `scripts/demo-proxy.sh` 运行示例配置
|
||||
- [X] T025 [US3] 补充 README “示例代理” 章节链接 demo 脚本与 quickstart
|
||||
|
||||
**Checkpoint**: 示例配置 & 文档可指导用户完成最小代理体验,并由自动化测试验证。
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: 收尾文档、CI 与质量保证,确保 Phase 1 可长期维护。
|
||||
|
||||
- [X] T026 在 `cmd/any-hub/main.go` 与 `internal/*` 新增中文注释,解释 server/cache 关键流程
|
||||
- [X] T027 [P] 增加 `tests/integration/interrupt_test.go`,验证下载中断后的缓存清理
|
||||
- [X] T028 更新 `CHANGELOG.md` 与 `README.md`,记录 Phase 1 完成情况
|
||||
- [X] T029 运行 `gofmt`、`go test ./...`,并在 `DEVELOPMENT.md` 记录验证结果与命令
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
- Phase 1 → Phase 2 → User Stories (US1/US2/US3) → Phase 6
|
||||
|
||||
### User Story Dependencies
|
||||
- US1 (Host 路由) 依赖 Phase 2 完整;US2 依赖 US1 提供的 Fiber/Proxy 框架;US3 依赖 US1+US2 的代理能力
|
||||
|
||||
### Parallel Execution Examples
|
||||
- **US1**: T008 与 T009(测试)可并行;T010/T011 实现后可由 T012/T013 跟进
|
||||
- **US2**: T014/T015 可并行;缓存实现 T016 可与日志扩展 T019 同步推进
|
||||
- **US3**: T022(配置)与 T024(脚本)可并行,测试 T021 需在示例完成后执行
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP First (User Story 1 Only)
|
||||
1. 完成 Phase 1-2
|
||||
2. 交付 Host 路由 + HTTP 服务(US1),即可演示基础代理能力
|
||||
|
||||
### Incremental Delivery
|
||||
1. 在 MVP 基础上继续 US2(缓存)→ US3(示例)
|
||||
2. 每个阶段完成后运行 quickstart + 集成测试,确保可独立交付
|
||||
|
||||
### Parallel Team Strategy
|
||||
- 团队 A:负责 server/router(US1)
|
||||
- 团队 B:负责 cache/proxy(US2)
|
||||
- 团队 C:负责示例配置与文档(US3)
|
||||
Reference in New Issue
Block a user