跳到主要内容

JSON、PureJSON 和 Protobuf 的比较与实践

PureJSON 和 JSON 的对比

使用 JSON 的场景

Web 应用安全

在 Web 应用中,当需要输出 JSON 数据并且这些数据可能直接在客户端的 HTML 中使用时,我通常选择使用 JSON 方法。JSON 方法会自动转义 HTML 字符,例如 <>&"',这有助于防止 XSS(跨站脚本攻击),提高应用的安全性。

通用 API 响应

对于大多数 API 响应,尤其是服务于不同客户端的服务,使用 JSON 是一种良好的实践。它提供了一致且安全的输出格式,方便各类客户端解析和处理数据。

处理用户输入

当 API 需要输出包含用户输入的数据时,使用 JSON 方法可以自动减少由于 HTML 字符引发的潜在安全风险,避免恶意输入对系统造成影响。

使用 PureJSON 的场景

保持数据完整性

当我需要确保数据以其原始形式输出,不进行任何转义时,会选择使用 PureJSON。这在处理某些特定的数据格式(如特殊编码或文件内容)时非常有用,能够保证数据的完整性。

前端 JavaScript 处理

如果确定 JSON 响应仅供前端 JavaScript 使用,并且这些数据不会被嵌入到 HTML 中,那么使用 PureJSON 可以避免不必要的字符转义,简化前端的数据处理流程。

内部服务通信

在内部服务之间进行通信时,如果我完全控制客户端如何处理 JSON 数据,可以使用 PureJSON。这样可以确保数据在服务之间传输时不被修改,保持数据的一致性。

Protobuf 的使用

前端直接使用 Protobuf 的考虑

环境兼容性

虽然有 JavaScript 版本的 Protobuf 库(如 google-protobuf),但 Protobuf 主要设计用于服务器和客户端之间的高效数据传输,并非针对直接在浏览器中使用。浏览器对 Protobuf 的原生支持有限,需要额外的库支持。

编码解码处理

在浏览器中使用 Protobuf,需要在客户端进行额外的编码和解码步骤。尽管技术上可行,但这增加了前端代码的复杂性和处理负担。

调试和透明性

相较于 JSON,Protobuf 在浏览器中的调试更为复杂。由于其在网络传输中使用二进制格式,不易于直观理解,调试时可能会遇到困难。

使用中间层转换的优势

数据兼容性和灵活性

通过服务器端中间层将 Protobuf 数据转换为 JSON 格式,可以让前端应用利用标准的 JSON 对象和 API 进行交互,简化开发和维护。

性能与安全

中间层可以处理数据解码,同时实施安全措施,如验证和清洗数据,避免将这些逻辑放在客户端执行,提升应用的整体安全性。

减轻前端负担

通过中间层预处理数据,可以减少前端应用的计算负担,尤其在处理大量或复杂数据结构时,这种方式更为高效。

Protobuf 工具函数的封装

下面是我封装的一个用于发送和接收 Protobuf 消息的工具函数:

const axios = require("axios");
const protobuf = require("protobufjs");
const path = require("path");

/**
* 发送和接收 Protobuf 消息的工具函数
* @param {Object} options 参数对象
* @param {string} options.file .proto 文件的路径
* @param {string} options.api 发送请求的 API 端点
* @param {string} options.method 使用的 HTTP 方法
* @param {Object} options.data 请求中发送的数据对象
* @param {string} options.type Protobuf 消息的类型名称
* @returns {Promise<Object>} 返回解析后的响应数据
*/

async function protoLoader({ file, api, method, data, type }) {
try {
// 加载 Protobuf schema
const root = await protobuf.load(path.resolve(__dirname, file));
const ProtoType = root.lookupType(type);

// 验证数据是否符合 Protobuf schema
const message = ProtoType.create(data); // 如果传递的是对象,也可以使用 .fromObject
const errorMessage = ProtoType.verify(message);
if (errorMessage) throw new Error(errorMessage);

// 将数据编码成 Buffer
const buffer = ProtoType.encode(message).finish();

// 发送包含二进制数据的请求
const response = await axios({
url: api,
method: method,
data: buffer,
responseType: "arraybuffer",
headers: { "Content-Type": "application/x-protobuf" },
});

// 解码响应数据
const decodedResponse = ProtoType.decode(new Uint8Array(response.data));
return decodedResponse.toJSON();
} catch (err) {
// 处理错误
console.error("protoLoader 出错", err);
throw err;
}
}

module.exports = protoLoader;

使用方法如下:

protoLoader({
file: "sumingcheng",
api: "http://sumingcheng",
method: "POST",
data: {
// 根据 Protobuf schema 定义的数据对象
},
type: "YourProtobufTypeName",
})
.then((response) => {
console.log("解析后的响应", response);
})
.catch((error) => {
console.error("发生错误", error);
});

注意事项

  • 在实际使用中,请将 "sumingcheng""http://sumingcheng""YourProtobufTypeName" 替换为你实际的 .proto 文件路径、API 端点和 Protobuf 类型名称。
  • 由于网络请求和编码解码都是异步操作,建议在使用时处理好错误,以防止意外崩溃。