PEP 694 – 上传 Python 包存储库的 2.0 API

猫勺猫勺 03-16 164 阅读 0 评论

抽象

目前没有用于将文件上传到 Python 包的标准化 API 存储库,例如 PyPI。相反,每个人都被迫进行逆向工程 来自 PyPI 的非标准 API。

该 API 虽然功能正常,但泄露了原始 API 的许多实现细节 PyPI 代码库,现在必须在新的 代码库和替代实现。

除上述问题外,当前 API 还存在许多主要问题:

  • 它是一个完全同步的 API,这意味着我们被迫拥有一个单一的 请求可能会保持打开很长时间,无论是对于上传本身, 然后,当存储库处理上传的文件以确定成功时 或失败。

  • 它不支持任何恢复上传的机制,其中文件最大 PyPI 的大小略低于 1GB,如果出现这种情况,则浪费了很多带宽 大文件在上传结束时出现网络闪烁。

  • 它将单个文件视为原子操作单元,这可能会有问题 当一个版本可能有多个二进制轮时,这可能会导致人们获得 在上传文件时的不同版本,如果 SDIST 碰巧 不是最后去,可能正在尝试构建一些难以构建的软件包 从源头。

  • 它对与用户通信的支持非常有限,没有任何支持 对于多个错误、警告、弃用等。它完全限于 HTTP 状态代码和原因短语,其中原因短语是 自 HTTP/2 (RFC 7540) 起已弃用。

  • 发布/文件的元数据与文件一起提交,但是这 元数据是出了名的不可靠,大多数安装程序都选择下载 整个文件并读取它,部分原因是这种不可靠性。

  • 没有允许存储库执行任何理智的机制 在带宽开始在上传上消耗之前进行检查,而很多 可以检查无效元数据或不正确权限的情况 在上传之前。

  • 它不支持在将草稿发布到之前将其“暂存”到 存储 库。

  • 它不支持在不上传文件的情况下创建新项目。

PEP 提出了一个用于上传的新 API,并弃用了现有的非标准 应用程序接口。

现状

这并不是试图成为当前 API 的完全详尽的文档,而是 对现有 API 进行高级概述。

端点

现有的上传 API(以及现已删除的注册 API)位于 url 中,用于传达您想要的特定 API 若要调用,请添加值为 的 URL 参数。价值观 的 、 和 也曾经被支持,但是 不再是。https://upload.pypi.org/legacy/:actionfile_uploadsubmitsubmit_pkg_infodoc_upload

它还有一个参数,理论上允许新版本的 API 被编写,但实际上从未发生过,并且值始终为 .protocol_version1

因此,在实践中,在 PyPI 上,端点是 .https://upload.pypi.org/legacy/?:action=file_upload&protocol_version=1

编码

要提交的数据将作为具有内容类型的请求提交 之。这是由于历史性质,这个 API 实际上并没有被设计为一个 API,而是初始 PyPI 上的一个表单 实现,然后编写客户端代码以编程方式提交该表单。POSTmultipart/form-data

内容

粗略地说,包中包含的元数据是作为部分提交的 其中 content-disposition 是 ,name 是 田。这些不同元数据的名称没有记录在案,它们 有时,但并不总是与文件中使用的名称匹配。外壳 虽然很少匹配,但总的来说,到 转换是 极度不一致。form-dataMETADATAMETADATAform-data

然后,文件本身将作为带有名称的部分发送 的,如果附加了 PGP 签名,则它将被包括在内 作为名称为 的部分。application/octet-streamcontentapplication/octet-streamgpg_signature

规范

此 PEP 跟踪现有 API 的大多数问题的根本原因 大致有两件事:

  • 元数据与文件一起提交,而不是从 文件本身。

  • 如果用作预检查,这实际上很好,但应该对其进行验证 针对发行版中的实际或类似文件。METADATA

它支持单个请求,仅使用表单数据,任一成功 或失败,并且一切都已完成并包含在该单个请求中。

然后,我们提出了一个多请求工作流,它基本上可以归结为:

  1. 启动上载会话。

  2. 在上传会话中上传文件。

  3. 完成上传会话。

  4. (可选)检查上传会话的状态。

此处描述的所有 URL 都将相对于根终结点,这可能是 位于域的 URL 结构中的任何位置。所以它可能在 ,或者 。https://upload.example.com/https://example.com/upload/

版本控制

此 PEP 使用与 PEP 691 中使用的相同的版本控制系统, 但它在其他方面是独立版本的。现有 API 由 此规范为 版本 ,但除此之外,它不会尝试修改 以任何方式使用该 API。MAJOR.MINOR1.0

