
Effect 4.0 新增 HttpServerResponse.fromClientResponse客户端响应到服务端响应的直接转换【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect本篇聚焦 Effect 仓库中一个面向effect/unstable/http模块的新特性HttpServerResponse.fromClientResponse。该函数用于把HttpClientResponse客户端收到的响应直接转换为HttpServerResponse服务端要返回的响应其变更说明见 changeset 文件 chilled-mice-wash.md并在 CHANGELOG.md 中记录为 4.0.0-beta.28 的 patch 变更。读完本文你将理解这两个 HTTP 响应模型之间的转换语义状态码、请求头、Cookie、流式 Body 的处理方式、源码层面的实现细节与边界行为以及如何在代理转发和服务端测试场景中使用这一 API。两个响应模型为什么要直接互转Effect 的 HTTP 体系中存在两组平行的响应模型HttpClientResponse客户端视角的响应绑定一个发起请求的HttpClientRequest携带url、status、headers、cookies以及stream以HttpClientError为错误类型的StreamUint8Array。HttpServerResponse服务端视角的响应在 HttpServerResponse.ts 中定义包含status、可选statusText、headers、cookies与bodyHttpBody可为 Empty / Stream / Uint8Array / Text / Json / FormData 等变体。典型需要在这两个模型之间搬运数据的场景是代理/网关服务端通过HttpClient调用上游服务拿到HttpClientResponse后需要原样转发给真实客户端。过去这类转发需要手动重建响应逐个复制状态码、请求头、Body 流容易遗漏Set-Cookie迁移、content-length校验等细节。fromClientResponse把这一过程收敛为一次调用这正是 changeset 中所描述的用途“for directly converting client responses into server responses”。该函数定义于 HttpServerResponse.tsJSDoc 明确标注since 4.0.0、category converting并通过 http/index.ts 以effect/unstable/http子路径导出。API 语义函数做了什么源码中的完整实现如下HttpServerResponse.ts/** * Converts an HttpClientResponse to an HttpServerResponse. * * **Details** * * The response body is streamed from the client response. Set-Cookie headers are * removed from the header map and represented in the response cookie collection. * * category converting * since 4.0.0 */ export const fromClientResponse ( response: HttpClientResponse.HttpClientResponse ): HttpServerResponse { const headers Headers.remove(response.headers, set-cookie) return makeResponse({ status: response.status, headers, cookies: response.cookies, body: Body.stream( Stream.catchIf(response.stream, isEmptyBodyError, () Stream.empty), Option.getOrUndefined(Headers.get(headers, content-type)), bodyInternal.parseContentLength(headers[content-length]) ) }) }从实现可以逐条确认它的行为契约状态码原样保留response.status直接传给makeResponse不做任何改写。Set-Cookie迁移到 Cookie 集合先用Headers.remove(response.headers, set-cookie)把Set-Cookie从头映射中移除再把response.cookies客户端响应已解析出的 Cookie 集合整体挂到新响应上。这与HttpServerResponse的模型约定一致——Cookie 走cookies字段而不是头映射测试用例中response.headers[set-cookie]为undefined而response.cookies.cookies.session?.value 123正是对这一点的验证。Body 以流式方式承接转换后的 Body 恒为Body.stream变体流就是客户端响应的response.stream数据在消费时才真正流动惰性透传而非立即缓冲。content-type与content-length从请求头恢复content-type原值作为流 Body 的 contentTypecontent-length经过bodyInternal.parseContentLength校验后作为 contentLength 传入。空 Body 错误降级为空流Stream.catchIf(response.stream, isEmptyBodyError, () Stream.empty)配合下面的判定把上游返回的EmptyBodyError转成一个正常的空流const isEmptyBodyError ( error: HttpClientError.HttpClientError ): error is HttpClientError.HttpClientError HttpClientError.isHttpClientError(error) error.reason._tag EmptyBodyError这一点处理了 204/304 这类“协议上不允许携带 Body”的响应上游读取时会抛出EmptyBodyError转换后它变成合法的“空流”而不是让下游消费者失败。内部构造makeResponse 如何同步头信息fromClientResponse的最终产物由私有构造函数 makeResponse 创建它负责“Body 元信息与请求头的一致性同步”当 Body 非Empty且自带contentType/contentLength时若头映射中尚未显式给出对应的content-type/content-length就自动补写反之若调用方已经显式设置了这两个头则显式值优先。这个规则在测试 “synchronizes body metadata headers for empty and replaced bodies”HttpServerResponse.test.ts中得到验证setBody替换 Body 后旧的content-type/content-length会被移除新 Body 的元信息会重新同步进头映射。fromClientResponse传入的 headers 已包含上游的content-type因此不会与显式值冲突而content-length若未通过parseContentLength校验则 Body 的 contentLength 为undefined头中保留上游的原始值见下文测试分析。测试用例逐条解读HttpServerResponse.test.ts 为fromClientResponse提供了 5 个专门用例是理解其边界行为最可靠的材料1. 保留状态码、请求头、Cookie 与 JSON Bodyconst clientResponse HttpServerResponse.toClientResponse( HttpServerResponse.jsonUnsafe({ foo: bar }, { status: 201 }).pipe( HttpServerResponse.setHeader(x-test, ok), HttpServerResponse.setCookieUnsafe(session, 123) ), { request } ) const response HttpServerResponse.fromClientResponse(clientResponse)断言覆盖了状态码 201 保留、content-type: application/json保留、自定义头x-test保留、set-cookie从头映射中消失并进入cookies集合、JSON Body 经往返后仍可解析为{ foo: bar }。2. 流 Body 保留 Effect 依赖Requirementsconst clientResponse HttpServerResponse.toClientResponse( HttpServerResponse.stream( Stream.fromEffect(TestValue).pipe(Stream.map(String), Stream.encodeText) ) ) const response HttpServerResponse.fromClientResponse(clientResponse) const text yield* roundTrip.text.pipe(Effect.provideService(TestValue, 420))该用例证明转换只是“包装”不会把流固化Body 流仍然携带TestValue服务依赖只有在消费时通过Effect.provideService(TestValue, 420)提供该服务后流才能成功求值出420。3. 保留 FormData Body将HttpServerResponse.formData转成客户端响应再转回服务端响应后content-type仍以multipart/form-data; boundary开头且formData中foobar可读。说明 multipart 边界的请求头在“流化”过程中被完整带过。4. 空客户端 Body 变成空服务端流const clientResponse HttpClientResponse.fromWeb(request, new Response(null, { status: 200 })) const response HttpServerResponse.fromClientResponse(clientResponse) assert.strictEqual(yield* roundTrip.text, )对应源码中的EmptyBodyError降级逻辑Web 层空响应流读取时产生的EmptyBodyError被Stream.catchIf吸收为空流最终text为而非失败。5. 非法或不安全的 content-length 被忽略for (const contentLength of [2junk, 1.5, 1e3, 9007199254740992]) { const clientResponse HttpClientResponse.fromWeb( request, new Response(hello, { headers: { content-length: contentLength } }) ) const response HttpServerResponse.fromClientResponse(clientResponse) assert.strictEqual(response.body._tag, Stream) assert.strictEqual(response.body.contentLength, undefined) }可以确认bodyInternal.parseContentLength会拒绝四类值非纯数字2junk、小数1.5、指数记法1e3以及超出 JS 安全整数范围的90071992547409922^53。此时 Body 的contentLength为undefined即响应以无content-length的流式chunked 语义响应呈现而不是携带一个不可信的长度值。配套 APItoClientResponse 与往返转换fromClientResponse的逆方向是 toClientResponseexport const toClientResponse ( response: HttpServerResponse, options?: { readonly request?: HttpClientRequest.HttpClientRequest | undefined } ): HttpClientResponse.HttpClientResponse它通过内部类ServerHttpClientResponseHttpServerResponse.ts把服务端响应包装成客户端响应不传request时url为空字符串status/headers/cookies全部透传Body 按变体映射到streamEmpty映射为空流Stream变体则把错误重映射为HttpClientError。这对 API 的实际组合方式是双向桥接服务端测试仓库内 HttpApiTest.ts 就使用toClientResponse把 HTTP API 的处理结果包成客户端响应供断言代理转发fromClientResponse把上游HttpClientResponse变成可交给 HTTP 服务器框架HttpApp等返回的HttpServerResponse。一个基于上述测试模式整理的往返示例导入方式与 HttpServerResponse.test.ts 一致import { Effect, Stream } from effect import { HttpClientRequest, HttpClientResponse, HttpServerResponse } from effect/unstable/http // 模拟“上游响应”由服务端响应包装而来真实场景中来自 HttpClient 调用 const request HttpClientRequest.get(http://localhost:3000/todos/1) const clientResponse HttpServerResponse.toClientResponse( HttpServerResponse.jsonUnsafe({ foo: bar }, { status: 201 }), { request } ) // 核心一步客户端响应 - 服务端响应 const toForward HttpServerResponse.fromClientResponse(clientResponse) toForward.status // 201 toForward.headers[content-type] // application/json版本与使用前提该函数标注since 4.0.0变更在 CHANGELOG.md 的4.0.0-beta.28“Patch Changes” 小节中首次记录所在模块effect/unstable/http属于不稳定unstable子路径API 仍可能随 4.0.0 正式版调整生产使用建议锁定版本并关注该子路径的变更说明转换是纯数据映射、无副作用也不消费任何 Fiber/服务可在任意 Effect 程序内同步调用Body 的真实数据传输发生在流被消费的时刻。小结HttpServerResponse.fromClientResponse以约 15 行实现HttpServerResponse.ts补全了 Effect HTTP 体系中“客户端 ↔ 服务端”响应的直接转换能力状态码与请求头透传、Set-Cookie自动迁入 Cookie 集合、Body 惰性流化、EmptyBodyError降级为空流、content-length严格校验后采信。配合toClientResponse与既有测试基线HttpServerResponse.test.ts代理网关转发上游响应、以及 HTTP 服务端响应的客户端化断言都获得了官方支持的标准写法。【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考