Apache Thrift JavaScript 库完全指南:浏览器 RPC 客户端、Node.js Web 服务器与 Grunt 构建实践
【免费下载链接】thriftApache Thrift项目地址: https://gitcode.com/GitHub_Trending/thr/thrift
本文是 Apache Thrift JavaScript(浏览器端)库的实战指南,基于仓库中 lib/js/README.md 展开,结合 lib/js/src/thrift.js、lib/js/Gruntfile.js 与 lib/nodejs/lib/thrift/web_server.js 等源码深入讲解。读完你将掌握:如何用thrift编译器从 IDL 生成 JS 代码、如何通过 XHR 与 WebSocket 两种传输在浏览器中发起 RPC 调用、如何用createWebServer在 Node.js 侧托管静态页面与 Thrift 服务、如何用 Grunt 一键完成构建与测试,以及 64 位整数等关键边界问题的处理方式。
一、库定位:浏览器端的 Apache Thrift 实现
Apache Thrift 是一套跨语言 RPC 框架,而 JavaScript 分支(位于 lib/js)是其浏览器端实现。它支持 RPC 客户端以JSON 协议(TJSONProtocol)通过Http[s](XHR)与WebSocket两种传输通道与服务端通信。在浏览器中,你无需任何插件,只要引入生成的 JS 文件与库文件,即可直接调用 Thrift 服务。
从源码注释可以确认,thrift.js只创建一个全局对象Thrift,所有特性都收敛在该命名空间内(lib/js/src/thrift.js),库中Version常量为'0.25.0',与 lib/js/package.json 中声明的版本一致。该命名空间内部又分为几个层次:
- 传输层(Transport):
Thrift.TXHRTransport(别名Thrift.Transport)负责基于 XMLHttpRequest 的字节级 I/O,Thrift.TWebSocketTransport负责基于 WebSocket 的字节级 I/O; - 协议层(Protocol):
Thrift.TJSONProtocol(别名Thrift.Protocol)负责消息的序列化与反序列化; - 辅助类型:
Thrift.TException、Thrift.TApplicationException、Thrift.TProtocolException、Thrift.Multiplexer等。
端到端的典型调用方式如下(源码注释中的示例,lib/js/src/thrift.js):
var transport = new Thrift.Transport('http://localhost:8585'); var protocol = new Thrift.Protocol(transport); var client = new MyThriftSvcClient(protocol); var result = client.MyMethod();二、获取源码与 Grunt 构建
本目录是 Apache Thrift JavaScript 库的根目录,其中包含Gruntfile.js与package.json。构建与测试工具链依赖较新版本的 Node.js。
2.1 安装构建依赖
执行以下命令安装 Grunt 构建所需的支持文件:
npm install该命令读取 lib/js/package.json 并从互联网拉取相应依赖,其中包括:grunt(^1.6.3)、grunt-cli、grunt-contrib-concat(用于拼接)、grunt-contrib-uglify(用于压缩)、grunt-contrib-jshint(用于代码检查)、grunt-contrib-qunit(用于运行测试)、grunt-jsdoc(用于生成文档)、browserify、node-int64与json-int64(用于 64 位整数支持)等。
2.2 执行构建
npx grunt该命令运行项目本地安装的 Grunt(位于./node_modules/.bin/),执行的任务链定义在 lib/js/Gruntfile.js 的default任务中:["test", "concat", "uglify", "jsdoc"],即:
- test:先执行
installAndGenerate(创建目录、复制thrift.js、安装 Node 依赖、调用编译器生成各变体的测试代码、安装测试依赖、用 browserify 打包 Int64 相关库),随后执行jshint代码检查,启动 HTTP/HTTPS/ES6 测试服务器并运行 QUnit 浏览器测试(详见第六节); - concat:将
src/**/*.js拼接为dist/thrift.js; - uglify:压缩生成带版本横幅的
dist/thrift.min.js; - jsdoc:基于
src/*.js与./README.md生成doc/下的 HTML 文档。
2.3 构建产物目录结构
构建前(及构建后)的目录如下:
| 目录 | 说明 |
|---|---|
/src | JavaScript Apache Thrift 源码(核心为 src/thrift.js) |
/doc | 由 jsdoc 生成的 HTML 文档(构建后出现) |
/dist | 发行文件thrift.js与thrift.min.js(构建后出现) |
/test | 各类测试,也是查阅示例代码的好去处 |
/node_modules | 由npm install安装的构建支持文件 |
三、从 IDL 生成 JavaScript 代码
Thrift 编译器(thrift,源码在 compiler/cpp)支持针对 JS 的多种代码生成选项。以官方测试套件为例,lib/js/Gruntfile.js 中展示了各类变体的生成命令:
thrift -gen js --out test/gen-js ../../test/v0.16/ThriftTest.thrift thrift -gen js:jquery --out test/gen-js-jquery ../../test/v0.16/ThriftTest.thrift thrift -gen js:node --out test/gen-nodejs ../../test/v0.16/ThriftTest.thrift thrift -gen js:es6 --out test/gen-js-es6 ../../test/v0.16/ThriftTest.thrift thrift -gen js:node,es6 --out ./test/gen-nodejs-es6 ../../test/v0.16/ThriftTest.thrift各生成选项的适用场景:
-gen js:生成浏览器端普通 JavaScript(配合 XHR 传输,典型文件名xxx.js+xxxClient);-gen js:jquery:生成依赖 jQuery(1.5+)的浏览器端代码,使用jqRequest方式发起异步请求;-gen js:node:生成 Node.js 端代码(典型为gen-nodejs/xxx.js,配合thriftnpm 包使用);-gen js:es6/-gen js:node,es6:生成 ES6 语法版本,测试服务器可通过--es6参数切换加载。
其中-gen js生成的浏览器端代码(如hello_svcClient)依赖第二节所述的thrift.js运行时;-gen js:node生成的 Node 端代码则依赖thriftnpm 包(即 lib/nodejs 的 Node.js 分支),二者的运行时是分开的。
四、完整示例:hello_svc 服务端到端跑通
下面这个完整示例展示了一个简单的基于浏览器的 JavaScript Thrift 客户端与 Node.js JavaScript 服务端,服务为hello_svc(以下三个代码块完整引自 lib/js/README.md,并补充运行说明)。
4.1 hello.thrift —— 服务 IDL
service hello_svc { string get_message(1: string name) }用以下命令生成浏览器端(gen-js/)与 Node 端(gen-nodejs/)代码:
thrift -gen js -gen js:node hello.thrift4.2 hello.html —— 浏览器客户端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello Thrift</title> </head> <body> Name: <input type="text" id="name_in"> <input type="button" id="get_msg" value="Get Message" > <div id="output"></div> <script src="thrift.js"></script> <script src="gen-js/hello_svc.js"></script> <script> (function() { var transport = new Thrift.TXHRTransport("/hello"); var protocol = new Thrift.TJSONProtocol(transport); var client = new hello_svcClient(protocol); var nameElement = document.getElementById("name_in"); var outputElement = document.getElementById("output"); document.getElementById("get_msg") .addEventListener("click", function(){ client.get_message(nameElement.value, function(result) { outputElement.innerHTML = result; }); }); })(); </script> </body> </html>要点说明:
- 页面先引入运行时库
thrift.js,再引入编译器生成的gen-js/hello_svc.js; - 传输层
Thrift.TXHRTransport("/hello")指向服务端挂载 Thrift 服务的 URL 路径; - 协议层
Thrift.TJSONProtocol(transport)负责 JSON 序列化; - 生成的客户端
hello_svcClient(protocol)的get_message(name, callback)为异步调用,回调参数即服务端返回的字符串结果。
4.3 hello.js —— Node.js 服务器
var thrift = require('thrift'); var hello_svc = require('./gen-nodejs/hello_svc.js'); var hello_handler = { get_message: function(name, result) { var msg = "Hello " + name + "!"; result(null, msg); } } var hello_svc_opt = { transport: thrift.TBufferedTransport, protocol: thrift.TJSONProtocol, processor: hello_svc, handler: hello_handler }; var server_opt = { staticFilePath: ".", services: { "/hello": hello_svc_opt } } var server = thrift.createWebServer(server_opt); var port = 9099; server.listen(port); console.log("Http/Thrift Server running on port: " + port);说明:
- 服务端使用 Node.js 分支的
thrift包(lib/nodejs),createWebServer是创建"静态文件 + Thrift 服务"一体化 Web 服务器的工厂函数(实现在 lib/nodejs/lib/thrift/web_server.js); hello_svc_opt中processor与handler成对出现:handler提供业务实现,processor由 IDL 编译生成,负责把请求路由到对应处理方法;- 一个服务器可以通过
services对象挂载多个服务路径,浏览器客户端只需把TXHRTransport的 URL 指向对应路径即可。
注意:README 示例中服务端选项写为
staticFilePath,而当前源码 web_server.js 实际读取的选项键名是files。若选项缺失或为空字符串,静态文件服务将被禁用(见 web_server.js),因此建议使用files指定静态资源根目录。另外,README 示例中的Thrift.createWebServer应理解为thrift.createWebServer(小写包名导出的函数),与第五节正式配置示例保持一致。
五、createWebServer 服务端配置详解
createWebServer(options)返回一个原生 Node.js HTTP/HTTPS Server 实例(可直接listen(port))。其完整选项在 lib/nodejs/lib/thrift/web_server.js 中有详细注释,整理如下。
5.1 ServerOptions(服务器级配置)
| 选项 | 类型 | 说明 |
|---|---|---|
cors | array/object | 允许跨域请求的来源(Origin)字符串数组;含"*"或命中请求 Origin 时放行,否则返回 403 |
files | string | 静态文件服务根目录;缺省或为空字符串时禁用静态文件服务 |
headers | object | 静态文件 GET 响应时附加的响应头(键值对哈希) |
services | object | 将服务 URI 字符串映射到 ServiceOptions 对象的哈希 |
tls | object | Node.js TLS 选项(见 Node.js tls 文档);不提供或为 null 时使用普通 HTTP,启用 SSL/TLS 至少需要key与cert |
5.2 ServiceOptions(服务级配置)
| 选项 | 类型 | 说明 |
|---|---|---|
transport | object | 分层传输,默认TBufferedTransport |
protocol | object | 序列化协议,默认TBinaryProtocol(示例与 JS 浏览器端配套常用TJSONProtocol) |
processor | object | IDL 编译器生成的服务类/处理器;为兼容历史用法,也可用cls键传入;直接传入处理器对象或处理器类均可(见 web_server.js) |
handler | object | 服务的业务处理方法实现 |
一个真实的配置范例来自测试服务器 lib/js/test/server_http.js:
const thrift = require('../../nodejs/lib/thrift'); const ThriftTestSvc = require('./gen-nodejs/ThriftTest.js'); const ThriftTestHandler = require('./test_handler').ThriftTestHandler; const ThriftTestSvcOpt = { transport: thrift.TBufferedTransport, protocol: thrift.TJSONProtocol, processor: ThriftTestSvc, handler: ThriftTestHandler }; const ThriftWebServerOptions = { files: __dirname, services: { '/service': ThriftTestSvcOpt } }; const server = thrift.createWebServer(ThriftWebServerOptions); server.listen(8089);5.3 请求路由与 CORS 处理
createWebServer创建的服务器在底层为不同 HTTP 方法挂接了不同的处理逻辑(web_server.js):
- POST:
processPost按请求路径在services中查找服务,将请求体交给svc.transport.receiver(...)累积数据,再以new svc.protocol(transportWithData)构造输入协议、new svc.protocol(new svc.transport(...))构造输出协议,最终调用svc.processor.process(input, output)完成 RPC(web_server.js); - GET:
processGet提供静态文件服务。它会对路径做规范化校验,防止../逃逸出baseDir(web_server.js),目录请求自动回退到index.html,并按扩展名映射 Content-Type(如.html→text/html、.js→application/javascript、.json→application/json等,见 web_server.js); - OPTIONS:处理 CORS 预检,放行时返回 204,并设置
access-control-allow-origin、access-control-allow-methods: GET, POST, OPTIONS、access-control-allow-headers: content-type, accept等头(web_server.js); - upgrade(WebSocket):服务端手工完成 RFC 6455 握手(校验
Sec-WebSocket-Key并计算Sec-WebSocket-Accept),随后按帧协议解析客户端消息并路由到services中的第一个服务(web_server.js)。帧编码支持文本(TJSONProtocol)与二进制(TBinaryProtocol)两种 opcode。
六、传输层与协议层源码级解析
6.1 TXHRTransport:基于 XHR 的 HTTP 传输
Thrift.TXHRTransport(别名Thrift.Transport)在 lib/js/src/thrift.js 中定义,构造函数接受 URL 与可选 options(useCORS、customHeaders)。其flush(async, callback)方法:
- 通过
getXmlHttpRequestObject()获取浏览器 XHR 对象(兼容旧版ActiveXObject); - 以POST方式发送
send_buf,并在请求头中设置Accept与Content-Type为application/vnd.apache.thrift.json; charset=utf-8; - 异步模式下通过
onreadystatechange在readyState == 4 && status == 200时把responseText写入接收缓冲并回调;同步模式下直接检查status == 200并填充recv_buf(thrift.js)。
此外,jqRequest(client, postData, args, recv_method)提供了 jQuery 集成路径:它要求 jQuery 1.5+(jQuery.Deferred),通过jQuery.ajax发起 POST,并利用自定义 converter'text thrift'将响应文本交给recv_method反序列化(thrift.js)。
6.2 TWebSocketTransport:基于 WebSocket 的传输
Thrift.TWebSocketTransport(thrift.js)维护callbacks(待回调队列)与send_pending(连接未建立前缓存的发送请求):
flush(async, callback):连接已打开时立即socket.send(send_buf)并把回调压入队列;未打开时先缓存,待__onOpen时统一补发(thrift.js);__onMessage:按先进先出顺序弹出回调并传入服务端消息数据;open():仅在readyState为 CLOSED(或无 socket)时才创建new WebSocket(url)并绑定onopen/onmessage/onerror/onclose。
6.3 TJSONProtocol:JSON 序列化协议
Thrift.TJSONProtocol(别名Thrift.Protocol)在 thrift.js 中定义。它把 Thrift 类型映射为 JSON 内的类型标记字符串(thrift.js):
| Thrift 类型 | JSON 类型标记 |
|---|---|
| BOOL | "tf" |
| BYTE/I08 | "i8" |
| I16 | "i16" |
| I32 | "i32" |
| I64 | "i64" |
| DOUBLE | "dbl" |
| STRING/UTF7 | "str" |
| STRUCT | "rec" |
| MAP | "map" |
| LIST | "lst" |
| SET | "set" |
序列化时通过writeMessageBegin(name, messageType, seqid)将消息组织为[version, name, messageType, seqid, ...]数组结构(Thrift.Protocol.Version = 1),反序列化时通过readMessageBegin()读取并校验版本号,不匹配则抛出"Wrong thrift protocol version"(thrift.js)。反序列化会优先使用JSONInt64.parse(当引入json-int64时)以正确处理 64 位整数。
协议层还内置了两道安全防线:
- 递归深度限制:
Thrift.DEFAULT_RECURSION_DEPTH = 64,incrementRecursionDepth()超过该值会抛出DEPTH_LIMIT类型的TProtocolException(thrift.js); - 负数长度防御:
readMapBegin/readListBegin在 size 为负时抛出NEGATIVE_SIZE异常,skip()深度超过 64 层抛出DEPTH_LIMIT,防止恶意数据导致异常行为(thrift.js)。对应测试可参考 lib/js/test/test-protocol-negative-size.js。
6.4 多路复用:Multiplexer
Thrift.Multiplexer(thrift.js)允许在单条连接上承载多个服务:createClient(serviceName, SCl, transport)为客户端生成自增seqid,并通过Thrift.MultiplexProtocol在写消息时把方法名改写为serviceName + ':' + name,服务端据此区分不同服务。
七、测试体系:如何验证你的 JS 分支
7.1 浏览器端测试入口
lib/js/test/README.md 说明测试结构如下:
- 服务端:
server_http.js是支持标准 Apache Thrift 测试套件(test/v0.16/ThriftTest.thrift)的 Node.js Web 服务器,同时支持 XHR 与 WebSocket 客户端;server_https.js是同一服务器的 SSL/TLS 版本,证书取自 test/keys 目录;HTTP 对应ws:,HTTPS 对应wss:; - 客户端:三个基于 QUnit 的 HTML 驱动文件——
test.html测试 jQuery 生成代码(-gen js:jquery)、test-nojq.html测试普通 JS 构建(-gen js,均使用 XHR 传输)、testws.html测试 WebSocket 传输;test*.js为实际测试逻辑。
7.2 通过 Grunt 一键运行
在 lib/js 目录执行npx grunt test(或默认的npx grunt)即可自动完成:生成各类测试代码(ThriftGen系列任务)→jshint代码检查 → 启动四个测试服务器(HTTP/HTTPS × 普通/ES6)→ 用 headless Puppeteer 运行 QUnit 测试(覆盖 XHR、jQuery、WebSocket、ES6、Int64、递归深度、双重渲染等场景,见 Gruntfile.js)→ 结束后杀掉服务器进程。
7.3 与 Java 测试服务器联调
如需以 Java 分支的测试服务器运行客户端测试,可在 lib/js 目录执行make check(要求已先构建 Apache Thrift Java 分支并在 lib/java 中执行过make check)。
八、TypeScript 支持
浏览器端与 Node 端的 TypeScript 定义文件也可以通过编译器生成:
thrift --gen js:ts file.thrift生成的.d.ts定义文件可为 TypeScript 项目提供完整的类型提示。此外,Node.js 分支的 TypeScript 相关测试与工具位于 lib/nodets,可作为补充参考。
九、64 位整数与 Breaking Changes
从 0.13.0 版本起,一个重要的破坏性变更影响了 64 位整数常量的生成方式:64 位整数常量现在使用node-int64生成,例如:
var x = new Int64("7fffffffffffffff");在浏览器端,lib/js/package.json 将node-int64与json-int64列为依赖;Grunt 的ThriftBrowserifyNodeInt64任务会用 browserify 把node-int64、json-int64及 lib/nodejs/lib/thrift/int64_util.js 打包为浏览器可用的Int64、JSONInt64、Int64Util全局对象(Gruntfile.js)。协议层writeI64会调用Int64Util.toDecimalString(i64)进行十进制字符串转换,readMessageBegin优先使用JSONInt64.parse解析(thrift.js),从而保证超过 JavaScript 安全整数范围的 64 位数值不会丢失精度。相关测试可见 lib/js/test/test-int64.js 与 lib/js/test/test-int64.html。
十、实践要点小结
- 浏览器端:引入
dist/thrift.js(或thrift.min.js)与编译器生成的gen-js/*.js,用TXHRTransport(url)+TJSONProtocol构造客户端即可异步调用 RPC; - 传输选择:需要双向/长连接用
TWebSocketTransport,普通请求/响应用 XHR 即可;服务端createWebServer同时支持二者(WebSocket 握手由服务端内置实现); - 服务端:
createWebServer一个实例即可同时托管静态页面(files)与多个 Thrift 服务(services),并通过tls选项一键启用 HTTPS/WSS,通过cors控制跨域; - 64 位整数:涉及大整数时务必配套
json-int64解析库,并按 0.13.0 之后的写法使用Int64包装常量; - 构建与测试:在 lib/js 下依次执行
npm install与npx grunt,即可完成代码检查、测试、拼接、压缩与文档生成全套流程。
更完整的 IDL 语法可参考 doc/specs/idl.md 与 doc/specs/thrift-json-protocol.md(文档目录下包含 JSON 协议规范),官方测试套件 test/v0.16/ThriftTest.thrift 覆盖了全部数据类型与集合组合,是编写 IDL 与验证客户端行为的最佳参照。
【免费下载链接】thriftApache Thrift项目地址: https://gitcode.com/GitHub_Trending/thr/thrift
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考