8.1 Swift 包注册表服务规范

原文链接: https://docs.swift.org/latest/documentation/packagemanagerdocs/registryserverspecification/

8.1 Swift 包注册表服务规范

了解 SwiftPM 注册表服务的规范。

1. 记号约定

本文档使用以下术语和约定。

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述来解读。

本规范使用 RFC 5234 中描述的扩展巴科斯-诺尔范式(ABNF)记号,以及 Unicode Technical Standard #18 中描述的 Unicode 正则表达式语法。

在路径中接受参数的 API 端点用统一资源标识符(URI)模板表示,如 RFC 6570 所述。

2. 定义

本文档中使用的下列术语具有如下含义。

  • 包(Package):按 Package.swift 清单文件组织成一个或多个模块的、有名称的 Swift 源代码集合。
  • 作用域(Scope):由包注册表分配的一组相关包的逻辑分组。
  • 发布(Release):包在应用某组特定变更之后的状态,由分配的版本号唯一标识。
  • 版本号(Version Number):符合语义化版本规范(SemVer) 的包发布标识。
  • 优先级(Precedence):按语义化版本规范(SemVer) 定义的各版本号之间的相对顺序。

3. 约定

本文档在描述客户端与服务端交互时使用以下约定。

3.1. 应用层协议

客户端与服务端必须使用 https URI 方案,通过传输层安全(TLS)在安全连接上通信。

示例中使用 HTTP 1.1 并非规范性要求。客户端与服务端可以使用任何版本的 HTTP 协议按本规范通信。

3.2. 认证

对于访问包和包发布信息的客户端请求,服务端可以要求认证。

如果客户端向需要认证的端点发送请求却没有提供凭据,服务端应当响应状态码 401(Unauthorized)。当客户端提供了有效凭据但无权访问所请求的资源时,服务端可以响应状态码 404(Not Found)或 403(Forbidden)。

服务端可以采用自己选择的任何认证模型。不过,推荐使用像 OAuth 2.0 这样作用域受限、可撤销的授权框架。

3.3. 错误处理

服务端必须使用 RFC 7807 描述的“问题详情”(problem details)对象向客户端传达任何错误。例如,客户端请求某个不存在的包发布,会收到如下响应:

HTTP/1.1 404
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "release not found"
}

3.4. 速率限制

服务端可以通过响应状态码 429(Too Many Requests)来限制客户端发起的请求数量。

HTTP/1.1 429
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en
Retry-After: 60

{
   "detail": "try again in 60 seconds"
}

客户端应当遵循响应中提供的任何 Retry-After 头取值的指引,以免用重试请求压垮服务端。推荐客户端在重试逻辑中引入随机抖动,以避免[惊群效应]。

3.5. API 版本管理

包注册表 API 是带版本的。

API 版本号用十进制整数表示。本提案被接受的版本构成初始版本 1。后续修订应当按顺序编号(2、3,依此类推)。

API 版本号应当遵循语义化版本中关于主版本的约定。非破坏性变更——例如新增端点、为已有端点增加新的可选参数,或者以向后兼容的方式为已有端点增加信息——不应要求新版本。破坏性变更——例如以向后不兼容的方式移除或修改已有端点——必须对应一个新版本。

客户端应当设置 Accept 头字段,以指定请求的 API 版本。

GET /mona/LinkedList/list HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+json

有效的 Accept 头字段取值由下列规则描述:

    version     = "1"       ; The API version
    mediatype   = "json" /  ; JSON (default media type)
                  "zip"  /  ; Zip archives, used for package releases
                  "swift"   ; Swift file, used for package manifest
    accept      = "application/vnd.swift.registry" [".v" version] ["+" mediatype]

服务端必须设置 Content-Type 头字段,其值为响应的相应内容类型。

除非另有明确说明,服务端必须设置 Content-Version 头字段,其值为响应的 API 版本号。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Version: 1

如果客户端发送请求时没有带 Accept 头,服务端可以响应状态码 400 Bad Request,也可以用它选择的某个 API 版本来处理该请求,同时确保相应设置 Content-Type 和 Content-Version 头。

如果客户端发送请求时带了 Accept 头,但其中指定了未知或无效的 API 版本,服务端应当响应状态码 400(Bad Request)。

HTTP/1.1 400 Bad Request
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "invalid API version"
}

如果客户端发送请求时带了 Accept 头,其中指定了有效但不受支持的 API 版本,服务端应当响应状态码 415(Unsupported Media Type)。

HTTP/1.1 415 Unsupported Media Type
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "unsupported API version"
}

3.6. 包标识