端点

创建上传会话

要创建新的上传会话,您可以向 发送请求 , 有效负载如下所示:POST/

{  "meta": {    "api-version": "2.0"  },  "name": "foo",  "version": "1.0"}

它目前有三个键,、 和 。metanameversion

该密钥包含在所有有效负载中,它描述了有关 有效载荷本身。meta

键是此会话尝试的项目的名称 将文件添加到。name

关键是此会话所访问的项目版本 将文件添加到。version

如果创建会话成功,则服务器必须返回响应 如下所示:

{  "meta": {    "api-version": "2.0"  },  "urls": {    "upload": "...",    "draft": "...",    "publish": "..."  },  "valid-for": 604800,  "status": "pending",  "files": {},  "notices": [    "a notice to display to the user"  ]}

除了键之外,此响应还有五个键:、、、和。metaurlsvalid-forstatusfilesnotices

键是将标识符映射到相关 URL 的字典 会期。urls

键是一个整数,表示直到 服务器本身将使此会话过期(因此,其中包含的所有 URL)都将过期。 会话至少应该存在更长的时间,除非客户端本身 已取消会话。服务器可以选择增加这个时间,但应该 永远不要减少它,除非通过时间的流逝自然而然地减少它。valid-for

键是一个字符串,其中包含 、 、 或 之一,此字符串表示 会话。statuspendingpublishederroredcanceled

密钥是包含已上传的文件名的映射 到此会话,到包含有关每个文件的详细信息的映射。files

该键是一个可选键,它指向一系列通知,该通知 服务器希望与最终用户进行通信,但这些通信不是特定于任何用户的 一个文件。notices

对于映射中的每个文件名,有三个键 , , , 和。filesstatusurlnotices

该密钥与顶级密钥相同,只是它 指示特定文件的状态。statusstatus

密钥是客户端应上传该特定 URL 的绝对 URL 文件添加到(或用于删除该文件)。url

该键是一个可选键,即服务器通知的数组 希望与特定于此文件的最终用户进行通信。notices

成功创建会话所需的响应代码是响应,它必须包含一个标头,该标头是 此会话的 URL,可用于检查其状态或取消它。201 CreatedLocation

对于密钥,目前可能会出现三个密钥:urls

密钥,这是此会话要启动的上传终结点 文件上传。upload

密钥,即提供这些文件的存储库 URL 在发布之前。draft

密钥,即触发发布会话的终结点。publish

除上述内容外,如果为相同的名称+版本创建第二个会话 pAIr,则上传服务器必须返回已经存在的会话,而不是 而不是创建一个新的、空的。

上传每个文件

启动一个或多个文件的上传会话后,您就拥有 实际上传每个文件。

没有用于实际上传文件的设置端点,该端点被赋予 客户端作为创建上传会话的一部分,并且客户端不得假定这些 URL 的外观有任何共性 一个会话到下一个会话。

要启动文件上传,客户端会向上传 URL 发送请求 在会话中,请求正文如下所示:POST

{  "meta": {    "api-version": "2.0"  },  "filename": "foo-1.0.tar.gz",  "size": 1000,  "hashes": {"sha256": "...", "blake2b": "..."},  "metadata": "..."}

除了标准密钥外,目前还有 4 个密钥:meta

  • filename:正在上传的文件的文件名。

  • size:正在上传的文件的大小(以字节为单位)。

  • hashes:哈希名称到十六进制编码摘要的映射,每个摘要 是该文件的摘要,当由名称中标识的哈希进行哈希处理时。

默认情况下,任何可通过 hashlib 获得的哈希算法(特别是任何可以 被传递给并且不需要额外的参数)可以 用作哈希字典的键。必须始终包含至少一种来自 SECURE 算法。当时 特别推荐使用此 PEP。hashlib.new()hashlib.algorithms_guaranteedsha256

一次可以传递多个哈希值,但所有哈希值必须对 文件。

  • metadata:一个可选键,它是包含文件核心元数据的字符串。

服务器可以使用此响应中提供的数据进行一些健全性检查 在允许上传文件之前,这可能包括但不限于 自:

  • 检查是否已存在。filename

  • 检查是否会使某些配额无效。size

  • 检查 的内容是否有效(如果提供)。metadata

如果服务器确定客户端应尝试上传,它将返回 一个响应,正文为空,标头指向 到文件本身应上传到的 URL。201 CreatedLocation

此时,会话的状态应显示文件名,并带有上述 url 包含在其中。

上传数据

