编程语言编译器云原生语言运行时【免费下载链接】wingA programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡项目地址https://gitcode.com/gh_mirrors/wi/wing点击查看免费下载本篇技术指南以 Wingwinglang官方测试套件中的快照文件 put.test.w_compile_tf-aws.md 为解剖对象完整解读一个带路径参数的 PUT 路由从 Wing 源码到 AWS 基础设施代码Terraform JSON的编译全过程。你将理解cloud.Api的抽象设计、tf-aws平台如何把一次api.put()调用展开为 API Gateway、Lambda、IAM、S3 等十余个底层资源并能用快照反推 Wing 的部署与权限模型最终在自己的 Wing 工程里复现并验证同样的编译产物。快照的来龙去脉hangar 编译快照与 put.test.w本次分析的对象是tools/hangar/__snapshots__/test_corpus/sdk_tests/api/put.test.w_compile_tf-aws.md它由 hangar 测试框架自动生成。从文件名的三段式命名即可读出它的身份被测源文件为put.test.w操作阶段为compile编译目标平台为tf-aws。hangar 的生成逻辑位于 generated_test_targets.tscompileTest函数会对每个.test.w入口文件执行wing compile -t tf-aws将产物目录target/name.tfaws下的main.tf.json与.wing目录中的 preflight/inflight JavaScript 汇总成一个 Markdown 快照写入__snapshots__目录快照目录定义见 paths.ts。test_corpus/sdk_tests/api前缀表明它属于 SDK 测试语料库中的api主题目录源文件位于 tests/sdk_tests/api。值得注意的细节快照中的ASSET_KEY、ASSET_SOURCE等占位符并非编译产物本来面目而是 utils.ts 中sanitize_json_paths的清理结果——它把形如asset.xxx.zip的密钥与本地绝对源码路径统一替换为占位符避免快照因本地路径差异而不稳定同时该函数会删除terraform字段与 CDKTF 版本号保证快照跨环境可复现。被测 Wing 源码put.test.w 的 PUT 路由定义快照的主体是编译产物但理解编译产物必须先回到源头。完整源码见 tests/sdk_tests/api/put.test.w其核心逻辑如下bring cloud; bring http; bring util; let api new cloud.Api(); let body Json {cat: Tion}; let user guy; let _id 12345; api.put(/path/:id/nn/:user, inflight (req: cloud.ApiRequest): cloud.ApiResponse { let path /path/{_id}/nn/{user}; assert(req.method cloud.HttpMethod.PUT); assert(req.vars?.get(id) _id); assert(req.vars?.get(user) user); assert(req.path path); assert(req.body Json.stringify(body)); assert(req.headers?.get(content-type) application/json); return cloud.ApiResponse { status: 200, headers: {content-type application/json; charsetutf-8}, body: req.vars?.get(id) }; }); test http.put and http.fetch can preform a call to an api { let url {api.url}/path/{_id}/nn/{user}; let response: http.Response http.put(url, headers: { content-type application/json }, body: Json.stringify(body)); let fetchResponse: http.Response http.put(url, method: http.HttpMethod.PUT, headers: { content-type application/json }, body: Json.stringify(body)); assert(response.headers.get(content-type) application/json; charsetutf-8); assert(response.body _id); assert(response.status 200); assert(response.url url); assert(fetchResponse.headers.get(content-type) application/json; charsetutf-8); assert(fetchResponse.body _id); assert(fetchResponse.status 200); assert(fetchResponse.url url); }这段测试代码揭示了cloud.Api路由 API 的几个关键设计路径参数语法Wing 使用:id、:user声明路径参数。在云抽象基类 api.ts 中renderOpenApiPath会把/:name形式的路径转换为 OpenAPI 规范的/{name}形式快照中的 OpenAPI body 正是/{id}、/{user}形态。路径合法性由_validatePath校验cloud/api.ts参数名只允许字母、数字、下划线与连字符。inflight 处理器api.put()接收一个inflight闭包签名是(req: cloud.ApiRequest): cloud.ApiResponse。ApiRequest包含method、path、query、vars路径参数、body、headers等字段ApiResponse支持status、body、headers定义见 cloud/api.ts。测试即飞行前验证Wing 的test块在模拟器与真实云上均可执行通过http.put和http.fetch两种途径发起真实 HTTP 请求断言状态码、响应体、响应头与 URL从而验证 PUT 路由的端到端行为。编译产物全景main.tf.json 完整快照对put.test.w执行wing compile -t tf-aws后hangar 将main.tf.json原样收录进快照。下面完整呈现这份产物即关联文档的主体内容{ //: { metadata: { backend: local, stackName: root }, outputs: { root: { Default: { Default: { Api: { Endpoint: { Url: Api_Endpoint_Url_473FEE9F } } } } } } }, data: { aws_caller_identity: { account: { //: { metadata: { path: root/Default/account, uniqueId: account } } } }, aws_region: { Region: { //: { metadata: { path: root/Default/Region, uniqueId: Region } } } } }, output: { Api_Endpoint_Url_473FEE9F: { value: https://${aws_api_gateway_rest_api.Api_api_91C07D84.id}.execute-api.${data.aws_region.Region.name}.amazonaws.com/${aws_api_gateway_stage.Api_api_stage_E0FA39D6.stage_name} } }, provider: { aws: [ {} ] }, resource: { aws_api_gateway_deployment: { Api_api_deployment_7FB64CC4: { //: { metadata: { path: root/Default/Default/Api/api/deployment, uniqueId: Api_api_deployment_7FB64CC4 } }, lifecycle: { create_before_destroy: true }, rest_api_id: ${aws_api_gateway_rest_api.Api_api_91C07D84.id}, triggers: { redeployment: ${sha256(aws_api_gateway_rest_api.Api_api_91C07D84.body)} } } }, aws_api_gateway_rest_api: { Api_api_91C07D84: { //: { metadata: { path: root/Default/Default/Api/api/api, uniqueId: Api_api_91C07D84 } }, body: {\paths\:{\/path/{id}/nn/{user}\:{\put\:{\operationId\:\put-path/:id/nn/:user\,\responses\:{\200\:{\description\:\200 response\,\content\:{}}},\parameters\:[{\name\:\id\,\in\:\path\,\required\:true,\schema\:{\type\:\string\}},{\name\:\user\,\in\:\path\,\required\:true,\schema\:{\type\:\string\}}],\x-amazon-apigateway-integration\:{\uri\:\arn:aws:apigateway:${data.aws_region.Region.name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${data.aws_region.Region.name}:${data.aws_caller_identity.account.account_id}:function:put_path_-id_nn_-user0-c8982eb3/invocations\,\type\:\aws_proxy\,\httpMethod\:\POST\,\responses\:{\default\:{\statusCode\:\200\}},\passthroughBehavior\:\when_no_match\,\contentHandling\:\CONVERT_TO_TEXT\}}},\/{proxy}\:{\x-amazon-apigateway-any-method\:{\produces\:[\application/json\],\x-amazon-apigateway-integration\:{\type\:\mock\,\requestTemplates\:{\application/json\:\\\n {\\\statusCode\\\: 404}\\n \},\passthroughBehavior\:\never\,\responses\:{\404\:{\statusCode\:\404\,\responseParameters\:{\method.response.header.Content-Type\:\application/json\},\responseTemplates\:{\application/json\:\{\\\statusCode\\\: 404, \\\message\\\: \\\Error: Resource not found\\\}\}},\default\:{\statusCode\:\404\,\responseParameters\:{\method.response.header.Content-Type\:\application/json\},\responseTemplates\:{\application/json\:\{\\\statusCode\\\: 404, \\\message\\\: \\\Error: Resource not found\\\}\}}}},\responses\:{\404\:{\description\:\404 response\,\headers\:{\Content-Type\:{\type\:\string\}}}}}}},\openapi\:\3.0.3\}, lifecycle: { create_before_destroy: true }, name: api-c8f613f0 } }, aws_api_gateway_stage: { Api_api_stage_E0FA39D6: { //: { metadata: { path: root/Default/Default/Api/api/stage, uniqueId: Api_api_stage_E0FA39D6 } }, deployment_id: ${aws_api_gateway_deployment.Api_api_deployment_7FB64CC4.id}, rest_api_id: ${aws_api_gateway_rest_api.Api_api_91C07D84.id}, stage_name: prod } }, aws_cloudwatch_log_group: { Api_put_path_id_nn_user0_CloudwatchLogGroup_6FDE8C46: { //: { metadata: { path: root/Default/Default/Api/put_path_:id_nn_:user0/CloudwatchLogGroup, uniqueId: Api_put_path_id_nn_user0_CloudwatchLogGroup_6FDE8C46 } }, name: /aws/lambda/put_path_-id_nn_-user0-c8982eb3, retention_in_days: 30 } }, aws_iam_role: { Api_put_path_id_nn_user0_IamRole_1C9DE02F: { //: { metadata: { path: root/Default/Default/Api/put_path_:id_nn_:user0/IamRole, uniqueId: Api_put_path_id_nn_user0_IamRole_1C9DE02F } }, assume_role_policy: {\Version\:\2012-10-17\,\Statement\:[{\Action\:\sts:AssumeRole\,\Principal\:{\Service\:\lambda.amazonaws.com\},\Effect\:\Allow\}]} } }, aws_iam_role_policy: { Api_put_path_id_nn_user0_IamRolePolicy_0EB967A3: { //: { metadata: { path: root/Default/Default/Api/put_path_:id_nn_:user0/IamRolePolicy, uniqueId: Api_put_path_id_nn_user0_IamRolePolicy_0EB967A3 } }, policy: {\Version\:\2012-10-17\,\Statement\:[{\Effect\:\Allow\,\Action\:\none:null\,\Resource\:\*\}]}, role: ${aws_iam_role.Api_put_path_id_nn_user0_IamRole_1C9DE02F.name} } }, aws_iam_role_policy_attachment: { Api_put_path_id_nn_user0_IamRolePolicyAttachment_045FFFB7: { //: { metadata: { path: root/Default/Default/Api/put_path_:id_nn_:user0/IamRolePolicyAttachment, uniqueId: Api_put_path_id_nn_user0_IamRolePolicyAttachment_045FFFB7 } }, policy_arn: arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole, role: ${aws_iam_role.Api_put_path_id_nn_user0_IamRole_1C9DE02F.name} } }, aws_lambda_function: { Api_put_path_id_nn_user0_A9826CF0: { //: { metadata: { path: root/Default/Default/Api/put_path_:id_nn_:user0/Default, uniqueId: Api_put_path_id_nn_user0_A9826CF0 } }, architectures: [ arm64 ], environment: { variables: { NODE_OPTIONS: --enable-source-maps, WING_FUNCTION_NAME: put_path_-id_nn_-user0-c8982eb3, WING_TARGET: tf-aws } }, function_name: put_path_-id_nn_-user0-c8982eb3, handler: index.handler, logging_config: { log_format: JSON }, memory_size: 1024, publish: true, role: ${aws_iam_role.Api_put_path_id_nn_user0_IamRole_1C9DE02F.arn}, runtime: nodejs20.x, s3_bucket: ${aws_s3_bucket.Code.bucket}, s3_key: ${aws_s3_object.Api_put_path_id_nn_user0_S3Object_5E224671.key}, timeout: 60, vpc_config: { security_group_ids: [], subnet_ids: [] } } }, aws_lambda_permission: { Api_api_permission-PUT-526849ff_9087005A: { //: { metadata: { path: root/Default/Default/Api/api/permission-PUT-526849ff, uniqueId: Api_api_permission-PUT-526849ff_9087005A } }, action: lambda:InvokeFunction, function_name: ${aws_lambda_function.Api_put_path_id_nn_user0_A9826CF0.function_name}, principal: apigateway.amazonaws.com, source_arn: ${aws_api_gateway_rest_api.Api_api_91C07D84.execution_arn}/*/PUT/path/{id}/nn/{user}, statement_id: AllowExecutionFromAPIGateway-PUT-526849ff } }, aws_s3_bucket: { Code: { //: { metadata: { path: root/Default/Code, uniqueId: Code } }, bucket_prefix: code-c84a50b1- } }, aws_s3_object: { Api_put_path_id_nn_user0_S3Object_5E224671: { //: { metadata: { path: root/Default/Default/Api/put_path_:id_nn_:user0/S3Object, uniqueId: Api_put_path_id_nn_user0_S3Object_5E224671 } }, bucket: ${aws_s3_bucket.Code.bucket}, key: ASSET_KEY, source: ASSET_SOURCE } } } }这份快照包含 12 类资源覆盖了一个 HTTP 端点从入口到后端的完整链路。下面逐资源结合 SDK 源码深度解析。逐资源深度解析元数据与数据源确定性命名与区域探测快照最上层的//.metadata记录了backend: local本地 Terraform 状态后端与stackName: root这是 CDKTF 合成 Terraform 配置时的标准头。outputs块则声明了本 stack 对外暴露的输出——Api/Endpoint/Url其最终值指向 output 区块中的Api_Endpoint_Url_473FEE9F。data区块中的aws_caller_identity路径root/Default/account与aws_region路径root/Default/Region是 AWS provider 的数据源前者在部署期解析当前账号 ID后者解析部署区域。它们被大量引用——Lambda 函数 ARN、API Gateway 集成 URI、Endpoint URL 都依赖这两个值。这意味着编译产物对账号/区域完全无感同一份main.tf.json可在任意 AWS 账号与区域直接 apply。API Gateway 三件套RestApi、Deployment 与 Stagetf-aws平台把cloud.Api实现为 API Gateway REST APIaws_api_gateway_rest_api、部署aws_api_gateway_deployment与阶段aws_api_gateway_stage三件套对应 target-tf-aws/api.ts 中WingRestApi构造器依次调用_initApiGatewayRestApi、_initApiGatewayDeployment、_initApiGatewayStage的逻辑。RestApi 的 OpenAPI body 是最值得研读的部分。快照中body字段是一段完整的 OpenAPI 3.0.3 规范openapi:3.0.3由两条路径组成业务路径/path/{id}/nn/{user}由api.put(/path/:id/nn/:user, ...)生成。operationId为put-path/:id/nn/:user两个路径参数id、user均被声明为required: true的 string 类型——这正是 cloud/api.ts 中_addToSpec解析:param语法并生成parameters数组的结果。每个方法GET/POST/PUT 等都会自动获得一个200响应定义。集成扩展x-amazon-apigateway-integration这是 AWS API Gateway 的 OpenAPI 扩展将请求代理到 Lambda。其uri指向put_path_-id_nn_-user0-c8982eb3这个 Lambda 函数type: aws_proxy表示全代理模式请求原样转发、响应原样返回httpMethod: POST是 Lambda 集成的内部调用方式passthroughBehavior: when_no_match与contentHandling: CONVERT_TO_TEXT定义了映射行为。该扩展的生成逻辑在 target-tf-aws/api.ts 的_createApiSpecExtension中。兜底路径/{proxy}对未匹配任何已定义路由的请求Wing 注入了一个 mock 集成直接返回404与{statusCode: 404, message: Error: Resource not found}。该逻辑由 shared-aws/api.default.ts 的createApiDefaultResponse生成defaultKey会根据已有路径的最长参数前缀构造本快照中无根级参数路径故直接为/{proxy}。若开启了 CORS此兜底路径还会额外处理 OPTIONS 预检请求返回 204 并注入 CORS 响应头。Deployment 与 Stageaws_api_gateway_deployment通过triggers.redeployment sha256(rest_api.body)实现API 定义变化即自动重新部署源码对应 target-tf-aws/api.tsaws_api_gateway_stage的stage_name固定为prod该常量定义于 shared-aws/api.ts。两个资源均使用lifecycle.create_before_destroy避免更新时先删后建造成短暂停机。后端 Lambda 函数运行时、资源与环境变量aws_lambda_function是本次编译为 PUT 路由生成的处理函数Api_put_path_id_nn_user0_A9826CF0其全部属性都可在 target-tf-aws/function.ts 中找到源码对应快照属性值源码说明runtimenodejs20.xWing inflight 代码编译为 JS 后在 Node.js 20 运行时执行architectures[arm64]默认使用 ARM64 架构成本更低memory_size1024默认内存取自DEFAULT_MEMORY_SIZE1024 MBtimeout60默认超时 1 分钟Duration.fromMinutes(1).secondshandlerindex.handlerSDK 生成的 Lambda 入口publishtrue发布版本配合 Lambda 版本管理logging_config.log_formatJSONCloudWatch 日志采用 JSON 格式环境变量揭示了三件事WING_FUNCTION_NAME让运行时知道自己是哪个函数function.ts 中注入WING_TARGET: tf-aws标记部署目标便于运行时分发NODE_OPTIONS: --enable-source-maps让堆栈信息可映射回 Wing 源码行号方便排查function.ts。函数名put_path_-id_nn_-user0-c8982eb3也大有来头前缀put_path_-id_nn_-user0来自 target-tf-aws/api.ts 中prefix method.toLowerCase() path.replace(/\//g, _)的命名规则尾缀是资源名哈希长度上限 64 字符、只允许字母数字与-/_的约束定义在 function.ts。IAM 三角角色、内联策略与托管策略Lambda 需要身份才能运行快照中对应三个 IAM 资源aws_iam_role信任策略只允许lambda.amazonaws.com服务代入sts:AssumeRole这是 Lambda 执行角色的标准形态function.ts。aws_iam_role_policy内联策略中有一条Action: none:null的占位语句。这是 Wing 的刻意设计——当函数尚未绑定任何资源权限时Terraform 不允许空策略SDK 便注入这条无操作语句占位function.ts。一旦通过addPolicyStatements绑定了真实权限例如访问 Bucket、Queue这些语句会被合并进同一策略none:null占位即被替换。aws_iam_role_policy_attachment附加 AWS 托管策略AWSLambdaBasicExecutionRole授予向 CloudWatch Logs 写日志的基础权限function.ts。CloudWatch 日志组默认 30 天保留aws_cloudwatch_log_group的名称与 Lambda 函数名一致/aws/lambda/put_path_-id_nn_-user0-c8982eb3retention_in_days: 30是默认保留期。源码位于 function.ts默认 30 天可传logRetentionDays覆盖传负值则改为无限期保留。路由授权aws_lambda_permission 的精确来源限制aws_lambda_permission是 API Gateway 调用 Lambda 的通行证其四个关键字段展示了 Wing 的最小权限原则principal: apigateway.amazonaws.com只允许 API Gateway 服务调用。source_arn: …execution_arn/*/PUT/path/{id}/nn/{user}来源被精确限定到方法级路径——只有对/path/{id}/nn/{user}的 PUT 请求才能触发该函数同一 API 下的其他路由无法调用。statement_id: AllowExecutionFromAPIGateway-PUT-526849ff由PUT方法与路径的 SHA-1 哈希后 8 位拼接而成target-tf-aws/api.ts。这份资源与快照中//.metadata的路径root/Default/Default/Api/api/permission-PUT-526849ff相互印证同一个哈希526849ff同时出现在资源名与 statement_id 中。代码分发S3 桶与对象aws_s3_bucketroot/Default/Code是承载所有 Lambda 代码包的共享桶bucket_prefix: code-c84a50b1-由 App 级别的codeBucket创建aws_s3_object则把本次函数的 zip 包上传到该桶function.ts。对象key形如asset.addr.bundleHash.zip——只要代码变化bundle 哈希即变化对象键随之变化从而驱动 Lambda 更新。快照中的ASSET_KEY、ASSET_SOURCE是前文提到的sanitize_json_paths替换产物真实产物里key是一个asset.xxx.zip字符串source是本机打包目录的绝对路径。aws_lambda_function通过s3_bucket/s3_key引用该对象形成了「S3 代码包 → Lambda」的部署链路。Endpoint URL 输出最后看output.Api_Endpoint_Url_473FEE9F它拼接为https://rest_api_id.execute-api.region.amazonaws.com/prod注意该 URL 是手工构造的而非直接引用invokeUrl属性。源码注释解释了原因target-tf-aws/api.tsinvokeUrl与 deployment 共享依赖若在栈中其他位置引用会引发 Terraform 循环依赖故 SDK 自行拼装 URL 来打破依赖环。从快照反推的一次 PUT 请求调用链综合上述资源可以完整还原一次 HTTP PUT 请求的生命周期客户端请求https://api-id.execute-api.region.amazonaws.com/prod/path/12345/nn/guy对应test块中http.put发起的请求API Gateway 依据 OpenAPI body 匹配/path/{id}/nn/{user}的 PUT 方法将12345与guy解析为路径参数id、userx-amazon-apigateway-integration通过aws_proxy把请求连同参数转发给 Lambdaput_path_-id_nn_-user0-c8982eb3授权由aws_lambda_permission保障Lambda 中的 inflight 处理器执行断言并返回ApiResponsestatus 200、content-type: application/json; charsetutf-8、body 为id参数值API Gateway 将响应原样回传http.put的调用方收到 200 与响应体12345最终通过快照同源的测试断言。若请求打到未定义路径则落入/{proxy}兜底 mock直接返回 404{statusCode: 404, message: Error: Resource not found}。在本地复现这份快照你可以不依赖 hangar直接在仓库中手动复现编译产物cd tests/sdk_tests/api wing compile -t tf-aws put.test.w执行后会在put.test.w同目录下生成target/put.test.tfaws/main.tf.json其内容与快照一致除被sanitize_json_paths替换的占位符外。也可以运行 hangar 的测试套件来校验快照是否仍然匹配cd tools/hangar pnpm test -- sdk_tests/api如果 SDK 行为发生变化导致编译产物偏离快照测试即失败这正是快照测试的价值所在——它把「不可见的编译行为」固化为「可 diff 的文本资产」。相关实现参考 generated_test_targets.ts、utils.ts 与 generate_tests.ts。小结通过put.test.w这份 tf-aws 编译快照我们完整看到了 Wing「一份语言、多种云」抽象的具体落地一行api.put(/path/:id/nn/:user, inflight ...)在编译期被系统性地展开为 OpenAPI 规范、API Gateway 三件套、Lambda 函数、IAM 权限三角、CloudWatch 日志、S3 代码分发与最小权限授权共十余个 Terraform 资源。快照既是 SDK 行为的回归测试锚点也是理解 Wing 资源命名规则put_path_-id_nn_-user0、默认值1024 MB 内存、60 秒超时、30 天日志保留、prod阶段与依赖编排思路手工构造 Endpoint URL 规避循环依赖的最佳教材。若想继续深入可对照阅读 tests/sdk_tests/api 下其他路由方法get/post/delete 等的快照以及 packages/winglang/sdk/src/target-tf-aws/api.ts 中私有 API Gateway 的 VPC 配置分支观察同一抽象在不同平台与配置下的形态差异。赞分享编程语言编译器云原生语言运行时【免费下载链接】wingA programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡项目地址https://gitcode.com/gh_mirrors/wi/wing点击查看免费下载相关推荐Wing 的 cloud.Api 在 tf-aws 目标下的编译产物深入解读 aws-api.test.w 的 Terraform 快照Wing 的 cloud.Api 在 tf aws 目标下的编译产物深入解读 aws api.test.w 的 Terraform 快照 Wing 语言把基础编程语言编译器云原生语言运行时Wing 云原生 API 的 CORS 配置解析从 cloud.Api(cors: true) 到 tf-aws Terraform 编译产物Wing 云原生 API 的 CORS 配置解析从 cloud.Api cors: true 到 tf aws Terraform 编译产物 Wing 语言将编程语言编译器云原生语言运行时Wing 语言 cloud.Api 的 OPTIONS/HEAD/CONNECT 路由从测试用例到 tf-aws Terraform 编译产物全解析Wing 语言 cloud.Api 的 OPTIONS/HEAD/CONNECT 路由从测试用例到 tf aws Terraform 编译产物全解析 导读 本编程语言编译器云原生语言运行时上一篇群晖硬盘兼容性警告清不掉3步跑通Synology_HDD_db让任意硬盘上户口下一篇Cyclone与数据库集成高效处理异步数据操作的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?