包可以在其清单中声明外部包作为依赖。每个包依赖都可以指定一条关于允许哪些版本的要求。

外部包依赖自身也可以带有一个或多个外部包依赖,称为传递依赖。当多个包有共同的依赖时,Swift Package Manager 会通过一个称为包解析的过程,确定应当使用该包的哪个版本(如果存在满足所有指定要求的版本)。

每个外部包都由形如 scope.package-name 的带作用域标识符唯一标识。

3.6.1 包作用域

作用域为包注册表中相关的包提供命名空间。包作用域由字母数字字符和连字符组成。连字符不能出现在开头或结尾,也不能在作用域中连续出现。包作用域的最大长度是 39 个字符。有效的包作用域匹配下面这个正则表达式模式:

\A[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}\z

包作用域不区分大小写(例如 mona ≍ MONA)。

3.6.2. 包名

包的名称在某个作用域内唯一标识一个包。包名由字母数字字符、下划线和连字符组成。连字符和下划线不能出现在开头或结尾,也不能在名称中连续出现。包名的最大长度是 100 个字符。有效的包名匹配下面这个正则表达式模式:

\A[a-zA-Z0-9](?:[a-zA-Z0-9]|[-_](?=[a-zA-Z0-9])){0,99}\z

包名不区分大小写(例如 LinkedList ≍ LINKEDLIST)。

4. 端点

服务端必须响应下列端点:

链接方法路径描述
[1]GET/{scope}/{name}列出包的发布版本
[2]GET/{scope}/{name}/{version}获取包发布的元数据
[3]GET/{scope}/{name}/{version}/Package.swift{?swift-version}获取包发布的清单文件
[4]GET/{scope}/{name}/{version}.zip下载包发布的源代码归档
[5]GET/identifiers{?url}查询为某个 URL 注册的包标识
[6]PUT/{scope}/{name}/{version}创建包发布

服务端还应当响应上述每个端点的 HEAD 请求。

客户端可以发送一个带星号(*)的 OPTIONS 请求,以确定服务端允许的通信选项。服务端可以响应一个 Link 头,其中包含一个 service-doc 关系类型的条目(指向本文档的链接),以及一个 service-desc 关系类型的条目(指向 OpenAPI 规范的链接)。


4.1. 列出包的发布版本

客户端可以针对匹配表达式 /{scope}/{name} 的 URI 发送 GET 请求,以获取某个特定包可用发布的列表。客户端应当把 Accept 头设置为 application/vnd.swift.registry.v1+json,并可以在请求的 URI 后追加 .json 扩展名。

GET /mona/LinkedList HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+json

如果在请求的位置找到了包,服务端应当响应状态码 200(OK)以及 Content-Type 头 application/json。否则,服务端应当响应状态码 404(Not Found)。

服务端应当响应一个 JSON 文档,其中包含所请求包的各个发布。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Version: 1
Content-Length: 508
Link: <https://github.com/mona/LinkedList>; rel="canonical",
      <ssh://git@github.com:mona/LinkedList.git>; rel="alternate",
      <https://packages.example.com/mona/LinkedList/1.1.1>; rel="latest-version",
      <https://github.com/sponsors/mona>; rel="payment"

{
    "releases": {
        "1.1.1": {
            "url": "https://packages.example.com/mona/LinkedList/1.1.1"
        },
        "1.1.0": {
            "url": "https://packages.example.com/mona/LinkedList/1.1.0",
            "problem": {
                "status": 410,
                "title": "Gone",
                "detail": "this release was removed from the registry"
            }
        },
        "1.0.0": {
            "url": "https://packages.example.com/mona/LinkedList/1.0.0"
        }
    }
}

响应体必须包含一个嵌套在顶层 releases 键下的 JSON 对象,其键是各发布的版本号,值是包含下列字段的对象:

键类型描述要求级别
urlString该发布资源的位置。OPTIONAL
problemObject一个问题详情对象。OPTIONAL

服务端可以用 url 键指定某个发布的 URL。如果提供了该键,客户端应当使用 url 键的值来定位发布。否则,客户端应当通过在源主机上展开 URI 模板 /{scope}/{name}/{version} 来定位发布。

服务端应当使用一个“问题详情”对象来说明某个包发布不可用。客户端在包解析时应当把任何带有 problem 的发布视为不可用。

如果存在某个包优先级最高的已发布版本,服务端应当用带 latest-version 关系的 Link 头字段提供指向它的链接。

服务端应当按优先级从高到低列出各发布。不过,客户端不应假定响应中各版本有任何特定的顺序。

服务端可以包含一个 canonical 关系类型的 Link 条目,指向该包的源代码仓库。

