news 2026/9/10 12:39:09

用 Swaggatherer 从 Swagger 2.0 规范批量生成 ASP.NET Core 路由匹配基准测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 Swaggatherer 从 Swagger 2.0 规范批量生成 ASP.NET Core 路由匹配基准测试

用 Swaggatherer 从 Swagger 2.0 规范批量生成 ASP.NET Core 路由匹配基准测试

【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore

Swaggatherer(Swagger + Gatherer)是 ASP.NET Core 仓库内、位于 src/Http/Routing/tools/Swaggatherer 目录下的一个命令行小工具,它把一个或多个 Swagger 2.0 JSON 规范文件"收集"成可用于 BenchmarkDotNet 的路由匹配基准测试 C# 源码。路由匹配是 ASP.NET Core 框架最核心、最热门的代码路径之一,而真实的基准测试需要大规模、真实的 API 路由集合才有说服力——Swaggatherer 正是为了解决"从哪弄来大量真实路由模板"这一工程问题而存在。读完本文,你将掌握它的命令行用法、两条代码生成管线(单文件与目录批量模式)、它如何把 Swagger 的 paths 转换成语义等价的路由表与测试请求,以及生成的基准代码如何接入 EndpointRoutingBenchmarkBase 与 DfaMatcher。

Swaggatherer 定位:用真实世界 API 规范喂饱路由基准

ASP.NET Core 的路由匹配性能调优,不能只靠几个手写的样例模板——那无法覆盖真实 API 在路径段数量、参数位置、字面量与参数混杂方式上的多样性。因此该仓库在 src/Http/Routing/perf/Microbenchmarks 中保留了一系列以真实 API 为蓝本的基准:

  • MatcherGithubBenchmark 及其生成的基类 MatcherGithubBenchmarkBase.generated.cs 顶部注明 "Generated from https://github.com/APIs-guru/openapi-directory",内含 243 条 GitHub API 风格端点;
  • MatcherAzureBenchmark 及其 MatcherAzureBenchmarkBase.generated.cs 标注 "Generated from https://github.com/Azure/azure-rest-api-specs"。

这些文件头部都有醒目标注"This code was generated by the Swaggatherer",说明 Swaggatherer 就是这些真实路由基准的产出工具。从代码结构看,其工作流是:先下载/转换一份大型 Swagger 2.0 规范 → 运行 Swaggatherer 生成*.generated.cs→ 手写一个小的 Benchmark 类(如 MatcherGithubBenchmark)继承生成的基类 → 用 BenchmarkDotNet 运行。

命令行用法

Swaggatherer 是依赖Microsoft.Extensions.CommandLineUtils(源码以 Shared 方式引入,见 eng 下的 CommandLineUtils 共享目录)的命令行应用,入口是 Program.cs,参数解析与主流程都封装在 SwaggathererApplication.cs。

README 给出的两种基本用法:

# 从单个 swagger 文件生成基准 dotnet run -- -i swagger.json -o MyGeneratedBenchark.generated.cs # 从目录批量生成(递归查找目录下所有 *.json) dotnet run -- -d /some/directory -o MyGeneratedBenchark.generated.cs

支持的全部命令行选项(对应 SwaggathererApplication.cs 构造函数):

选项别名类型含义
-i--inputMultipleValue输入的 Swagger 2.0 JSON 文件,可重复传入多个
-d(README 中缩写)SingleValue输入目录,工具会递归搜索目录下所有.json文件
-oSingleValue输出文件;省略时默认写入Out.generated.cs
-m--methodNoValue(开关)允许保留仅靠 HTTP 方法区分的多个端点
-h--helpNoValue打印帮助

两个隐含的校验规则值得注意:

  1. -i-d必须且只能提供其一:都未提供或同时提供都会打印帮助并以退出码 1 结束;
  2. -i是 MultipleValue,意味着你可以用一条命令把多个独立规范文件串起来处理;-d目录模式本质上等价于Directory.EnumerateFiles(dir, "*.json", SearchOption.AllDirectories)(SwaggathererApplication.cs),因此会递归收集子目录下的全部 JSON,README 所说的"recursively search for .json files"正源于此。