要上传文件,客户端有两种选择,他们可以将文件上传为 单个块,或多个块。任何一种选择都是可以接受的,但它是 建议大多数客户端应选择将每个文件作为单个块上传 因为这需要更少的请求,并且通常具有更好的性能

但是,对于特别大的文件,可能会导致在单个请求中上传 在超时,因此可能需要在多个区块中上传较大的文件。

无论哪种情况,客户端都必须为每次上传生成唯一的令牌(或随机数) 尝试获取文件,并且必须在标头的每个请求中包含该令牌。是使用 base64 编码的二进制 blob ,周围环绕着 a 在两侧。客户端应至少使用 32 字节的加密 随机数据。您可以使用以下命令生成它:Upload-TokenUpload-Token:

import base64import secretsheader = ":" + base64.b64encode(secrets.token_bytes(32)).decode() + ":"

允许从上传中省略的一次 请求是指客户端希望选择退出可恢复或分块文件上传 功能完全。在这种情况下,他们可以省略 和 文件必须在单个 HTTP 请求中成功上传,如果失败,则 必须在另一个 HTTP 请求中重新发送整个文件。Upload-TokenUpload-Token

要在单个块中上传,客户端会从 该文件名的会话响应。客户端必须包含一个标头,该标头等于文件的大小(以字节为单位),并且该标头必须与 原始会话创建中给出的大小。POSTContent-Length

例如,如果上传一个 100,000 字节的文件,您将发送如下标头:

Content-Length: 100000Upload-Token: :nYuc7Lg2/Lv9S4EYoT9WE6nwFZgN/TcUXyk9wtwoABg=:

如果上传成功完成,服务器必须以状态进行响应。此时,此文件不得存在于 存储库,但只是暂存,直到上传会话完成。201 Created

要上传多个块,客户端会向同一个块发送多个请求 URL 和以前一样,每个块一个。POST

然而,这一次,等于 他们正在发送的块。此外,客户端必须包含一个标头,该标头指示内容包含的字节偏移量 在此请求中,从 开始,标头设置为 。Content-LengthUpload-OffsetUpload-Incomplete1

例如,如果以 1000 字节的块上传 100,000 字节的文件,并且此块 表示字节 1001 到 2000,您可以发送如下标头:

Content-Length: 1000Upload-Token: :nYuc7Lg2/Lv9S4EYoT9WE6nwFZgN/TcUXyk9wtwoABg=:Upload-Offset: 1001Upload-Incomplete: 1

但是,最后一个数据块省略了标头,因为 此时,上传不再不完整。Upload-Incomplete

对于每个成功的块,服务器必须使用标头进行响应,但最后一个块除外,该块必须是 .202 Accepted201 Created

以下限制将针对上传,无论它们是否 单个块或多个块:

  • 客户端不得并行执行多个请求 相同的文件,以避免竞争条件和数据丢失或损坏。服务器可以终止使用相同 .POSTPOSTUpload-Token

  • 如果 中提供的偏移量不是或下一个块 在不完成的上传中,则服务器必须以 409 冲突进行响应。Upload-Offset0

  • 使用特定令牌开始上传后,不得使用其他令牌 对于该文件,而不删除正在进行的上传。

  • 文件上传成功后,您可以启动另一次上传 该文件,这样做将替换该文件。

简历上传

要恢复上传,您首先必须知道服务器有多少数据 已收到,无论您最初是否将文件上传为 单个块或多个块。

要获取单个上传的状态,客户端可以发出请求 与它们现有的 URL 相同。HEADUpload-Token

服务器必须使用响应进行响应,并带有指示客户端应继续的偏移量的标头 上传自。如果服务器没有收到任何数据,则这将是 , 如果它接收了 1007 个字节,那么它将是 .204 No ContentUpload-Offset01007

一旦客户端检索到了他们需要从中开始的偏移量,他们就可以 如上所述,在单个请求中上传文件的其余部分 包含所有剩余数据或多个块。

取消正在进行的上传

如果客户希望取消特定文件的上传,例如,因为 他们需要上传不同的文件,他们可以通过向文件上传 URL 发出请求来上传 文件。DELETEUpload-Token

成功的取消请求必须以 .204 No Content

删除已上传的文件

可以通过向文件发出请求来删除已上传的文件 上传 URL 不带 .DELETEUpload-Token

成功的删除请求必须以 .204 No Content

会话状态

与文件上传类似,会话 URL 在响应中提供 创建上传会话,并且客户端不得假定存在任何 这些 URL 从一个会话到下一个会话的外观的共性。