服务端可以包含一个或多个 alternate 关系类型的 Link 条目,指向其他源代码仓库位置。

服务端可以通过响应一个 Link 头字段来对结果分页,该字段可以包含下列任何关系:

名称描述
next紧邻的下一页结果。
last最后一页结果。
first第一页结果。
prev紧邻的上一页结果。

例如,分页结果第三页响应的 Link 头字段:

Link: <https://packages.example.com/mona/HashMap/5.0.3>; rel="latest-version",
      <https://packages.example.com/mona/HashMap?page=1>; rel="first",
      <https://packages.example.com/mona/HashMap?page=2>; rel="previous",
      <https://packages.example.com/mona/HashMap?page=4>; rel="next",
      <https://packages.example.com/mona/HashMap?page=10>; rel="last"

服务端可以响应额外的 Link 条目,例如带 payment 关系、用于赞助包维护者的条目。

4.2. 获取包发布的信息

客户端可以针对匹配表达式 /{scope}/{name}/{version} 的 URI 发送 GET 请求,以获取某个发布的信息。客户端应当把 Accept 头设置为 application/vnd.swift.registry.v1+json,并可以在请求的 URI 后追加 .json 扩展名。

GET /mona/LinkedList/1.1.1 HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+json

如果在请求的位置找到了发布,服务端应当响应状态码 200(OK)以及 Content-Type 头 application/json。否则,服务端应当响应状态码 404(Not Found)。

HTTP/1.1 200 OK
Content-Version: 1
Content-Type: application/json
Content-Length: 720
Link: <https://packages.example.com/mona/LinkedList/1.1.1>; rel="latest-version",
      <https://packages.example.com/mona/LinkedList/1.0.0>; rel="predecessor-version"
{
  "id": "mona.LinkedList",
  "version": "1.1.1",
  "resources": [
    {
      "name": "source-archive",
      "type": "application/zip",
      "checksum": "a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812",
      "signing": {
        "signatureBase64Encoded": "l1TdTeIuGdNsO1FQ0ptD64F5nSSOsQ5WzhM6/7KsHRuLHfTsggnyIWr0DxMcBj5F40zfplwntXAgS0ynlqvlFw==",
        "signatureFormat": "cms-1.0.0"
      }
    }
  ],
  "metadata": { ... },
  "publishedAt": "2023-02-16T04:00:00.000Z"
}

响应体应当包含一个 JSON 对象,其中含有下列字段:

键类型描述必需
idString带命名空间的包标识符。✓
versionString包发布的版本号。✓
resourcesArray该发布可用的资源。✓
metadataObject关于该发布的附加信息。✓
publishedAtString该包发布被发布时的 ISO 8601 格式日期时间字符串,由注册表记录。参见 metadata 中相关的 originalPublicationTime。

服务端应当响应一个 Link 头,其中包含下列条目:

关系描述
latest-version该包优先级最高的已发布版本
successor-version该包按优先级排序的下一个已发布版本(如果存在)
predecessor-version该包按优先级排序的上一个已发布版本(如果存在)

带 latest-version 关系的链接可以指向所请求的那个发布。

4.2.1. 包发布资源

resources 数组中的每个元素都是一个 JSON 对象,具有下列键:

键类型描述
nameString资源的名称。
typeString资源的内容类型。
checksumString该资源 SHA256 摘要的十六进制表示。
signingObject关于签名的信息。仅当资源已签名时必需。

signing JSON 对象包含这些键:

键类型描述
signatureBase64EncodedString该资源的签名,base64 编码。
signatureFormatString签名格式。(例如 cms-1.0.0)

资源对象的 name 和 type 取值应当是下列组合之一:

名称内容类型描述
source-archiveapplication/zip包源代码的归档。

对于给定的 name 和 type 取值组合,一个发布不能有多个资源对象。

4.2.2. 包发布元数据标准

附录 B 定义了包发布元数据的 JSON schema,这些元数据作为“创建包发布”请求的一部分提交。服务端可以通过扩展该 schema 来允许和/或填充额外的元数据。“获取包发布的信息” API 响应中的 metadata 键会包含用户提供的元数据以及服务端填充的元数据。

4.3. 获取包发布的清单文件

客户端可以针对匹配表达式 /{scope}/{name}/{version}/Package.swift 的 URI 发送 GET 请求,以获取某个发布的包清单文件。客户端应当把 Accept 头设置为 application/vnd.swift.registry.v1+swift。

GET /mona/LinkedList/1.1.1/Package.swift HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+swift

如果在请求的位置找到了发布,服务端应当响应状态码 200(OK)以及 Content-Type 头 text/x-swift。否则,服务端应当响应状态码 404(Not Found)。

