NuGet 服务器实现指南

某些情况下,可能需实现自己的 NuGet 包源。 尽管存在众多现有实现允许你以各种方式托管自己的源,但已记录官方 NuGet 客户端软件和包源之间的协议,以便你从头开始构建自己的源实现。

此协议会随着时间的推移而演变,而本指南适用于那些希望或已实现 NuGet 包服务器的用户。

自 2015 年 NuGet V3 协议的初始版本发布以来,NuGet 已演变为可为开发人员提供更丰富的体验,而这需要包存储库执行额外工作来为其包使用者提供附加价值,而不仅仅是从托管包中提取元数据并以各种形式返回该元数据。 例如,搜索和包元数据终结点不仅包含 nupkg 的 nuspec 文件中找到的元数据。

请注意,本指南重点介绍 NuGet V3 协议,因为 V2 协议本质上并未进行记录,且自 2015 年以来,NuGet 客户端和服务器通信的推荐协议便是 V3 协议。 有关详细信息,请阅读有关协议版本控制的信息。

年表

为帮助现有 NuGet 存储库的作者了解 NuGet 的最新功能,以下提供本文档其余部分所提及相关功能的年表。

年份 功能
2013 一篇介绍如何在 nuget.org 上管理包所有者的博客文章已阐明该网站上显示的所有者为有权上传新版本的帐户,因此会忽略包中的owners元数据
2017 verified 添加到 SearchQueryService 响应。
语义版本控制 2.0.0 支持
2018 嵌入式许可证
2019 嵌入式图标
RegistrationBaseUrl(包元数据资源)中提供包弃用
2020 RegistrationsBaseUrl(包元数据资源)中提供包漏洞信息
SearchQueryService 请求添加 packageTypes 查询参数
2021 嵌入式自述文件
2023 对已进行身份验证的请求进行预验证
VulnerabilityInfo 资源

所有者字段

考虑包清单文件 (.nuspec) 字段、<authors><owners> 中的两项。 打包第三方内容的包作者通常会将第三方名称放在 <authors> 字段中。 <owners> 字段旨在表示在存储库中发布包的人员,因此在出现打包问题或疑问时应联系此人员。

这已在 2013 年的某一博客文章中进行解释,因此 <owners> 字段在 .nuspec 文件中被视为已弃用。 如果包的清单包含此元数据,则应将其忽略。 请勿在搜索资源包元数据资源 JSON 响应中返回 owners 属性中 .nuspec 文件的 <owners> 字段的值。

如果存储库具有按包分配的权限,则建议报告有权在 owner 元数据中发布新版本的帐户,以便搜索和打包元数据资源 JSON 响应。

verified 搜索响应字段

将新字段 verified 设为 true 时,Visual Studio 的包管理器 UI 会在搜索服务结果中的包旁显示蓝色复选标记。

NuGet.org 会将此与包前缀数据(服务器端数据,而不是 NuGet API 的一部分)搭配使用;因此,仅当拥有包的帐户上传包时,才会向客户显示此复选标记。 例如,仅当包属于 nuget.org 上的 Microsoft 帐户时,才会验证带前缀 microsoft.* 的包。在实施保留的前缀之前上传包 ID 以 microsoft. 开头的包的任何人,则均没有此已验证的复选标记。 NuGet.org 还允许将前缀设为非独占,以便所有人均可在 Contoso.ToolWithPlugins.Community.* 中上传包,但不会获得已验证的复选标记。

语义版本控制 2.0.0 支持

NuGet 支持 System.Version 和语义版本之间的混合版本,但已在 2017 年添加对语义版本 2.0.0 的支持。 因此,将版本返回到低于 3.6.0 的客户端版本的 NuGet API 资源不得返回使用语义 2.0.0 功能(与语义版本控制 1.0.0 不兼容)的包。

这两个版本之间的最重要区别在于预发行标签和元数据字符串。 语义版本控制 1.0.0 规范为仅允许用作预发行标签一部分的字符提供了 [0-9A-Za-z-] 以作为正则表达式示例字符串,且不支持元数据字符串。 语义版本控制 2.0.0 规范允许用 . 字符分隔预发行标识符(并禁止数字标识符包含前导零),且额外允许在 + 之后添加生成元数据。

包元数据资源 (RegistrationsBaseUrl)中,低于 3.6.0 的资源版本只能返回符合 .NET 的 System.Version 或语义版本控制 1.0.0 的包。 这意味着其版本仅符合语义版本控制 2.0.0 的包对这些客户端版本不可见。

同样,搜索查询服务 (SearchQueryService)自动完成服务 (SearchAutocompleteService) 已新增 &semVerLevel={version} 查询参数。 缺少 semVerLevel 时,会将值假设为 1.0.0。 与包元数据资源一样,当 semVerLevel 值低于 2.0.0 时,不得返回其版本仅与语义版本控制 2.0.0 兼容的包。

嵌入式文件

图标许可证自述文件可(且建议)嵌入包中。 这些文件需要一个 URL 终结点(提取并放在静态文件服务器上,或是会动态提取所请求 .nupkg 中文件的 URL),以便无需下载整个 nupkg 即可查看这些文件。 如果包存储库提供包浏览和查看包详细信息,则可使用 URL 向客户显示网站上的嵌入式内容。