要检查会话的状态,客户端会向 会话 URL,服务器将使用与 他们在最初创建上传会话时获得,但任何 对 、 或 updated 的更改已反映。GETstatusvalid-forfiles

会话取消

要取消上传会话,客户端会向 与以前相同的会话 URL。此时,服务器将会话标记为 已取消,则可以清除作为该会话的一部分上传的任何数据, 将来尝试访问该会话 URL 或任何文件上传 URL 可能会返回 .DELETE404 Not Found

为了防止大量悬空会话,服务器还可以选择取消 会议自发。建议服务器清除其 会话后不少于一周,但每个服务器可以选择自己的 附表。

会话完成

要完成会话并发布已包含在其中的文件, 客户端必须向 会话状态有效负载。POSTpublish

如果服务器能够立即完成会话,它可能会这样做 并返回响应。如果无法立即 完成会话(例如,如果它需要进行处理,则可能 在单个 HTTP 请求中花费的时间超过合理时间),然后它可能会返回 一个回应。201 Created202 Accepted

无论哪种情况,服务器都应包含指向的标头 返回到会话状态 URL,如果服务器返回 , 客户端可能会轮询该 URL 以监视状态是否更改。Location202 Accepted

错误

所有包含正文的错误响应的正文如下所示:

{  "meta": {    "api-version": "2.0"  },  "message": "...",  "errors": [    {      "source": "...",      "message": "..."    }  ]}

除了标准键之外,它还有两个顶级键和 .metamessageerrors

密钥是一条单数消息,它封装了所有错误 可能已在此请求上发生。message

键是特定错误的数组,每个错误都包含 一个键,它是一个字符串,指示 错误是,并且是该特定错误的键。errorssourcemessasge

和 字符串没有任何特定含义,并且 旨在供人类解释,以找出根本问题 是。messagesource

内容类型

与 PEP 691 一样,此 PEP 建议来自 上传 API 将具有描述内容的标准内容类型 是,它代表的 API 是什么版本,以及序列化格式是什么 被使用过。

此内容类型的结构为:

application/vnd.pypi.upload.$version+format

由于只有主要版本才应该对试图 了解这些 API 内容主体之一,只有主要版本将是 包含在内容类型中,并将以 a 为前缀以阐明 它是一个版本号。v

与 PEP 691 不同,此 PEP 不会更改任何 方式,因此将需要服务器来托管此 PEP 中描述的新 API,网址为 与现有上传 API 不同的终结点。1.0

这意味着,对于新的 2.0 API,内容类型为:

JSON格式: application/vnd.pypi.upload.v2+json

除上述内容外,还支持一个名为 , 其目的是允许客户端请求绝对最新版本,而无需 必须提前知道那个版本是什么。但是,建议: 客户明确说明他们支持哪些版本。latest

这些内容类型不适用于文件上传本身,仅适用于 上传 API 中的其他 API 请求/响应。文件本身应该使用 内容类型。application/octet-stream

版本 + 格式选择

与 PEP 691 类似,此 PEP 标准化使用服务器驱动 内容协商,允许客户端请求不同的版本,或者 序列化格式,其中包括 url 参数。format

由于此 PEP 期望现有的旧版上传 API 存在于 不同的端点,并且它目前只提供 JSON 序列化,这个 机制不是特别有用,客户端只有一个版本和 他们可以请求的序列化。但是,客户端应设置为处理 在附加格式或版本的情况下,内容协商优雅 将来会添加。1.0

常见问题

这是否意味着 PyPI 计划放弃对现有上传 API 的支持?

目前,PyPI 没有任何具体的计划来放弃对 现有上传 API。

与 PEP 691 不同,这样做有广泛的好处,因此很有可能 我们希望在未来的某个时候放弃对它的支持,但是 在此 API 实现并得到广泛使用之前,它还为时过早 制定任何计划来实际放弃对它的支持。

这个断点续传协议是基于什么的吗?

是的!

它实际上是 Active Internet-Draft 中指定的协议, 作者利用他们在实施 TUS 时学到的知识,以完全通用的、基于标准的方式提供可恢复上传的想法 道路。

我们与该规范的唯一偏差是,我们没有在第一个请求中使用信息响应。做出此决定有几个原因:104 Upload Resumption SupportedPOST

  • 这是该草案的唯一部分 它不完全依赖于 现有标准,因为它增加了新的信息状态。104 Upload Resumption Supported

  • 许多客户端和 Web 框架不支持信息性 以一种非常好的方式做出回应,如果有的话,添加它会变得复杂 实施几乎没有好处。1xx

  • 支持的目的是允许 客户端,以确定他们正在交互的任意终结点 支持断点续传。由于此 PEP 强制要求支持 在服务器中,客户端可以假设它们是服务器 与支持它交互,因此不需要使用它。104 Upload Resumption Supported

  • 从理论上讲,如果对响应的支持得到解决,并且草案 被接受,我们可以在以后没有 更改 API 的整体流程。1xx