HTTP/1.1 200 OK
Cache-Control: public, immutable
Content-Type: text/x-swift
Content-Disposition: attachment; filename="Package.swift"
Content-Length: 361
Content-Version: 1
Link: <http://packages.example.com/mona/LinkedList/1.1.1/Package.swift?swift-version=4>; rel="alternate"; filename="Package@swift-4.swift"; swift-tools-version="4.0",
      <http://packages.example.com/mona/LinkedList/1.1.1/Package.swift?swift-version=4.2>; rel="alternate"; filename="Package@swift-4.2.swift"; swift-tools-version="4.2"

// swift-tools-version:5.0
import PackageDescription

let package = Package(
    name: "LinkedList",
    products: [
        .library(name: "LinkedList", targets: ["LinkedList"])
    ],
    targets: [
        .target(name: "LinkedList"),
        .testTarget(name: "LinkedListTests", dependencies: ["LinkedList"]),
    ],
    swiftLanguageVersions: [.v4, .v5]
)

服务端应当响应一个 Content-Length 头,其值为清单文件的字节大小。

服务端应当响应一个 Content-Disposition 头,其值设为 attachment,并带一个 filename 参数,等于清单文件的名称(例如 “Package.swift”)。

服务端可以省略 Content-Version 头,因为响应内容(即清单文件)不应随不同的 API 版本而变化。

推荐客户端和服务端支持 RFC 7234 所描述的缓存。

对于该发布源代码归档中每一个带版本的包清单文件,其文件名若匹配下面这个正则表达式模式,服务端就必须为它包含一个 Link 头字段取值:

\APackage@swift-(\d+)(?:\.(\d+))?(?:\.(\d+))?.swift\z