最后,包元数据资源搜索资源必须包含 JSON 响应的 iconUrllicenseUrl 和/或 readmeUrl 属性中所托管的 URL。 包(.nupkg 文件)不得进行修改,因为客户端功能(锁定文件和已签名包)会在包已被篡改时检测修改。

请注意,许可证可既可是 SPDX 表达式,也可是嵌入式文件(但不能同时为两者)。 在搜索和包元数据结果中表示使用许可证表达式的包时,可将 licenseUrl 设为许可证表达式、对 URL 进行编码并将其追加到 https://licenses.nuget.org/ 的末尾。 例如 https://licenses.nuget.org/Apache-2.0。 NuGet.org 服务器团队在 licenses.nuget.org 上提供了其他文档

已知漏洞和弃用数据

包元数据资源 (RegistrationsBaseUrl)

包元数据资源可包含弃用漏洞信息。 这样,便可向在 Visual Studio 的包管理器用户界面上浏览包或在其他 IDE 中浏览包的客户发送有关重大安全或维护问题的通知。

如果包存储库是其他存储库中的“向上溯源”包,则为在自己的源中镜像包,我们建议定期检查原始源是否存在弃用或漏洞数据),并在自己的存储库中镜像该元数据。 如果包存储库专门从 nuget.org 进行向上溯源,则通过保留上次检查状态(“游标”)时的状态,可使用Catalog 资源高效检查当前所镜像的包是否有包更新,而无需从上游源下载大量包元数据 JSON 文件。 有指南介绍了如何使用目录资源并提供了示例代码,以便帮助你入门。

已知漏洞数据库 (VulnerabilityInfo)

为在包还原期间提供高性能漏洞扫描,NuGet 会从VulnerabilityInfo 资源下载已知漏洞的完整列表。 Nuget.org 将为 GitHub 公告数据库中所有 GitHub 已审查的公告提供漏洞数据,其中包括未托管在 nuget.org 上的包。

如果包存储库托管有第一方包,且你想使用自己的源向客户提供漏洞信息,但尚无任何已披露的包漏洞,则应提供包含一个或多个漏洞页面漏洞索引(其内容为空 JSON 数组 ([]))。

如果包存储库旨在由应用用作默认存储库(而不是 nuget.org),则可使用 nuget.org 的漏洞数据。 其中一个选项是:在服务索引中使用 nuget.org 的漏洞索引 URL。 另一选项则是:定期检查 nuget.org 的 VulnerabilityInfo 索引,并下载所有已更改的页面以在本地进行镜像。

packageTypes 搜索查询

.NET CLI 允许使用 dotnet tool search 命令搜索 .NET 工具包。 此操作是通过将 &packageTypes={value} 查询参数添加到搜索查询资源来实现的,而该参数会从包的 .nuspec 文件中的 <packageTypes> 字段读取值。

已经过验证的源的 URL 结构

NuGet API 概述中所述,所有 NuGet 服务器通信的起始 URL 均为服务索引。 此文档包含 NuGet 客户端将查询的所有其他资源的 URL。 从 NuGet 6.7(Visual Studio MSBuild 17.7 和 .NET SDK 7.0.400)起,NuGet 将使用 .NET 的 HttpClientHandler.PreAuthenticate,它只在后续 URL 位于先前已经过验证的 URL 所在的同一虚拟目录或子目录中时,才会避免匿名 HTTP 请求。 此特性可大幅减少发送到服务器的未经验证的 HTTP 请求数,从而降低服务器工作负荷。

以下是一些示例:

URL PreAuthenticate 也会这样吗?
https://pkgs.contoso.com/nuget/v3/feed/index.json 不适用,这是服务索引。
https://pkgs.contoso.com/nuget/v3/search 否,不在与服务索引相同的目录或子目录中。
https://search.pkgs.contoso.com/nuget/v3/feed/ 否,不在与服务索引相同的主机名上。
https://pkgs.contoso.com/nuget/v3/feed/search 是,在与服务索引相同的目录中。
https://pkgs.contoso.com/nuget/v3/registration/ 否,不在服务索引的子目录中。
https://pkgs.contoso.com/nuget/v3/feed/registration/ 是,在服务索引的子目录中。
https://pkgs.contoso.com/nuget/v3/{guid}/registration/ 请参阅以下内容

在上一示例中,服务器可能具有规范(在此示例中为 guid)名称,且有一个或多个别名。 如果在非规范 URL 上对服务索引请求进行身份验证(在 feed 示例中为“友好”名称),则不会;因为针对规范 URL 下资源的所有请求均不匹配针对 PreAuthenticateHttpClientHandler 规则。 但是,如果非规范 URL 是指向规范 URL https://pkgs.contoso.com/nuget/v3/{guid}/index.json 的 HTTP 重定向,则此 URL 将在 HttpClientHandler 的凭据缓存中使用。 在此情况下,由于重定向,针对服务索引的每个请求均会出现额外延迟。

虽然 NuGet 的 V3 API 旨在适应静态文件服务器,但搜索资源却是例外,因为它始终需要动态 Web 服务来处理请求。 如果要在不同服务器上托管搜索或任意其他 NuGet API 资源以便从 HttpClientHandlerPreAuthenticate中受益,则需使用反向代理来确保服务索引中面向客户的所有 URL 均满足“相同目录或子目录”规则。