上述草案存在不被接受的风险,但即使它被接受 没有,这实际上并没有影响我们。这只是意味着我们的 对断点续传的支持是特定于应用程序的协议,但 仍然完全符合标准。

开放性问题

分段上传与 tus

此 PEP 目前基于互联网草稿的实际文件上传 从支持断点续传文件上传的 tus.io。

该协议需要满足以下几点要求:

  • 客户端选择用于标识的安全 上传单个文件。Upload-Token

  • 如果客户端没有一次性上传整个文件,那么他们有 以正确的顺序连续提交块,除 最后一个块有一个标头。Upload-Incomplete: 1

  • 恢复上传本质上只是查询服务器以查看如何 他们得到的大量数据,然后发送剩余的字节(作为单个字节 请求,或分块)。

  • 当服务器成功获取所有 来自客户端的数据。

这有一个很大的好处,如果客户不关心恢复他们的 下载,工作支持,从客户端,断点续传是可以的 被完全忽略。他们可以只将文件发送到 URL,如果 它没有成功,他们可以再次打开整个文件。POSTPOST

另一个好处是,即使您确实想支持恢复,也可以 仍然只是文件,除非您需要恢复下载, 这就是你所要做的。POST

另一个可能是理论上的好处是,对于上传的文件进行哈希处理, 串行块要求意味着服务器可以保持哈希状态 在请求之间,为每个请求更新它,然后将该文件写回 存储。不幸的是,这实际上不可能与 Python 的 hashlib 一起完成, 虽然有一些像 Rehash 这样的库实现了它,但它们并不支持 hashlib 所做的每个哈希 (在撰写本文时,具体来说不是 Blake2 或 SHA3)。

无论如何,我们可能还需要重新构建下载以进行处理 诸如从中提取元数据等,这将使它成为一个有争议的问题。

缺点是无法并行化单个上传 文件,因为每个块都必须按顺序提交。

AWS S3 具有类似的 API(大多数 blob 存储都批量复制了它 或类似的东西),他们称之为分段上传。

分段上传的基本流程是:

  • 启动分段上传以获取上传 ID。

  • 将您的文件分解为块,并单独上传每个块。

  • 上传所有区块后,完成上传。 - 这是会发生任何错误的步骤。

它不直接支持恢复上传,但允许客户端 通过调整每个零件的尺寸来控制失效的“爆炸半径” 他们上传,如果任何部分出现故障,他们只需要重新发送这些部分 特定部分。

这有一个很大的好处,因为它允许在上传文件时并行化, 允许客户端使用多个线程发送 数据。

我们不需要显式步骤 (1),因为我们的会话会隐式 为每个文件启动分段上传。

它确实有其自身的缺点:

  • 客户必须对每个请求做更多的工作才能拥有类似的东西 续传。他们必须将文件分解为多个部分 而不仅仅是发出一个 POST 请求,而只需要处理 如果出现问题,则具有复杂性。

  • 根本不关心复工的客户仍然需要处理 第三个显式步骤,尽管他们可以将文件全部上传为 单件。

  • S3 通过为一次性上传提供另一个 API 来解决此问题,但 我宁愿不要有两个不同的 API 来上传同一个文件。

  • 验证哈希变得更加复杂。AWS 实施哈希 通过对每个部分进行哈希处理来上传分段,那么整体哈希只是一个 这些哈希值的哈希值,而不是内容本身的哈希值。我们需要知道 PyPI 文件本身的实际哈希值,因此我们必须重新构建 文件并读取其内容,并在完全上传后对其进行哈希处理, 虽然我们仍然可以使用哈希的哈希技巧来校验和 上传自身。

  • 请参阅上文,了解这在实践中是否真的是一个缺点,或者 如果只是理论上。

我倾向于 tus 风格的可恢复上传,因为我认为它们更简单 使用和实施,主要的缺点是我们可能会离开 桌面上的一些多线程性能,我认为我是 个人还好吗?

我想 S3 风格的多部分上传的另一个好处是 您不必尝试对并行上传进行任何形式的保护, 因为它们只是受支持。仅此一项就可能抹去大部分服务器端 实施简化。

版权

本文档被置于公共领域或位于 CC0-1.0-通用许可证,以更宽松的许可证为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/65.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,164人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码