每个链接取值应当具有 alternate 关系类型、一个 filename 属性(设为该带版本的包清单文件名,例如 Package@swift-4.swift),以及一个 swift-tools-version 属性(设为该包清单文件所指定的 [Swift 工具版本],例如以注释 // swift-tools-version:4.0 开头的清单文件对应 4.0)。

4.3.1. swift-version 查询参数

客户端可以指定 swift-version 查询参数,以请求适用于某个特定 Swift 版本的清单文件。

GET /mona/LinkedList/1.1.1/Package.swift?swift-version=4.2 HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+swift

如果包中包含名为 Package@swift-{swift-version}.swift 的文件,服务端应当响应状态码 200(OK),并在响应体中包含该文件的内容。

HTTP/1.1 200 OK
Cache-Control: public, immutable
Content-Type: text/x-swift
Content-Disposition: attachment; filename="Package@swift-4.2.swift"
Content-Length: 361
Content-Version: 1

// swift-tools-version:4.2
import PackageDescription

let package = Package(
    name: "LinkedList",
    products: [
        .library(name: "LinkedList", targets: ["LinkedList"])
    ],
    targets: [
        .target(name: "LinkedList"),
        .testTarget(name: "LinkedListTests", dependencies: ["LinkedList"]),
    ],
    swiftLanguageVersions: [.v3, .v4]
)

否则,服务端应当响应状态码 303(See Other),并重定向到不带限定的 Package.swift 资源。

HTTP/1.1 303 See Other
Content-Version: 1
Location: https://packages.example.com/mona/LinkedList/1.1.1/Package.swift

4.4. 下载源代码归档

客户端可以针对匹配表达式 /{scope}/{name}/{version}.zip 的 URI 发送 GET 请求,以获取某个发布的源代码归档。客户端应当把 Accept 头设置为 application/vnd.swift.registry.v1+zip,并且必须在请求路径后追加 .zip 扩展名。

GET /mona/LinkedList/1.1.1.zip HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+zip

如果在请求的位置找到了发布,服务端应当响应状态码 200(OK)以及 Content-Type 头 application/zip。否则,服务端应当响应状态码 404(Not Found)。

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public, immutable
Content-Type: application/zip
Content-Disposition: attachment; filename="LinkedList-1.1.1.zip"
Content-Length: 2048
Content-Version: 1
Digest: sha-256=oqxUzyX7wa0AKPA/CqS5aDO4O7BaFOUQiSuyfepNyBI=
Link: <https://mirror-japanwest.example.com/mona-LinkedList-1.1.1.zip>; rel=duplicate; geo=jp; pri=10; type="application/zip"
X-Swift-Package-Signature-Format: cms-1.0.0
X-Swift-Package-Signature: l1TdTeIuGdNsO1FQ0ptD64F5nSSOsQ5WzhM6/7KsHRuLHfTsggnyIWr0DxMcBj5F40zfplwntXAgS0ynlqvlFw==

服务端必须响应一个 Content-Length 头,其值为该归档的字节大小。对于响应超过预期内容长度的任何请求,客户端应当终止。

服务端可以响应一个 Digest 头,其中包含该源代码归档的加密摘要。

服务端应当响应一个 Content-Disposition 头,其值设为 attachment,并带一个 filename 参数,等于包名后接连字符(-)、版本号和文件扩展名(例如 “LinkedList-1.1.1.zip”)。

服务端可以省略 Content-Version 头,因为响应内容(即源代码归档)不应随不同的 API 版本而变化。

推荐客户端和服务端支持 RFC 7233 所描述的范围请求,以及 RFC 7234 所描述的缓存。

如果发布已签名,服务端必须在响应中包含 X-Swift-Package-Signature-Format 和 X-Swift-Package-Signature 头。

4.4.1. 完整性校验

客户端必须使用 GET /{scope}/{name}/{version} 响应中相关 source-archive 资源的 checksum 值来校验所下载源代码归档的完整性,如 4.2.1 所述。

客户端还应当使用源代码归档响应的 Digest 头中提供的任何取值来校验完整性(例如使用命令 shasum -b -a 256 LinkedList-1.1.1.zip | cut -f1 | xxd -r -p | base64)。

4.4.2. 下载位置

服务端可以使用带 duplicate 关系的 Link 头字段来指定镜像或多个下载位置,如 RFC 6249 所述。客户端可以使用这些信息来确定自己偏好的下载策略。

服务端可以响应状态码 303(See Other),把客户端重定向到另一个主机去下载源代码归档。客户端不得跟随会降级到不安全连接的重定向。客户端应当限制重定向次数,以避免重定向循环。

例如,服务端把客户端重定向到内容分发网络(CDN),使用带签名的 URL 下载:

HTTP/1.1 303 See Other
Location: https://example.cdn.com/LinkedList-1.1.1.zip?key=XXXXXXXXXXXXXXXXX
GET /LinkedList-1.1.1.zip?key=XXXXXXXXXXXXXXXXX HTTP/1.1
Host: example.cdn.com
Accept: application/vnd.swift.registry.v1+zip
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public, immutable
Content-Type: application/zip
Content-Disposition: attachment; filename="LinkedList-1.1.1.zip"
Content-Length: 2048
Content-Version: 1
Digest: sha-256=a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812
4.4.3. 签名验证

客户端必须按照签名格式和配置验证已签名归档的签名。签名信息也可以在 GET /{scope}/{name}/{version} 响应中相关的 source-archive 资源里找到,如 4.2.1 所述。

4.5. 查询为某个 URL 注册的包标识

客户端可以针对匹配表达式 /identifiers?url={url} 的 URI 发送 GET 请求,以获取与某个特定 URL 关联的包标识。客户端应当把 Accept 头设置为 application/vnd.swift.registry.v1+json。

GET /identifiers?url=https://github.com/mona/LinkedList HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1

客户端必须为 url 查询参数提供 URL。如果没有指定 url 参数,服务端应当响应状态码 400(Bad Request)。

如果有一个或多个包标识与指定的 URL 关联,服务端应当响应状态码 200(OK)以及 Content-Type 头 application/json。否则,服务端应当响应状态码 404(Not Found)。

服务端应当响应一个 JSON 文档,其中包含为指定 URL 注册的包标识。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Version: 1

{
    "identifiers": [
      "mona.LinkedList"
    ]
}

响应体必须包含一个嵌套在顶层 identifiers 键下的包标识字符串数组。

推荐客户端和服务端支持 RFC 7234 所描述的缓存。

4.5.1 URL 到包标识的映射

作为包发布元数据 JSON 对象的一部分,repositoryURLs 数组可以用来指定与某个包标识关联的 URL。这是服务端为该 API 获取 URL 到包标识映射的途径之一。

服务端可以选择其他机制让包作者指定这些映射。

服务端应当验证包作者对所对应仓库的所有权主张。

4.6. 创建包发布

客户端可以针对匹配表达式 /{scope}/{name}/{version} 的 URI 发送 PUT 请求,以发布某个包的某个发布。客户端必须提供一个以 multipart 表单数据编码的请求体,其中包含下列分段:

键Content-Type描述要求级别
source-archiveapplication/zip该包的源代码归档。REQUIRED
source-archive-signatureapplication/octet-stream源代码归档的签名。OPTIONAL
metadataapplication/json关于该发布的附加信息。OPTIONAL
metadata-signatureapplication/octet-stream元数据的签名。OPTIONAL

客户端必须把 Content-Type 头设置为 multipart/form-data。boundary 可以是任意字符串。

客户端可以为 Content-Transfer-Encoding 头使用任何有效取值(例如 binary)。

客户端应当把 Content-Length 头设置为请求体的总字节大小。

客户端应当把 Accept 头设置为 application/vnd.swift.registry.v1+json。

如果源代码归档已签名,客户端必须设置 X-Swift-Package-Signature-Format 头,其值为签名格式。

PUT /mona/LinkedList/1.1.1 HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1+json
Content-Type: multipart/form-data;boundary="boundary"
Content-Length: 336
Expect: 100-continue
X-Swift-Package-Signature-Format: cms-1.0.0

--boundary
Content-Disposition: form-data; name="source-archive"
Content-Type: application/zip
Content-Length: 32
Content-Transfer-Encoding: base64

gHUFBgAAAAAAAAAAAAAAAAAAAAAAAA==

--boundary
Content-Disposition: form-data; name="source-archive-signature"
Content-Type: application/octet-stream
Content-Length: 88
Content-Transfer-Encoding: base64

l1TdTeIuGdNsO1FQ0ptD64F5nSSOsQ5WzhM6/7KsHRuLHfTsggnyIWr0DxMcBj5F40zfplwntXAgS0ynlqvlFw==

--boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
Content-Transfer-Encoding: quoted-printable
Content-Length: 3

{ "repositoryURLs": [] }

--boundary
Content-Disposition: form-data; name="metadata-signature"
Content-Type: application/octet-stream
Content-Length: 88
Content-Transfer-Encoding: base64

M6TdTeIuGdNsO1FQ0ptD64F5nSSOsQ5WzhM6/7KsHRuLHfTsggnyIWr0DxMcBj5F40zfplwntXAgS0ynlqvlFw==

对于任何创建包发布的请求,服务端应当要求客户端进行认证。推荐使用多因素认证。

客户端可以按任意顺序发布各个发布。例如,如果某个包已有 1.0.0 和 2.0.0 两个发布,客户端可以发布新的 1.0.1 或 1.1.0 发布。

发布一旦发布,与该发布相关的任何资源(包括其源代码归档)都不得再更改。

如果在指定版本上该包已存在某个发布,服务端应当响应状态码 409(Conflict)。

HTTP/1.1 409 Conflict
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "a release with version 1.0.0 already exists"
}

