Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 381 – PyPI 的镜像基础设施

作者:
Tarek Ziadé <tarek at ziade.org>, Martin von Löwis <martin at v.loewis.de>
状态:
已撤回
类型:
标准跟踪
主题:
打包
创建:
2009-03-21
历史记录:


目录

摘要

本 PEP 描述了 PyPI 的镜像基础设施。

PEP 撤回

主要的 PyPI 网络服务已于 2013 年 5 月迁移到 Fastly 缓存 CDN 后面:https://mail.python.org/pipermail/distutils-sig/2013-May/020848.html

随后,这种安排被正式化为对 PSF 的实物赞助,PSF 也承担了在该赞助安排终止的情况下进行风险管理的任务。

以前直接在 PyPI 上提供的下载统计数据现在通过 Google Big Query 间接发布:https://packaging.pythonlang.cn/guides/analyzing-pypi-package-downloads/

因此,本 PEP 中描述的镜像提案不再需要,并且已被标记为已撤回。

基本原理

PyPI 主机超过 6000 个项目,并且每天都被人们用来构建应用程序。尤其是像 easy_installzc.buildout 这样的系统,对 PyPI 进行了密集的使用。

对于那些密集使用 PyPI 的人来说,它可能成为单点故障。人们已经开始建立一些镜像,包括私有镜像和公共镜像。这些镜像是主动镜像,这意味着它们会浏览 PyPI 以进行同步。

为了使系统更加可靠,本 PEP 描述了

  • 在 PyPI 上列出和注册镜像
  • 公共镜像应该维护的页面。这些页面将被 PyPI 使用,以获取点击次数和最后修改日期。
  • 镜像如何与 PyPI 同步
  • 客户端如何实现故障转移机制

镜像列表和注册

想要镜像 PyPI 的用户可以在 catalog-SIG 上提出建议。当在邮件列表中提出镜像建议时,它会在经过检查以确保符合镜像规则后,被手动添加到 PyPI 应用程序的镜像列表中。

镜像列表以以下形式的主机名列表提供

X.pypi.python.org

X 的值依次为 a、b、c、…、aa、ab、…,a.pypi.python.org 是主服务器;镜像从 b 开始。CNAME 记录 last.pypi.python.org 指向最后一个主机名。镜像操作员应该使用静态地址,并在 distutils-sig 上提前报告计划的地址更改。

新镜像还会出现在 http://pypi.python.org/mirrors 上,这是一个人类可读的页面,它提供了镜像列表。该页面还解释了如何注册新的镜像。

统计页面

PyPI 在 /stats 上提供下载统计数据。该页面每天由 PyPI 计算,方法是读取所有镜像的本地统计数据并将其汇总。

统计数据以每天或每月文件的形式呈现,位于 /stats/days/stats/months 下。每个文件都是一个 bzip2 文件,具有以下格式

  • YYYY-MM-DD.bz2 用于每日文件
  • YYYY-MM.bz2 用于每月文件

示例

  • /stats/days/2008-11-06.bz2
  • /stats/days/2008-11-07.bz2
  • /stats/days/2008-11-08.bz2
  • /stats/months/2008-11.bz2
  • /stats/months/2008-10.bz2

镜像真实性

使用分布式镜像系统,客户端可能想要验证镜像副本是否真实。需要考虑多种威胁

  1. 中央索引可能会被破坏
  2. 中央索引被认为是可信的,但镜像可能被篡改。
  3. 中央索引和最终用户之间或镜像和最终用户之间的中间人可能会篡改数据报。

本规范只处理第二种威胁。有一些规定可以检测中间人攻击。为了检测第一次攻击,包作者需要使用 PGP 密钥对他们的包进行签名,以便用户验证包来自他们信任的作者。

中央索引在 URL /serverkey 上提供一个 DSA 密钥,采用 PEM 格式,由“openssl dsa -pubout”生成(即 RFC 3280 SubjectPublicKeyInfo,算法为 1.3.14.3.2.12)。此 URL 不得镜像,客户端必须直接从 PyPI 获取官方 serverkey,或者使用 PyPI 客户端软件附带的副本。镜像仍然应该下载密钥,以检测密钥切换。

对于每个包,在 /serversig/<package> 上提供一个镜像签名。这是平行 URL /simple/<package> 的 DSA 签名,采用 DER 格式,使用 SHA-1 与 DSA(即,作为 RFC 3279 Dsa-Sig-Value,由算法 1.2.840.10040.4.3 创建)。

使用镜像的客户端需要执行以下步骤来验证包

  1. 下载 /simple 页面,并计算其 SHA-1 哈希
  2. 计算该哈希的 DSA 签名
  3. 下载相应的 /serversig,并将其(逐字节)与步骤 2 中计算的值进行比较。
  4. 计算并验证(针对 /simple 页面)从镜像下载的所有文件的 MD-5 哈希。

验证算法的实现可在 https://svn.python.org/packages/trunk/pypi/tools/verify.py 中找到。

从中央索引下载时不需要验证,应该避免验证以减少计算开销。

大约每年一次,密钥将被替换为一个新的密钥。镜像将不得不重新获取所有 /serversig 页面。使用镜像的客户端需要找到新服务器密钥的可信副本。获得其中一种方法是从 https://pypi.python.org/serverkey 下载它。为了检测中间人攻击,客户端需要验证 SSL 服务器证书,该证书将由 CACert 颁发机构签名。

