多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

TypeSpec HTTP Client JS 中的 File 序列化:multipart 文件上传与 base64 编码控制

TypeSpec HTTP Client JS 中的 File 序列化:multipart 文件上传与 base64 编码控制 TypeSpec HTTP Client JS 中的 File 序列化multipart 文件上传与 base64 编码控制【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec导读在 TypeSpec 生态中File是描述 HTTP 请求、响应与 multipart 载荷中文件的统一模型而typespec/http-client-js代码生成器负责把它翻译为可运行的 JavaScript/TypeScript 客户端代码。本文基于仓库中的序列化场景文档 file.md完整剖析 File 类型从 TypeSpec 定义到生成的ToApplicationTransform/ToTransportTransform序列化函数的全过程并深入源码解释一个关键行为文件内容contents默认不做 base64 编码。读完本文你将掌握如何在 TypeSpec 中声明带特定 Content-Type 的文件 multipart 请求、理解生成代码中两组转换函数的职责差异以及如何通过源码确认文件二进制数据的传输语义。File 模型TypeSpec HTTP 库中的统一文件抽象File并非http-client-js包的私有类型而是定义在typespec/http库的标准模型 packages/http/lib/main.tsp 中。其完整定义如下对应源码 main.tspsummary(A file in an HTTP request, response, or multipart payload.) Private.httpFile model FileContentType extends string string, Contents extends bytes | string bytes { contentType?: ContentType; // 文件内容的 MIME 类型 filename?: string; // 文件名 contents: Contents; // 文件内容bytes 或 string }三个属性的语义需要特别注意详见源码内注释contentType描述文件内容本身的媒体类型。在文件体file body场景下它来自请求/响应头的Content-Type在 JSON body 场景下它作为响应中的一个字段被序列化。它不一定等于承载该文件的那个请求/响应的Content-Type头。filename文件名称。在文件体场景下来自Content-Disposition头的filename参数默认情况下它不能出现在请求载荷中因为请求头中不允许Content-Disposition只能用于响应与 multipart 载荷。若确实需要在请求中发送必须扩展File并用 HTTP 元数据装饰器重新定位该属性这正是后文场景中FileSpecificContentType extends File并重写filename的动机。contents文件二进制内容类型为bytes或string该属性为必填。场景定义带特定 Content-Type 的文件 multipart 请求关联文档给出了一个典型场景上传一张image/jpg类型的头像图片。TypeSpec 定义为namespace Test; model FileSpecificContentType extends File { filename: string; contentType: image/jpg; } model FileWithHttpPartSpecificContentTypeRequest { profileImage: HttpPartFileSpecificContentType; } post op imageJpegContentType( header contentType: multipart/form-data, multipartBody body: FileWithHttpPartSpecificContentTypeRequest, ): NoContentResponse;要点解析FileSpecificContentType extends File继承标准File并将filename设为必填、把contentType收敛为字面量类型image/jpg。这样生成的客户端类型中contentType会被推导为精确的字符串字面量从而实现类型级约束。HttpPartFileSpecificContentType来自typespec/http的HttpPart包装声明profileImage是一个 multipart 文件 part。multipartBody标记请求体为 multipart/form-data配合header contentType: multipart/form-data明确传输媒体类型。返回NoContentResponse表示上传成功返回 204。从源码看multipart body 的处理入口是 multipart-transform.tsx它遍历HttpOperationMultipartBody.parts对每个 part 调用HttpPartTransform。而 part-transform.tsx 的分流逻辑很直接part.multi为真走数组 partpart.filename存在走FilePartTransform文件 part否则走普通SimplePartTransform。也就是说只要一个 part 带有 filename 语义就会按文件 part 处理。生成的序列化器Application 与 Transport 两组转换函数基于上述 TypeSpechttp-client-js会生成两个序列化函数对应文档 file.md 的 Serializers 小节均位于生成的src/models/internal/serializers.ts1. Application 层jsonFileSpecificContentTypeToApplicationTransformexport function jsonFileSpecificContentTypeToApplicationTransform( input_?: any, ): FileSpecificContentType { if (!input_) { return input_ as any; } return { filename: input_.filename, contentType: input_.contentType, contents: input_.contents, }!; }该函数把传输层transport形态的数据还原为应用层application模型FileSpecificContentType逐字段拷贝filename、contentType、contents。它通常用于响应体解析把 wire 上收到的数据映射回类型安全的 TypeSpec 模型。2. Transport 层jsonFileSpecificContentTypeToTransportTransformexport function jsonFileSpecificContentTypeToTransportTransform( input_?: FileSpecificContentType | null, ): any { if (!input_) { return input_ as any; } return { filename: input_.filename, contentType: input_.contentType, contents: input_.contents, }!; }该函数把应用层模型序列化为传输层wire数据用于请求发送。它与 Application 版本结构对称都是对三个字段的透传映射区别仅在于输入输出类型方向相反input_?: FileSpecificContentType | null输入、any输出。值得留意的是两个函数开头的空值保护if (!input_) return input_ as any;这保证了null/undefined输入被原样透传避免在客户端边界抛出异常体现了生成代码对可选 body 的容错设计。关键行为为什么文件内容不做 base64 编码文档在两组转换函数之后特别强调了一句话It shouldnt try to base64 encode the contents不应尝试对内容做 base64 编码。这一点在生成代码中表现为contents: input_.contents的直接赋值没有任何编码调用。其源码依据在 serializers.tsx 的ModelSerializers组件中let bytesDefaultEncoding: base64 | none base64; if (isOrExtendsFile($, type)) { bytesDefaultEncoding none; } return ( EncodingProvider defaults{{ bytes: bytesDefaultEncoding }} JsonTransformDeclaration type{type} targettransport / JsonTransformDeclaration type{type} targetapplication / /EncodingProvider );判断逻辑isOrExtendsFile同文件 serializers.tsx会递归检查类型自身及其baseModel链是否命中$.model.isHttpFile(type)。只要类型是File或继承了File例如本场景的FileSpecificContentTypebytesDefaultEncoding就从默认的base64切换为none。这一设计的业务含义是对于普通模型中的bytes属性JSON 序列化时默认编码为 base64 字符串通用 JSON 惯例但对于File及其子类型contents承载的是真实文件二进制在 multipart/form-data 文件 part 场景下必须以原始字节发送而不是先 base64 化再塞进 JSON 字段。因此生成器对 File 系列模型禁用默认编码保证contents原样透传。这也解释了为什么文档中两个转换函数对contents都是直通赋值它们服务于文件上传/下载语义而非普通 JSON 对象的字节编码语义。传输侧组装multipart 文件 part 的createFilePartDescriptor序列化函数解决了模型字段的映射而 multipart 请求体的最终组装则由 file-part-transform.tsx 负责它生成对静态助手createFilePartDescriptor的调用return ts.FunctionCallExpression target{getCreateFilePartDescriptorReference()} args{args} /;其中args依次为 part 名称字符串字面量、part 引用itemRef形如body.profileImage以及——仅当 part 的contentTypes恰好为单个且不是*/*时——默认 Content-Type见getContentTypefile-part-transform.tsx。以本场景为例最终生成的客户端操作会是这样对应姊妹场景文档 multipart.md 中展示的完整调用const httpRequestOptions { headers: { content-type: options?.contentType ?? multipart/form-data, }, body: [createFilePartDescriptor(profileImage, body.profileImage, image/jpg)], };可以看到image/jpg正是从 TypeSpec 中contentType: image/jpg字面量推导而来的默认 part Content-Type文件 part 被包装进createFilePartDescriptor与数组 partArrayPartTransform、普通 partSimplePartTransform共同组成 multipart body 数组若 part 未声明唯一 Content-Type多个或*/*则第三个参数被省略由运行时按内容推断。验证路径如何复现与检查生成结果本文所有结论都可以在当前仓库中直接复现验证场景文档file.md 是http-client-js测试套件的场景快照展示了 File 序列化器的预期输出同类场景还包括 multipart.mdmultipart 模型与操作生成、basic_model.md普通模型序列化等全部位于 packages/http-client-js/test/scenarios/serializers 目录。File 模型定义packages/http/lib/main.tsp 中的File模型及属性语义注释。序列化器生成逻辑packages/http-client-js/src/components/serializers.tsx重点观察bytesDefaultEncoding与isOrExtendsFile。multipart 生成逻辑packages/http-client-js/src/components/transforms/multipart 下的multipart-transform.tsx、part-transform.tsx、file-part-transform.tsx三个文件。实践要点小结围绕 File 序列化整理出以下可直接套用的实践规则上传文件时用HttpPartT包装文件模型并配合multipartBody让生成器输出createFilePartDescriptor文件 part通过继承File并重写contentType为字面量类型可为该 part 固定 MIME 类型且生成代码会自动把它作为默认 Content-Type 传入。理解双序列化器ToTransportTransform负责请求方向应用模型 → wire 数据ToApplicationTransform负责响应方向wire 数据 → 应用模型二者字段映射一致仅方向相反。不要手动 base64凡继承自File的模型其contents以原始字节透传生成器已通过bytesDefaultEncoding none屏蔽默认的 base64 编码客户端代码中不要再对文件内容做二次编码。请求中发送文件名有限制filename默认仅用于响应与 multipart 载荷若要在普通请求载荷中携带文件名必须像本场景一样扩展File并显式声明属性的 HTTP 位置。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表