推荐服务端在作用域被转移给新所有者之后,为发布该包的新版本制定策略。例如,现有包的下一个发布以新的主版本号发布,或者只在转移满 45 天之后才允许发布。

如果客户端提供了 Expect 头,服务端应当先检查该请求能否成功,再响应状态码 100 (Continue)。不支持 expectation 的服务端应当响应状态码 417 (Expectation Failed)。作为回应,客户端可以移除 Expect 头并重试该请求。

HTTP/1.1 417 (Expectation Failed)
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "expectations aren't supported"
}

对这个端点的支持是可选的。服务端应当通过响应状态码 405(Method Not Allowed)来表明不支持发布。

HTTP/1.1 405 (Method Not Allowed)
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "publishing isn't supported"
}

服务端可以同步或异步地响应。更多信息参见 4.6.3。

4.6.1. 源代码归档

客户端必须包含一个名为 source-archive 的 multipart 分段,其中含有该发布的源代码归档。客户端应当把 Content-Type 头设置为 application/zip,并把 Content-Length 头设置为该 Zip 归档的字节大小。

--boundary
Content-Disposition: form-data; name="source-archive"
Content-Type: application/zip
Content-Length: 32
Content-Transfer-Encoding: base64

gHUFBgAAAAAAAAAAAAAAAAAAAAAAAA==

客户端应当使用 swift package archive-source 工具为该发布创建源代码归档。

服务端可以分析某个包以评估其可用性、执行安全测试,或者以其他方式评估软件质量。服务端可以出于任何理由,通过响应状态码 422(Unprocessable Entity)拒绝发布某个包发布。

HTTP/1.1 422 Unprocessable Entity
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "package doesn't contain a valid manifest (Package.swift) file"
}

服务端应当使用 swift package compute-checksum 工具计算校验和,该值会在客户端后续请求下载该发布的源代码归档时作为响应提供。

4.6.2. 包发布元数据

客户端可以包含一个名为 metadata 的 multipart 分段,其中含有关于该发布的附加信息。客户端应当把 Content-Type 头设置为 application/json,并把 Content-Length 头设置为该 JSON 文档的字节大小。包发布元数据必须基于该 JSON schema,如 4.2.2 所述。

