ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

用 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/aspnetcoreSwaggathererSwagger 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 文件可重复传入多个-dREADME 中缩写SingleValue输入目录工具会递归搜索目录下所有.json文件-oSingleValue输出文件省略时默认写入Out.generated.cs-m--methodNoValue开关允许保留仅靠 HTTP 方法区分的多个端点-h--helpNoValue打印帮助两个隐含的校验规则值得注意-i与-d必须且只能提供其一都未提供或同时提供都会打印帮助并以退出码 1 结束-i是 MultipleValue意味着你可以用一条命令把多个独立规范文件串起来处理-d目录模式本质上等价于Directory.EnumerateFiles(dir, *.json, SearchOption.AllDirectories)SwaggathererApplication.cs因此会递归收集子目录下的全部 JSONREADME 所说的recursively search for .json files正源于此。运行前需要先完成仓库环境激活构建与运行工具的完整说明可参考 README.md 与根目录的 activate 脚本并通过命令行参数中的路径传入 swagger JSON。从 Swagger paths 到路由模板解析与清洗管线读取与解析每个输入文件都用 Newtonsoft.Json 以JObject形式读入ReadInput若某个文件 JSON 解析失败会打印错误并把该文件当作空对象处理避免一条坏文件拖垮整个批量任务。核心转换逻辑在 ParseEntries读取规范顶层的basePath若存在作为所有路径的前缀拼接遍历paths对象下的每个路径对每个路径再遍历其 HTTP 方法键get/post/put/delete/…生成basePath path这样的模板文本用TemplateParser.Parse将模板文本解析成RouteTemplate并调用RoutePrecedence.ComputeInbound预先计算入站优先级封装进 RouteEntry字段Template、Method、Precedence、RequestUrl。三个过滤步骤复杂段、歧义路由、无法生成请求的路由真实世界规范往往包含 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 中同一路径下GET与POST是两个 operation但在 ASP.NET Core 路由里它们对应同一个模板、不同 method 约束属于仅靠方法区分的重复模板。这正是代码注释Support multiple endpoints that are distinguished only by http method与去重逻辑中开启-m才比较 Method的原因不开-mGET /gists和POST /gists会被判定为重复模板而只保留后遇到的开启后两者都被保留。生成测试请求参数值不求真实、只求不碰撞生成的基准不仅要能匹配还要保证每个请求确实能命中它对应的端点。问题在于路由模板里的参数段如{owner}/{repository}/{state}/{keyword}需要一个具体的值来发起请求。工具的生成策略相当朴素GenerateRequestUrl / GenerateParameterValuevar 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最终产物是一个继承EndpointRoutingBenchmarkBase的partial class GeneratedBenchmark包含三个方法块SetupEndpoints()为每条路由生成一行CreateEndpoint(模板, ControllerN, ActionM, 方法或 null)SetupRequests()为每条路由构造DefaultHttpContext、注入RequestServices、设置Request.Method使用HttpMethods.GetCanonicalizedValue与Request.PathSetupMatcher(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 的真实用法。生成的CreateEndpointTemplate.cs会构造defaults/requiredValues含area/controller/action/page其中 controller/action 用上一步编号、routeName并在 HTTP 方法存在时附加HttpMethodMetadata——后者正是HttpMethodMatcherPolicy在匹配时用来筛选方法的核心元数据。最终生成文件的EndpointCount常量等于路由条目总数。以仓库内真实产物为例MatcherGithubBenchmarkBase.generated.cs 的EndpointCount 243SetupEndpoints 逐行形如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完整使用流程可归纳为获取 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 形态编译并运行工具# 目录模式会递归抓取所有 .json dotnet run -- -d /path/to/specs -o MatcherXxxBenchmarkBase.generated.cs若希望保留仅靠 HTTP 方法区分的端点追加-m观察控制台输出的Processing N files...计数以及跳过的路由清单接线成可运行基准参考 MatcherGithubBenchmark 或 MatcherAzureBenchmark 的写法手写一个继承生成的*BenchmarkBase的子类在[GlobalSetup]中调用SetupEndpoints()/SetupRequests()并用BarebonesMatcherBuilder基线与CreateDfaMatcherBuilder()被测的 DFA matcher各构建一个Matcher然后分别写出Baseline与Dfa两个[Benchmark]方法每个请求匹配后调用Validate校验命中端点与预期一致运行基准在 Release 编译整个解决方案、激活仓库环境后dotnet run -c Release --framework tfm --filter MatcherGithubBenchmark不带 filter 时 BenchmarkDotNet 会列出全部基准供交互选择。生成的Baseline走的是 BarebonesMatcher 这类每端点一个独立匹配器的朴素实现Dfa走DfaMatcherBuilder构建的 DFA 状态机两者在同一批端点上对比即可量化 DFA 相对朴素匹配的开销与收益——这正是 Swaggatherer 服务的目标场景。局限性从代码结构可以明确推断出工具当前刻意保持简单使用时有几点需要知晓只接受 JSONSwagger/OpenAPI 规范常以 YAML 分发工具对.json后缀硬编码目录模式下用*.json过滤、读取时用JsonTextReaderYAML 需先转换面向 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),仅供参考
返回列表