运行前需要先完成仓库环境激活(构建与运行工具的完整说明可参考 README.md 与根目录的 activate 脚本),并通过命令行参数中的路径传入 swagger JSON。

从 Swagger paths 到路由模板:解析与清洗管线

读取与解析

每个输入文件都用 Newtonsoft.Json 以JObject形式读入(ReadInput);若某个文件 JSON 解析失败,会打印错误并把该文件当作空对象处理,避免一条坏文件拖垮整个批量任务。核心转换逻辑在 ParseEntries:

  1. 读取规范顶层的basePath(若存在),作为所有路径的前缀拼接;
  2. 遍历paths对象下的每个路径;
  3. 对每个路径再遍历其 HTTP 方法键(get/post/put/delete/…),生成basePath + path这样的模板文本;
  4. TemplateParser.Parse将模板文本解析成RouteTemplate,并调用RoutePrecedence.ComputeInbound预先计算入站优先级,封装进 RouteEntry(字段:TemplateMethodPrecedenceRequestUrl)。

三个过滤步骤:复杂段、歧义路由、无法生成请求的路由

真实世界规范往往包含 ASP.NET Core 路由系统表达不了的形态,Swaggatherer 采用"宁缺毋滥"策略,分三步剔除无法安全表达的条目(SwaggathererApplication.cs):

  • 跳过含复杂段的路由:只要模板任一 segment 的IsSimple为假(例如包含约束、catch-all、可选参数等复杂结构),就打印Skipping route with complex segment: <template>并移除。工具注释明确写着"我们目前还不想支持复杂段";
  • 去重歧义路由:真实规范可能自相矛盾。工具以RoutePrecedence.ComputeInbound计算出的优先级数值为键做分组,若两条路由优先级相同、各段字面量逐段OrdinalIgnoreCase相等(且在开启-m时方法名也相同),就视为重复,打印Duplicate route template: <template>并移除。这与 RoutePrecedence.cs 中ComputeInbound所代表的框架级优先级语义保持一致——相同优先级的模板在匹配时会互相竞争,必须保证输入无歧义;
  • 剔除无法生成请求 URL 的路由:见下文参数生成小节,失败时打印Failed to create a request for: <template>

HTTP 方法的取舍与-m开关

在没有-m时,所有 HTTP 方法键都被忽略,Method置为null;加入-m后,端点会记录各自的 HTTP 方法,以便后续生成带HttpMethodMetadata的端点。这里实际隐含了一个建模问题:在 Swagger 中,同一路径下GETPOST是两个 operation,但在 ASP.NET Core 路由里它们对应同一个模板、不同 method 约束,属于"仅靠方法区分"的重复模板。这正是代码注释("Support multiple endpoints that are distinguished only by http method")与去重逻辑中"开启-m才比较 Method"的原因:不开-mGET /gistsPOST /gists会被判定为重复模板而只保留后遇到的;开启后两者都被保留。

生成测试请求:参数值不求真实、只求不碰撞

生成的基准不仅要能匹配,还要保证每个请求确实能命中它对应的端点。问题在于路由模板里的参数段(如{owner}/{repository}/{state}/{keyword})需要一个具体的值来发起请求。工具的生成策略相当朴素(GenerateRequestUrl / GenerateParameterValue):

var text = Guid.NewGuid().ToString(); var length = Math.Min(text.Length, Math.Max(5, part.Name.Length)); return text.Substring(0, length);

即:取一个 GUID 字符串,截取其长度max(5, 参数名长度)的前缀作为参数值。两种取值的用意从代码注释可以读出:

  • 至少 5 个字符:避免值过短造成不同参数名恰好生成相同字面量的碰撞;
  • 随参数名变长:让不同路由生成的请求 URL 尽量"形态不同",降低与其它字面量段撞车的概率。

模板全部由字面量与参数组成时,最终请求 URL 形如/repos/9c6d4/3a。若某路由仅由 0 个段组成,则请求路径固定为/。即便有此策略,仍可能碰上无法构造合法请求的情形,此时该条目被移除并打印日志。可推断:由于参数值对参数名并不真正对应(并不保证值满足约束条件),含正则/范围约束的复杂段在前面的过滤阶段即被剔除,正是为了避免生成永远无法命中的请求。