--boundary
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
Content-Length: 226
Content-Transfer-Encoding: quoted-printable

{
  "description": "One thing links to another.",
  "repositoryURLs": ["https://github.com/mona/LinkedList"],
  "licenseURL": "https://www.apache.org/licenses/LICENSE-2.0",
  "author": {
      "name": "Mona Lisa Octocat"
  }
}

服务端可以允许和/或填充该发布的额外元数据。

服务端可以把该 JSON schema 中的任何属性以及它定义的其他元数据设为必需。

如果客户端提供了无效的 JSON 文档,服务端应当响应状态码 422(Unprocessable Entity)或 413(Payload Too Large),并可以在响应体中传达校验错误的细节。

HTTP/1.1 422 Unprocessable Entity
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "invalid JSON provided for release metadata"
}
4.6.3. 同步发布与异步发布

服务端可以同步或异步地响应发布新包发布的请求。

客户端可以用一个包含 respond-async 令牌以及可选 wait 偏好的 Prefer 头字段,来表达自己希望采用异步处理,如 RFC 7240 所述。

PUT /mona/LinkedList/1.1.1 HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1
Prefer: respond-async, wait=300
4.6.3.1. 同步发布

如果处理是同步完成的,服务端必须响应状态码 201(Created),表示该包发布已发布。该响应还应当包含一个 Location 头,其 URL 指向这个新发布。

HTTP/1.1 201 Created
Content-Version: 1
Location: https://packages.example.com/github.com/mona/LinkedList/1.1.1

客户端可以设置超时,以保证每个请求都能及时得到响应。

4.6.3.2. 异步发布

如果处理是异步完成的,服务端必须响应状态码 202(Accepted),表示已收到并正在处理该请求。该响应必须包含一个 Location 头,其 URL 可供客户端轮询进度更新;并且应当包含一个 Retry-After 头,给出预计处理完成时间的估计。服务端可以把状态资源的端点放在它自己选择的 URI 上。不过,推荐使用非顺序的、随机生成的标识符。

HTTP/1.1 202 Accepted
Content-Version: 1
Location: https://packages.example.com/submissions/90D8CC77-A576-47AE-A531-D6402C4E33BC
Retry-After: 120

客户端可以向服务端在发布请求响应中提供的位置发送 GET 请求,以查看该过程的当前状态。

GET /submissions/90D8CC77-A576-47AE-A531-D6402C4E33BC HTTP/1.1
Host: packages.example.com
Accept: application/vnd.swift.registry.v1

如果异步发布请求仍在处理中,服务端应当响应状态码 202(Accepted)以及一个 Retry-After 头,给出预计处理完成时间的估计。服务端可以在响应体中包含额外的细节。

HTTP/1.1 202 Accepted
Content-Version: 1
Content-Type: application/json
Retry-After: 120

{
  "status": "Processing (2/3 steps complete)",
  "steps": {
    {"name": "Validate metadata", "status": "complete"},
    {"name": "Verify package manifest", "status": "complete"},
    {"name": "Scan for vulnerabilities", "status": "pending"}
  }
}

如果异步发布请求已成功处理完毕,服务端应当响应状态码 301(Moved Permanently)以及一个 Location 头,其 URL 指向该包发布。

HTTP/1.1 301 Moved Permanently
Content-Version: 1
Location: https://packages.example.com/mona/LinkedList/1.1.1

如果异步发布请求失败,服务端应当响应一个合适的客户端错误状态码(4xx)。

HTTP/1.1 400 Bad Request
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en
Location: https://packages.example.com/submissions/90D8CC77-A576-47AE-A531-D6402C4E33BC

{
   "detail": "invalid package"
}

客户端可以向服务端在发布请求响应中提供的位置发送 DELETE 请求,以取消该过程。

如果发布新包发布的请求会失败,服务端在立即响应时传达失败的方式,必须与响应客户端轮询状态时的方式一致。

如果客户端向一个正在异步处理某个包发布请求的服务端发起发布该发布的请求,服务端必须响应状态码 409(Conflict)

HTTP/1.1 409 Conflict
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en
Location: https://packages.example.com/submissions/90D8CC77-A576-47AE-A531-D6402C4E33BC

{
   "detail": "already processing a request to publish this package version"
}

如果客户端向一个已处理完某个失败的包发布请求的服务端发起发布该发布的请求,服务端应当再次尝试发布该发布。服务端可以通过响应状态码 409(Conflict)来拒绝满足后续发布某个包发布的请求。

