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 资源
2025 启用嵌入式 README 文件下载

所有者字段

考虑 包清单文件 (.nuspec 的两个字段,<authors><owners>。 打包第三方内容的包作者通常将第三方名称 <authors> 放在字段中。 该<owners>字段旨在表示谁在存储库中发布了包,因此应在打包问题或询问时联系他们。

这在2013 年的博客文章中进行了解释,因此在.nuspec文件中,该<owners>字段被视为已弃用。 如果包的清单包含此元数据,应将其忽略。 请勿在搜索资源包元数据资源 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 的支持。 因此,NuGet API 资源在返回版本给低于 3.6.0 的客户端版本时,必须避免返回使用与语义版本控制 1.0.0 不兼容的语义版本控制 2.0.0 特性的包。

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

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

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

嵌入文件

图标许可证自述 文件可以嵌入包中(建议将其嵌入)。 这些文件需要一个 URL 端点,将其提取后存放于静态文件服务器,或提供一个 URL,能够根据请求从 .nupkg 动态提取文件,这样就可以无需下载整个 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 请求数,因此会减少服务器工作负荷。

下面是一些示例:

网址 PreAuthenticate 也会这样吗?
https://pkgs.contoso.com/nuget/v3/feed/index.json N/A,这是服务索引。
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 的 HTTP 重定向,那么此 URL 就会在 HttpClientHandler 的凭据缓存中使用。 在这种情况下,由于重定向,对服务索引的每个请求都会有额外的延迟。

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

启用嵌入式自述文件下载

记录了用于构造 URL 的新资源 ,该 URL 可用于下载给定包的自述文件。 这将允许客户端(如 VS 中的包管理 UI)显示用户以前未安装的包的嵌入式自述文件。 客户端将构造此 URL 并尝试下载自述文件,并使用对请求的响应来确定 README 是否可用。 这意味着当用户导航 PM UI 时,服务器应期望对构造的终结点发出多个请求。