{"id":18621255,"url":"https://github.com/sofastack/sofa-bolt-node","last_synced_at":"2025-08-20T18:34:31.495Z","repository":{"id":57365673,"uuid":"134383201","full_name":"sofastack/sofa-bolt-node","owner":"sofastack","description":"The Node.js implementation of the SOFABolt protocol ","archived":false,"fork":false,"pushed_at":"2023-12-04T12:26:43.000Z","size":79,"stargazers_count":156,"open_issues_count":1,"forks_count":28,"subscribers_count":24,"default_branch":"master","last_synced_at":"2024-12-03T12:16:15.931Z","etag":null,"topics":["eggjs","nodejs","sofa-bolt","sofa-rpc","sofastack"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sofastack.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-05-22T08:18:06.000Z","updated_at":"2024-07-24T08:40:51.000Z","dependencies_parsed_at":"2024-06-18T18:22:46.814Z","dependency_job_id":"b6782d38-efc5-4ac9-8728-31c4c2134a2b","html_url":"https://github.com/sofastack/sofa-bolt-node","commit_stats":null,"previous_names":["alipay/sofa-bolt-node"],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofastack%2Fsofa-bolt-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofastack%2Fsofa-bolt-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofastack%2Fsofa-bolt-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofastack%2Fsofa-bolt-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sofastack","download_url":"https://codeload.github.com/sofastack/sofa-bolt-node/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230445927,"owners_count":18227060,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eggjs","nodejs","sofa-bolt","sofa-rpc","sofastack"],"created_at":"2024-11-07T04:10:05.056Z","updated_at":"2024-12-19T14:08:31.542Z","avatar_url":"https://github.com/sofastack.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sofa-bolt-node\n[Bolt](https://github.com/alipay/sofa-bolt) 协议 Nodejs 实现版本\n\n[![NPM version][npm-image]][npm-url]\n[![build status][travis-image]][travis-url]\n[![Test coverage][codecov-image]][codecov-url]\n[![David deps][david-image]][david-url]\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![npm download][download-image]][download-url]\n\n[npm-image]: https://img.shields.io/npm/v/sofa-bolt-node.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/sofa-bolt-node\n[travis-image]: https://img.shields.io/travis/alipay/sofa-bolt-node.svg?style=flat-square\n[travis-url]: https://travis-ci.org/alipay/sofa-bolt-node\n[codecov-image]: https://codecov.io/gh/alipay/sofa-bolt-node/branch/master/graph/badge.svg\n[codecov-url]: https://codecov.io/gh/alipay/sofa-bolt-node\n[david-image]: https://img.shields.io/david/alipay/sofa-bolt-node.svg?style=flat-square\n[david-url]: https://david-dm.org/alipay/sofa-bolt-node\n[snyk-image]: https://snyk.io/test/npm/sofa-bolt-node/badge.svg?style=flat-square\n[snyk-url]: https://snyk.io/test/npm/sofa-bolt-node\n[download-image]: https://img.shields.io/npm/dm/sofa-bolt-node.svg?style=flat-square\n[download-url]: https://npmjs.org/package/sofa-bolt-node\n\n\n## 一、简介\nSOFABoltNode 是 [SOFABolt](https://github.com/alipay/sofa-bolt) 的 Nodejs 实现，它包含了 Bolt 通讯层协议框架，以及 RPC 应用层协议定制。和 Java 版本略有不同的是，它并不包含基础通讯功能（连接管理、心跳、自动重连等等），这些功能会放到专门的 RPC 模块里实现。\n\n\n## 二、Bolt 通信层协议设计\n\nBolt 协议是一个标准的通讯层协议，目前包含两个大版本，定义如下：\n\n__V1 版本__\n```\nRequest command protocol for v1\n0     1     2           4           6           8          10           12          14         16\n+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+\n|proto| type| cmdcode   |ver2 |   requestId           |codec|        timeout        |  classLen |\n+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+\n|headerLen  | contentLen            |                             ... ...                       |\n+-----------+-----------+-----------+                                                                                               +\n|               className + header  + content  bytes                                            |\n+                                                                                               +\n|                               ... ...                                                         |\n+-----------------------------------------------------------------------------------------------+\n\nResponse command protocol for v1\n0     1     2     3     4           6           8          10           12          14         16\n+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+\n|proto| type| cmdcode   |ver2 |   requestId           |codec|respstatus |  classLen |headerLen  |\n+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+\n| contentLen            |                  ... ...                                              |\n+-----------------------+                                                                       +\n|                          header  + content  bytes                                             |\n+                                                                                               +\n|                               ... ...                                                         |\n+-----------------------------------------------------------------------------------------------+\n```\n\n__V2 版本__\n```\nRequest command protocol for v2\n0     1     2           4           6           8          10     11     12          14         16\n+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+-----+-----+-----+\n|proto| ver1|type | cmdcode   |ver2 |   requestId           |codec|switch|   timeout             |\n+-----------+-----------+-----------+-----------+-----------+------------+-----------+-----------+\n|classLen   |headerLen  |contentLen             |           ...                                  |\n+-----------+-----------+-----------+-----------+                                                +\n|               className + header  + content  bytes                                             |\n+                                                                                                +\n|                               ... ...                                  | CRC32(optional)       |\n+------------------------------------------------------------------------------------------------+\n\nResponse command protocol for v2\n0     1     2     3     4           6           8          10     11    12          14          16\n+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+-----+-----+-----+\n|proto| ver1| type| cmdcode   |ver2 |   requestId           |codec|switch|respstatus |  classLen |\n+-----------+-----------+-----------+-----------+-----------+------------+-----------+-----------+\n|headerLen  | contentLen            |                      ...                                   |\n+-----------------------------------+                                                            +\n|               className + header  + content  bytes                                             |\n+                                                                                                +\n|                               ... ...                                  | CRC32(optional)       |\n+------------------------------------------------------------------------------------------------+\n```\n\nV2 相比 V1 版本，主要两点改进：\n1. 增加了协议版本号（ver1）\n2. 协议层面支持了数据包的 CRC32 校验（后面详细介绍）\n\n主要字段介绍：\n\n- __proto:__ 协议标识位，bolt v1 是 0x01，bolt v2 是 0x02\n- __ver1:__ bolt 协议版本，从 v2 开始 proto 不会再变，升级只变这个版本号\n- __type:__ request/response/request oneway\n- __cmdcode:__ request/response/heartbeat，和 type 有交叉\n- __ver2:__ 应用层协议的版本（暂时没用）\n- __requestId:__ 数据包唯一标识 id\n- __codec:__ body 序列化方式，目前支持 hessian/hessian2/protobuf\n- __switch:__ 是否开启 crc32 校验\n- __headerLen:__ 自定义头部长度\n- __contentLen:__ 内容长度\n- __CRC32:__ 整个数据包通过计算出的 crc32 值（ver1 \u003e 1 时支持）\n\n## 三、功能介绍\n\n### 基本 RPC 调用功能\n\n客户端示例\n\n```js\n'use strict';\n\nconst net = require('net');\nconst pump = require('pump');\nconst protocol = require('sofa-bolt-node');\n\nconst options = {\n  sentReqs: new Map(),\n};\nconst socket = net.connect(12200, '127.0.0.1');\nconst encoder = protocol.encoder(options);\nconst decoder = protocol.decoder(options);\n\nsocket.once('connect', () =\u003e {\n  console.log('connected');\n});\nsocket.once('close', () =\u003e {\n  console.log('close');\n});\nsocket.once('error', err =\u003e {\n  console.log(err);\n});\n\n// 流式 API\npump(encoder, socket, decoder, err =\u003e {\n  console.log(err);\n});\n\n// 监听 response / heartbeat_acl\ndecoder.on('response', res =\u003e {\n  console.log(res);\n});\ndecoder.on('heartbeat_ack', res =\u003e {\n  console.log(res);\n});\n\n// 发送 RPC 请求\nencoder.writeRequest(1, {\n  args: [{\n    $class: 'java.lang.String',\n    $: 'peter',\n  }],\n  serverSignature: 'com.alipay.sofa.rpc.quickstart.HelloService:1.0',\n  methodName: 'sayHello',\n  timeout: 3000,\n});\n\n// 发送心跳包\nencoder.writeHeartbeat(2, { clientUrl: 'xxx' });\n```\n\n服务端示例\n\n```js\n'use strict';\n\nconst net = require('net');\nconst pump = require('pump');\nconst protocol = require('sofa-bolt-node');\n\nconst server = net.createServer(socket =\u003e {\n  const options = {\n    sentReqs: new Map(),\n  };\n  const encoder = protocol.encoder(options);\n  const decoder = protocol.decoder(options);\n  pump(encoder, socket, decoder, err =\u003e {\n    console.log(err);\n  });\n\n  decoder.on('request', req =\u003e {\n    console.log(req);\n    encoder.writeResponse(req, {\n      isError: false,\n      appResponse: {\n        $class: 'java.lang.String',\n        $: `hello ${req.data.args[0]} !`,\n      },\n    });\n  });\n  decoder.on('heartbeat', hb =\u003e {\n    console.log(hb);\n    encoder.writeHeartbeatAck(hb);\n  });\n});\n\nserver.listen(12200);\n```\n\n### 多种序列化方式支持\n\n目前推荐的序列化方式是 protobuf，因为它跨语言性做得比较好。在蚂蚁内部其实我们主要使用的是 hessian 序列化，后面我们会陆续开源关于它的一系列最佳实践，尽请期待。下面我们演示一个 pb 的 demo\n\n通过 *.proto 文件定义接口\n```proto\nsyntax = \"proto3\";\n\npackage com.alipay.sofa.rpc.test;\n\n// 可选\noption java_multiple_files = false;\n\nservice ProtoService {\n  rpc echoObj (EchoRequest) returns (EchoResponse) {}\n}\n\nmessage EchoRequest {\n  string name = 1;\n  Group group = 2;\n}\n\nmessage EchoResponse {\n  int32 code = 1;\n  string message = 2;\n}\n\nenum Group {\n  A = 0;\n  B = 1;\n}\n```\n\n客户端使用 protobuf\n```js\n'use strict';\n\nconst net = require('net');\nconst path = require('path');\nconst pump = require('pump');\nconst protocol = require('sofa-bolt-node');\nconst protobuf = require('antpb');\n\n// 存放 *.proto 文件的目录，加载 proto\nconst protoPath = path.join(__dirname, 'proto');\nconst proto = protobuf.loadAll(protoPath);\n\n// 将 proto 作为参数传入 encoder/decoder\nconst sentReqs = new Map();\nconst encoder = protocol.encoder({ sentReqs, proto });\nconst decoder = protocol.decoder({ sentReqs, proto });\n\nconst socket = net.connect(12200, '127.0.0.1');\nsocket.once('connect', () =\u003e {\n  console.log('connected');\n});\nsocket.once('close', () =\u003e {\n  console.log('close');\n});\nsocket.once('error', err =\u003e {\n  console.log(err);\n});\npump(encoder, socket, decoder, err =\u003e {\n  console.log(err);\n});\n\n// 指定序列化方式为 protobuf\nencoder.codecType = 'protobuf';\n\nconst req = {\n  serverSignature: 'com.alipay.sofa.rpc.test.ProtoService:1.0',\n  methodName: 'echoObj',\n  args: [{\n    name: 'peter',\n    group: 'B',\n  }],\n  timeout: 3000,\n};\n\ndecoder.on('response', res =\u003e {\n  console.log(res.data.appResponse);\n});\n\n// 记录请求、发送请求\nsentReqs.set(1, { req });\nencoder.writeRequest(1, req);\n```\n\n服务端使用 protobuf\n```js\n'use strict';\n\nconst net = require('net');\nconst path = require('path');\nconst pump = require('pump');\nconst protocol = require('sofa-bolt-node');\nconst protobuf = require('antpb');\n\nconst protoPath = path.join(__dirname, 'proto');\nconst proto = protobuf.loadAll(protoPath);\n\nconst server = net.createServer(socket =\u003e {\n  const options = {\n    sentReqs: new Map(),\n    proto,\n  };\n  const encoder = protocol.encoder(options);\n  const decoder = protocol.decoder(options);\n  pump(encoder, socket, decoder, err =\u003e {\n    console.log(err);\n  });\n\n  decoder.on('request', req =\u003e {\n    const reqData = req.data.args[0].toObject({ enums: String });;\n    encoder.writeResponse(req, {\n      isError: false,\n      appResponse: {\n        code: 200,\n        message: 'hello ' + reqData.name + ', you are in ' + reqData.group,\n      },\n    });\n  });\n\n  decoder.on('heartbeat', hb =\u003e {\n    console.log(hb);\n    encoder.writeHeartbeatAck(hb);\n  });\n});\n\nserver.listen(12200);\n```\n\n### CRC32 校验\n\nRPC 在网络传输过程中可能会遇到各种各样奇葩的问题，导致二进制被篡改，如果这个接口是和钱有关的，就可能导致资损，所以 Bolt 协议层面引入了一个校验功能，当开启时会在整个数据包后面额外传输 4 个 bytes 是数据包计算出来的 CRC32 值，接收端收到数据包以后先在本地重新计算 CRC32 值然后和附带的值比对，一致继续处理，不一致则直接报错\n\n该功能由客户端开启，但是开启之前一般有一个协商的过程，服务端通过协商告诉客户端它支持 crc32 校验\n\n```js\n'use strict';\n\nconst net = require('net');\nconst pump = require('pump');\nconst protocol = require('sofa-bolt-node');\n\nconst options = {\n  sentReqs: new Map(),\n};\n\nconst socket = net.connect(12200, '127.0.0.1');\nconst encoder = protocol.encoder(options);\nconst decoder = protocol.decoder(options);\npump(encoder, socket, decoder);\n\n// 客户端开启 crc 校验\nencoder.protocolType = 'bolt2'; // v2 版本以上才支持 crc 校验\nencoder.boltVersion = 2;\nencoder.crcEnable = true;\n\n// 发送\nencoder.writeRequest(1, {\n  args: [{\n    $class: 'java.lang.String',\n    $: 'peter',\n  }],\n  serverSignature: 'com.alipay.sofa.rpc.quickstart.HelloService:1.0',\n  methodName: 'sayHello',\n  timeout: 3000,\n});\n```\n\n## 四、用户接口\n\n### 全局接口\n- `encoder(options)` 创建一个 ProtocolEncoder\n  - @param {Map} sentReqs - 用于存储发送出去的请求\n  - @param {Map} [classCache] - 类定义缓存\n  - @param {Object} [classMap] - hessian 序列化的类型定义\n  - @param {Object} [proto] - protobuf 序列化的接口定义\n  - @param {Url} [address] - TCP socket 地址\n  - @param {String} [codecType] - 序列化方式\n- `decoder(options)` 创建一个 ProtocolDecoder\n  - @param {Map} sentReqs - 用于存储发送出去的请求\n  - @param {Map} [classCache] - 类定义缓存\n  - @param {Object} [classMap] - hessian 序列化的类型定义\n  - @param {Object} [proto] - protobuf 序列化的接口定义\n- `setOptions(options)` 设置一些全局的参数\n\n### ProtocolEncoder 接口\n- `protocolType` 设置协议，bolt/bolt2\n- `codecType` 设置序列化方式，hessian/hessian2/protobuf\n- `boltVersion` 设置 bolt 的版本\n- `crcEnable` 是否开启 crc 校验\n- `writeRequest(id, req, [callback])` 发送请求\n  - @param {Number} id - 数据包唯一标识\n  - @parma {Object} req - 请求对象\n    - @param {String} serverSignature - 服务的唯一标识\n    - @param {String} methodName - 方法名\n    - @param {Array} args - 参数列表\n    - @param {Number} timeout - 超时时间\n    - @param {Object} requestProps - 额外传递的 kv 参数\n- `writeResponse(req, res, [callback])` 发送响应\n  - @param {Object} req - 请求对象，有请求才有响应\n  - @parma {Object} res - 响应对象\n    - @param {Boolean} isError - 是否成功\n    - @param {String} errorMsg - 异常信息，isError=false 的话为 null\n    - @param {Object} appResponse - 响应对象\n    - @param {Object} responseProps - 额外传递的 kv 参数\n- `writeHeartbeat(id, hb, [callback])` 发送心跳请求\n  - @param {Number} id - 数据包唯一标识\n  - @parma {Object} hb - 心跳对象\n    - @param {String} clientUrl - 客户端 url\n- `writeHeartbeatAck(hb, [callback])` 发送心跳响应\n  - @parma {Object} hb - 心跳对象\n\n## 五、接口设计思想\n\n从上面的介绍和接口定义看，我们对协议的实现核心就是 Encoder 和 Decoder 两个类，并且采用了 Nodejs 里[流（Stream）](https://nodejs.org/api/stream.html)的风格\n\n```\n+---------+  pipe  +---------+  pipe  +---------+    response\n| Encoder |  ---\u003e  | Socket  |  ---\u003e  | Decoder |    ...\n+---------+        +---------+        +---------+\n                      |  ^\n                      |  |\n                      |  |\n                      v  |\n+---------+  pipe  +---------+  pipe  +---------+    request\n| Encoder |  ---\u003e  | Socket  |  ---\u003e  | Decoder |    ...\n+---------+        +---------+        +---------+\n```\n\n所有的协议细节，数据的切分都封装在 Encoder/Decoder 两个类中，并且提供标准的 API，所以以后我们要替换其他的通讯层协议（比如：dubbo），那么只需要直接替换就好了\n\n## 六、如何贡献\n\n请告知我们可以为你做些什么，不过在此之前，请检查一下是否有已经[存在的Bug或者意见](https://github.com/alipay/sofa-bolt-node/issues)。\n\n如果你是一个代码贡献者，请参考[代码贡献规范](https://github.com/eggjs/egg/blob/master/CONTRIBUTING.zh-CN.md)。\n\n## 七、开源协议\n\n[MIT](https://github.com/alipay/sofa-bolt-node/blob/master/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofastack%2Fsofa-bolt-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsofastack%2Fsofa-bolt-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofastack%2Fsofa-bolt-node/lists"}