5. 规范性引用文件

  • RFC 2119:Key words for use in RFCs to Indicate Requirement Levels
  • RFC 3230: Instance Digests in HTTP
  • RFC 3986: Uniform Resource Identifier (URI): Generic Syntax
  • RFC 3987: Internationalized Resource Identifiers (IRIs)
  • RFC 5234: Augmented BNF for Syntax Specifications: ABNF
  • RFC 5843: Additional Hash Algorithms for HTTP Instance Digests
  • RFC 6249: Metalink/HTTP: Mirrors and Hashes
  • RFC 6570: URI Template
  • RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format
  • RFC 7230: Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing
  • RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
  • RFC 7233: Hypertext Transfer Protocol (HTTP/1.1): Range Requests
  • RFC 7234: Hypertext Transfer Protocol (HTTP/1.1): Caching
  • RFC 7240: Prefer Header for HTTP
  • RFC 7578: Returning Values from Forms: multipart/form-data
  • RFC 7807: Problem Details for HTTP APIs
  • RFC 8288: Web Linking
  • SemVer: Semantic Versioning

6. 参考性引用文件

  • BCP 13 Media Type Specifications and Registration Procedures
  • RFC 6749: The OAuth 2.0 Authorization Framework
  • RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3
  • RFC 8631: Link Relation Types for Web Services
  • JSON-LD: A JSON-based Serialization for Linked Data
  • Schema.org: A shared vocabulary for structured data.
  • OAS: OpenAPI Specification

附录 A —— OpenAPI 文档

下面的 OpenAPI (v3) 规范 是非规范性的,提供给有兴趣构建自有包注册表的开发者参考。

参见 registry.openapi.yaml。

附录 B —— 包发布元数据 JSON Schema

创建包发布请求中的 metadata 部分必须是一个类型为 PackageRelease 的 JSON 对象,如下面的 JSON schema 所定义。

展开以查看 JSON schema
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md",
  "title": "Package Release Metadata",
  "description": "Metadata of a package release.",
  "type": "object",
  "properties": {
    "author": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Name of the author."
        },
        "email": {
          "type": "string",
          "format": "email",
          "description": "Email address of the author."
        },
        "description": {
          "type": "string",
          "description": "A description of the author."
        },
        "organization": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string",
              "description": "Name of the organization."
            },
            "email": {
              "type": "string",
              "format": "email",
              "description": "Email address of the organization."
            },
            "description": {
              "type": "string",
              "description": "A description of the organization."
            },
            "url": {
              "type": "string",
              "format": "uri",
              "description": "URL of the organization."
            },
          },
          "required": ["name"]
        },
        "url": {
          "type": "string",
          "format": "uri",
          "description": "URL of the author."
        },
      },
      "required": ["name"]
    },
    "description": {
      "type": "string",
      "description": "A description of the package release."
    },
    "licenseURL": {
      "type": "string",
      "format": "uri",
      "description": "URL of the package release's license document."
    },
    "originalPublicationTime": {
      "type": "string",
      "format": "date-time",
      "description": "Original publication time of the package release in ISO 8601 format."
    },
    "readmeURL": {
      "type": "string",
      "format": "uri",
      "description": "URL of the README specifically for the package release or broadly for the package."
    },
    "repositoryURLs": {
      "type": "array",
      "description": "Code repository URL(s) of the package release.",
      "items": {
        "type": "string",
        "description": "Code repository URL."
      }
    }
  }
}
PackageRelease 类型
属性类型描述必需
authorAuthor该包发布的作者。
descriptionString该包发布的描述。
licenseURLString该包发布许可证文档的 URL。
originalPublicationTimeString该包发布的原始发布时间,采用 ISO 8601 格式。如果该包发布此前曾在别处发布过,可以设置这个字段。
注册表应当独立记录发布时间,并在包发布元数据响应中把它作为 publishedAt 包含进去。
如果同时设置了 originalPublicationTime 和 publishedAt,应当使用 originalPublicationTime。
readmeURLString专门针对该包发布、或者面向整个包的 README 的 URL。
repositoryURLsArray该包的代码仓库 URL。建议把同一仓库的所有 URL 变体(例如 SSH、HTTPS)都包含进来。如果该包没有源代码管理表示,这个数组可以为空。
设置这个属性是注册表为“查询为某个 URL 注册的包标识” API获取仓库 URL 到包标识映射的途径之一。注册表可以选择其他机制让包作者指定这类映射。
Author 类型
属性类型描述必需
nameString作者的姓名。✓
emailString作者的电子邮件地址。
descriptionString作者的描述。
organizationOrganization作者所属的组织。
urlString作者的 URL。
Organization 类型
属性类型描述必需
nameString组织的名称。✓
emailString组织的电子邮件地址。
descriptionString组织的描述。
urlString组织的 URL。