生成基准代码:模拟属性路由(Controller/Action)的编排

模板渲染逻辑位于 Template.cs,最终产物是一个继承EndpointRoutingBenchmarkBasepartial class GeneratedBenchmark,包含三个方法块:

  • SetupEndpoints():为每条路由生成一行CreateEndpoint("模板", "ControllerN", "ActionM", 方法或 null)
  • SetupRequests():为每条路由构造DefaultHttpContext、注入RequestServices、设置Request.Method(使用HttpMethods.GetCanonicalizedValue)与Request.Path
  • SetupMatcher(MatcherBuilder builder):逐条builder.AddEndpoint(...)后返回builder.Build()

一个很有意思的建模细节是Controller/Action 的编号策略(Template.cs):代码注释说明,在 ASP.NET Core 属性路由中,同一 Controller 内的所有 Action 共享同一模板前缀。因此工具用字典templatesVisited记录每个模板文本被访问的次数——只有遇到新模板才递增 Controller 编号,同一模板下的第 1、2、3 个方法则映射为ControllerN下的Action1/Action2/Action3。这样生成的端点语义上等价于"一个 Controller 挂多个同模板 Action(靠 HTTP 方法或参数区分)",让基准更贴近 MVC 的真实用法。生成的CreateEndpoint(Template.cs)会构造defaults/requiredValues(含area/controller/action/page,其中 controller/action 用上一步编号)、routeName,并在 HTTP 方法存在时附加HttpMethodMetadata——后者正是HttpMethodMatcherPolicy在匹配时用来筛选方法的核心元数据。

最终生成文件的EndpointCount常量等于路由条目总数。以仓库内真实产物为例,MatcherGithubBenchmarkBase.generated.cs 的EndpointCount = 243,SetupEndpoints 逐行形如:

Endpoints[0] = CreateEndpoint("/emojis", "GET"); Endpoints[37] = CreateEndpoint("/legacy/repos/search/{keyword}", "GET");

排序与抽样:保证基准测量有意义

生成阶段还有一个易被忽视但影响基准正确性的关键操作——按优先级排序。在 Sort 中,所有条目先按RoutePrecedence.ComputeInbound的优先级值升序排列,优先级相同再按模板文本序。原因见于 EndpointRoutingBenchmarkBase.SampleRequests:抽样采用等差间隔sample[i] = i * (endpointCount / count),且当endpointCount / count < 2时会直接抛异常提醒抽样过密。因为路由模板已按优先级排序(简单路由在前、复杂路由在后),等间隔抽样就能"均匀覆盖从简单到复杂的路由形态",让少量样本也能代表整体匹配成本。比如 MatcherAzureBenchmark 定义SampleCount = 100,配合注释 "an even distribution of the 'complexity' of the routes" 印证了这一设计意图。

跑通基准:生成、接线与运行

综合 README、代码与 perf 目录 readme,完整使用流程可归纳为:

  1. 获取 Swagger 输入:下载一份大的 Swagger 2.0 JSON。README 的 Resources 一节推荐了三类公开来源:APIs.guru 的 openapi-directory(聚合了大量真实 API 规范)、Azure 的官方azure-rest-api-specs、以及 swagger editor(用于 YAML↔JSON 互转,因为工具只吃 JSON)。注意这些仓库多为 OpenAPI 3.x 或含 YAML,需要先用编辑器/转换工具转成 Swagger 2.0 JSON 形态;

  2. 编译并运行工具

    # 目录模式会递归抓取所有 .json dotnet run -- -d /path/to/specs -o MatcherXxxBenchmarkBase.generated.cs

    若希望保留仅靠 HTTP 方法区分的端点,追加-m;观察控制台输出的Processing N files...计数以及跳过的路由清单;

  3. 接线成可运行基准:参考 MatcherGithubBenchmark 或 MatcherAzureBenchmark 的写法,手写一个继承生成的*BenchmarkBase的子类,在[GlobalSetup]中调用SetupEndpoints()/SetupRequests(),并用BarebonesMatcherBuilder(基线)与CreateDfaMatcherBuilder()(被测的 DFA matcher)各构建一个Matcher,然后分别写出BaselineDfa两个[Benchmark]方法,每个请求匹配后调用Validate校验命中端点与预期一致;

  4. 运行基准:在 Release 编译整个解决方案、激活仓库环境后:

    dotnet run -c Release --framework <tfm> --filter MatcherGithubBenchmark

    不带 filter 时 BenchmarkDotNet 会列出全部基准供交互选择。

