题目的要求是根据 Docker Registry HTTP API v2 规范来设计一个简化的后端服务。运行逻辑的设计和BYR Arichive的思路差不多,也是基于P-C模型的多并发预生成线程池处理并发任务。
- 如何读懂文档
- 搞清楚api规定的不同uri的对应操作
- 从uri中正确解析出不同的param/header
- 什么是base64解密?怎么用openssl的函数进行SHA256计算digest以及生成uuid
- 由于没能及时修正拼写错误导致的各种小问题
Registry主要维护三种东西:blob manifest 和 manifest list 。根据不同的uri,通过路由转发到不同的端点进行处理,最底层的是各种文件操作。
对blob,主要有如下几种操作
| method URI | 函数 | 描述 |
|---|---|---|
GET /v2/ |
*router.c | 检查registry的api版本,在router中解析uri时构成响应 |
HEAD /v2/{repository_name}/blobs/{digest} |
blob_exists | 检查blob是否存在 |
GET /v2/{repository_name}/blobs/{digest} |
retrieve_blob | 下载blob内容 |
POST /v2/{repository_name}/blobs/uploads |
initiate_blob_upload | 初始化一个上传会话,获得一个会话uuid |
PATCH /v2/{repository_name}/blobs/uploads/{uuid} |
upload_blob_chunk | 追加body中包含的数据块,返回Range作为累计大小 |
PUT /v2/{repository_name}/blobs/{uuid}?digest=sha256: |
complete_blob_upload | 结束上传并校验digest |
GET /v2/{repository_name}/blobs/uploads/{uuid} |
get_blob_upload_status | 获取当前会话的状态 |
DELETE /v2/{repository_name}/blobs/uploads/{uuid} |
cancel_blob_upload | 取消上传,删除会话数据 |
对manifest, 主要有如下几种操作
当参数为reference时,一律都需要strcmp(uri, "sha256:")来判断是否为digest
| method URI | 函数 | 描述 |
|---|---|---|
PUT /v2/{repo_name}/manifests/{ref} |
put_image_manifest | - tag:将请求体解析为manifest,若config.data存在,则将config.data解base64作为镜像的config,校验后更新manifest到tags存储 |
(前置要求:请求header中必须带有Content-Length) |
- digest:将请求体解析为manifest,若config.data存在,则将config.data解base64作为镜像的config,校验后更新manifest到digest存储 |
|
GET /v2/{repo_name}/manifests/{ref} |
get_image_manifest | - tag:返回manifest list,若指定了Accept,则会从list中返回最佳匹配linux/amd64 |
(响应体中均携带Docker-Content-Digest) |
- digest:返回对应的manifest,响应头中的mediaType从对应的manifest中获取 |
|
HEAD /v2/{repo_name}/manifests/{ref} |
check_manifest_exist | 与GET方法一致,但只返回响应头,用于检查blob的每层layer的存在性 |
DELETE /v2/{repo_name}/manifests/{digest} |
delete_image_manifest | 按digest删除单个manifest,若manifest仍被任一tag的manifest list引用则删除失败 |
一个image带有多个layer,通常是一个压缩的tar,描述这个文件相对于它的上一层的文件系统改动。在manifest中,layers是一个数组,每个layer都有:digest size mediaType。除了layers,manifest里也有config字段,也可以用digest来引用。
解决这类问题的首要任务是读懂文档里描述的两件事:
- 维护的文件和它们的文件结构,它们之间的关系
- 对这些文件进行增删改查的操作api的定义
在Docker文档中,每个接口都有非常详尽的描述。入口参数在参数表中设定,请求Headers需要通过readhdr和get_hdr函数来存储到对应的变量中。通过rio进行读取请求体/请求头操作,从而得到对manifest/manifest list/blobs进行操作的先决条件。
manifest与manifest list两类文件的入与出都与json相关,cJSON库提供了很好的支持,让遍历、查找等工作简单快捷。
blob文件则对应二进制文件的操作,fopen返回的文件流配合rio能够对这些文件进行修改和添加。struct stat能够对文件的大小、可访问性等等进行便捷的操作。lseek可以覆盖到任意位置。这些好用的工具都为操作任意二进制字节流提供了安全可靠的接口。
| 优点 | 缺点 |
|---|---|
| 使用了模块化组织工程的结构,每个部分分工明确 | token鉴权没有完整实现 |
| blob分片上传可靠 | 断点续传似乎一直有问题 |
data目录结构清晰,工程二次阅读时能分清楚 |
缺少对大文件下载的管理机制,完全信任Content-Length |
| 缺少清理机制,过期的uploads、blobs会堆积 | |
| 多并发情况下的互斥操作缺少测试 |
- Gemini 2.5 Pro:能够在这样代码量较大的工程里帮助我在不同的时间厘清思路,区分 已经实现 和 尚未实现 的功能。在生成uuid、计算SHA256等陌生领域给出可靠的支持。在代码开发过程中,能够自动帮助校验已经写过的代码,从而在开发过程中就已经避免了大多数bug
- GPT-5:在测试阶段给出了很多好的本地测试方法。agent可以代劳测试-反馈-debug循环,两手一摊只等出结果。特别好用。test文件夹下的各种测试脚本都是他写的。
项目结构
| 路径 | 描述 |
|---|---|
| 📁 DockerItYourself | - |
├── 📄 server.c |
服务器的主程序 |
├── 📄 blob_handler.c/h |
处理与blob增删查改下载等的接口(endpoint) |
├── 📄 manifest_handler.c/h |
处理与manifest增删查改的接口 |
├── 📄 router.c/h |
根据uri分发请求,并提供两个错误返回函数统一调用 |
├── 📄 server_doit.c/h |
供worker线程调用的服务器处理函数 |
├── 📄 dockutils.c/h |
普遍使用的重要功能,如获取headers 根据字段查找headers 解析请求参数等等 |
├── 📄 cJSON/csapp/sbuf |
json文件解析/c语言网络通信和并发等等相关依赖 |
├── 📘 README.md |
readme |
| ├── 📁 Authserver | 假冒的鉴权服务器 |
├──├── 📄 authserver.c |
.c |
| ├── 📁 data | 所有服务器相关数据存储 |
| └── 📁 test | 测试用例或测试bash |
data存储路径
| 路径 | 说明 |
|---|---|
| ./data/blobs/ | 存储所有blobs |
| ./data/blobs/repository_name/digest/ | 根据repo/digest存储不同的blob |
| ./data/blobs/repository_name/uuid/data/ | 存放上传会话进行过程中的临时文件 |
| ./data/manifests/ | 分两类存储所有manifets |
| ./data/manifests/digest/ | 按digest存放manifest |
| ./data/manifests/tags/ | 按tags存放manifest list |