镜像需要提供的特殊页面

镜像是 PyPI 的子集副本,因此它通过复制 PyPI 来提供相同的结构。

  • simple:包索引的 REST 版本
  • packages:包,按 Python 版本和字母存储
  • serversig:simple 页面的签名

它还需要提供两个特定元素

  • last-modified
  • local-stats

最后修改日期

CPAN 使用一个新鲜度日期系统,其中镜像的最后同步日期是可用的。

对于 PyPI,每个镜像都需要维护一个 URL,其中包含代表镜像维护的最后同步日期的简单文本内容。

日期以 GMT 时间提供,使用 ISO 8601 格式 [2]。每个镜像将负责维护其最后修改日期。

此页面必须位于:/last-modified,并且必须是一个 text/plain 页面。

本地统计

每个镜像负责统计通过它完成的所有下载次数。PyPI 使用此数据来汇总所有下载次数,以便能够显示总计。

这些统计数据以类似 CSV 的形式呈现,第一行包含标题。它需要遵守 PEP 305。基本上,它应该可以被 Python 的 csv 模块读取。

此文件中的字段是

  • package:包的 distutils ID。
  • filename:已下载的文件名。
  • useragent:下载包的客户端的用户代理。
  • count:下载次数。

内容将如下所示

# package,filename,useragent,count
zc.buildout,zc.buildout-1.6.0.tgz,MyAgent,142
...

计数从镜像启动之日起开始,每天一个文件,使用 bzip2 格式进行压缩。每个文件以日期命名。例如,2008-11-06.bz2 是 2008 年 11 月 6 日的文件。

然后它们被提供在一个名为 days 的文件夹中。例如

  • /local-stats/days/2008-11-06.bz2
  • /local-stats/days/2008-11-07.bz2
  • /local-stats/days/2008-11-08.bz2

此页面必须位于 /local-stats 上。

镜像如何与 PyPI 同步

Martin v. Loewis 和 Jim Fulton 基于 easy_install 的工作方式,描述并实现了名为 Simple Index 的镜像协议。本节对其进行了总结,并提供了一些相关链接,以及关于 User-Agent 的一小部分内容。

镜像协议

镜像必须减少中央服务器和镜像之间传输的数据量。为了实现这一点,它们必须使用 changelog() PyPI XML-RPC 调用,并且只重新获取自上次获取以来已更改的包。对于每个包 P,它们必须复制文档 /simple/P/ 和 /serversig/P。如果包在中央服务器上被删除,则必须删除该包和所有相关文件。为了检测包文件的修改,它们可以使用文件 ETag 进行缓存,并且可以使用 If-none-match 头部请求跳过它。

每个镜像工具必须使用描述性的 User-agent 头部标识自己。

pep381client 包 [1] 提供了一个应用程序,该应用程序遵循此协议以浏览 PyPI。

用户代理请求头

为了能够区分客户端在 PyPI 上执行的操作,所有镜像软件都应该提供特定的用户代理名称。

这也适用于所有类似于

  • zc.buildout [3] 的客户端。
  • setuptools [4]
  • pip [5]

XXX PyPI 上的用户代理注册机制?

客户端如何使用 PyPI 及其镜像

浏览 PyPI 的客户端应该能够使用备用镜像,方法是使用 last.pypi.python.org 获取镜像列表。

代码示例

>>> import socket
>>> socket.gethostbyname_ex('last.pypi.python.org')[0]
'h.pypi.python.org'

到目前为止可以使用此机制的客户端包括

  • setuptools
  • zc.buildout(通过 setuptools)
  • pip

故障转移机制

浏览 PyPI 的客户端应该能够在 PyPI 或使用的镜像无法响应时使用故障转移机制。

客户端需要决定使用哪个镜像,例如通过查看其地理位置和响应速度。

本 PEP 没有描述这种故障转移机制的工作方式,但强烈建议客户端尝试使用最近的镜像。

到目前为止可以使用此机制的客户端包括

  • setuptools
  • zc.buildout(通过 setuptools)
  • pip

额外的包索引

很明显,某些软件包不会上传到 PyPI,可能是因为它们是私有的,也可能是因为项目维护者运行自己的服务器,用户可以从那里获取项目软件包。但是,强烈建议公共软件包索引遵循 PyPI 和 Distutils 协议。

换句话说,registerupload 命令应该与任何软件包索引服务器兼容。

与 PyPI 和 Distutils 兼容的软件

  • PloneSoftwareCenter [6] 用于运行 plone.org 产品部分。
  • EggBasket [7].

额外的软件包索引不是 PyPI 的镜像,但本身可以包含一些镜像。

合并多个索引

当客户端需要从多个不同的索引获取一些软件包时,它应该能够将每个索引用作潜在的软件包来源。不同的索引应定义为一个排序列表,以便客户端搜索软件包。

每个独立的索引当然都可以提供其镜像列表。

XXX 定义如何获取任意索引镜像的主机名。

这允许在客户端级别进行所有组合,从而为具有所有隐私级别的可靠打包系统。

客户端负责处理合并。

参考

致谢

Georg Brandl。


来源: https://github.com/python/peps/blob/main/peps/pep-0381.rst

最后修改时间: 2023-09-09 17:39:29 GMT