生成的Baseline走的是 BarebonesMatcher 这类每端点一个独立匹配器的朴素实现,DfaDfaMatcherBuilder构建的 DFA 状态机,两者在同一批端点上对比,即可量化 DFA 相对朴素匹配的开销与收益——这正是 Swaggatherer 服务的目标场景。

局限性

从代码结构可以明确推断出工具当前刻意保持简单,使用时有几点需要知晓:

  • 只接受 JSON:Swagger/OpenAPI 规范常以 YAML 分发,工具对.json后缀硬编码(目录模式下用*.json过滤、读取时用JsonTextReader),YAML 需先转换;
  • 面向 Swagger 2.0 字段形态:解析逻辑读取的是 Swagger 2.0 的顶层paths+ 每路径下的方法对象,并处理basePath前缀;OpenAPI 3.x 的servers/requestBody等结构并不在解析范围内;
  • 不含复杂段:带约束、catch-all、可选段等模板会被打印日志并跳过,生成结果偏向"纯字面量 + 参数"的简单模板形态;
  • 重复路由只留一条:未开-m时同路径的不同方法会被判为重复而移除;
  • 参数值语义随机:GUID 截断生成的值不求满足任何约束,只保证形态可用,因此不适合用来验证匹配正确性,只服务于性能测量——校验环节(Validate)依然必不可少。

总之,Swaggatherer 是 ASP.NET Core 路由性能工程链条上"喂数据"的一环:把公开 API 规范转换成大规模、有真实分布形态的路由匹配基准,让 DFA matcher 的调优建立在可信的工作负载之上。仓库内 MatcherGithubBenchmarkBase.generated.cs 与 MatcherAzureBenchmarkBase.generated.cs 两个产物就是其能力的直接证据。

【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 12:39:02

STM32F407驱动CS5532高精度称重固件设计

简介&#xff1a;本资源是一套基于STM32F407主控芯片的CS5532高精度模数转换器静态称重系统测试工程&#xff0c;面向嵌入式开发初学者及电子测量方向实践者&#xff0c;解决传感器信号采集、滤波处理与重量值标定等核心问题。压缩包共284个文件&#xff0c;以83个头文件&#…

作者头像 李华
网站建设 2026/9/10 12:37:54

超帧(Hyperframes):把批量变成一等公民,让数据管道吞吐翻倍

前阵子压测一套多路视频采集与特征提取管道&#xff0c;6路1080p实时流&#xff0c;每路30帧每秒&#xff0c;后端接着一串算子&#xff1a;解码、缩放、去噪、特征点提取。单路单帧的处理时间大概在8到12毫秒&#xff0c;看起来每帧都还能在实时边缘内跑完。可一旦把6路并行真…

作者头像 李华
网站建设 2026/9/10 12:37:39

Android应用打包全流程解析与优化实践

1. Android打包流程概述 作为一名Android开发者&#xff0c;每天都要经历数十次的打包过程。但你真的了解这个看似简单的操作背后发生了什么吗&#xff1f;从点击"Build"按钮到最终生成APK文件&#xff0c;Android Studio实际上执行了一系列复杂的操作。这个过程不仅…

作者头像 李华
网站建设 2026/9/10 12:34:55

Java使用Apache POI实现Word模板变量替换技术详解

1. 项目概述&#xff1a;Java操作Word文档的变量替换在Java生态中处理Office文档一直是个高频需求场景。最近接手一个合同管理系统改造项目&#xff0c;需要批量生成数百份条款相似的Word合同。传统复制粘贴方式不仅效率低下&#xff0c;更存在版本混乱风险。经过技术选型&…

作者头像 李华