原链接:https://webapplog.com/json-is-not-cool-anymore/
声明:csdn强制在图片上加水印,文中所使用图片均来自于作者的博客,本文只做翻译。
目前,在web文件传输中,有了一种比JSON更好的选择——Protocol Buffers(protobuf)。Protobuf提供了一个更紧凑的文件格式,并且提供一种数据模式(能让这个结构进行扩展,能够跟旧的代码兼容)
Protocol Buffers由Google提出。你可以在Protocol的官网上找到Protocol Buffers开发指南,如果想知道更多信息,点击为什么在接下来的服务中用Protocol Buffers而不是JSON的5大理由。
这篇文章的用意不是为了介绍protobufs多流弊,也不是为了向你兜售这个概念,网上有很多文章已经干过这个事儿了。这篇文章是叫你咋用,咋在Node.js环境下用。
这篇文章会带你过一遍Node.js,Express.js,Axios和Protobuf.js的一些API,这篇文章在Node v6.2.0下写成,用了最前沿的ES6/ES2015版本的JavaScript语言。
我们会创建一个message,message里面包含两个字段:text和lang,这两个字段会作为protobuf从服务器端发送,然后在浏览器端进行解码;我们还会创建一个按钮,这个按钮会将另外的protobuf的message发送到服务器端,代码源码放在github上:azat-co/proto-buffer-api。
整个项目的结构为:
/proto-buffer-api
/public
axios.min.js
bytebuffer.js
index.html
long.js
message.proto
protobuf.js
/node_modules
index.js
package.json
public下放置的是浏览器中所必须的js文件。我们使用Axios来从浏览器向服务器发出请求。这类似于Superagent和Request。你也可以使用JQuery来发出请求。如果你发出请求的库用的不是Axios,只要确保你发出的数据是以ArrayBuffer的形式并且是作为application/octet-stream发出来的就好了。
protobuf.js文件是Google公司的Protocol Buffers的JavaScript版本,所以我们需要在浏览器中加载这个js文件。JavaScript支持long型数据(在JavaScript中的数字长度只有53bits)。有一个库可以让我们使用64-bit的数字,这个库叫做long.js。
message.proto是message类的原型(也是protobuf一般情况下编译的文件)。这个类就是我们一般在浏览器端和服务器端传送的数据。这个数据一般长这样:
message Message {
required string text = 1;
required string lang = 2;
}
protobuf.js需要一个或者更多的依赖库,例如:bytebuffer.js用来存储ArrayBuffer类型的数据。
上面的Message的存储格式,应该相当容易理解吧。我们有两个字段,一个text,一个lang。这两个字段都是required类型的字段。紧跟着字段后面的值,就是protocol buffers编解码的内容。
index.html文件,麻雀虽小,五脏俱全,包含了我们前面讲到的所有的lib文件。<pre>容器中插入来自服务器的响应(response from the server);按钮用来触发sendMessage()函数(这个函数在后面会实现);<script>标签用来处理请求(requests)和protobuf代码。
<html>
<head>
<script src="long.js"></script>
<script src="bytebuffer.js"></script>
<script src="protobuf.js"></script>
<script src="axios.min.js"></script>
</head>
<body>
<pre id="content"></pre>
<button onClick="sendMessage()">send message to server</button>
<script type="text/javascript">
// Our requests and Protobuf code
</script>
</body>
</html>
接下来让我们深入探究一下浏览器中JavaScript的两种请求:GET请求来从服务器中获取信息,POST请求来向服务器发送信息。这两者都有用到protocol buffers。
首先,我们在message.proto文件中创建了一个Message的原型类。在loadProtoFile的回调函数中,我们通过调用loadMessage()的方式来从服务器中GET请求。
[注]
看博客帖子当然很不错啦,不过看视频课程有的时候可能更有感觉。
有不少开发者抱怨,Node.js缺少高质量的视频课程,在YouTube上看,还是花个500刀买课程看,都足够让程序员抓狂啦!
访问Node University,这里有很多Node的课程。
[注完](竟然是广告!!!!不过看起来资源不错,而且也是翻译的人家的,就挂在这儿啦~)
"use strict";
let ProtoBuf = dcodeIO.ProtoBuf
let Message = ProtoBuf
.loadProtoFile('./message.proto', (err, builder)=>{
Message = builder.build('Message')
loadMessage()
})
在loadMessage函数中,当然是使用我们之前提到的用于请求/响应的库Axios来设置请求,我们还需要提供一个响应的数据类型:arraybuffer,这个参数能够告诉HTTP,给我们返回正确的数据类型。Axios使用promises的方式运行,所以我们可以使用then函数来运行下一个函数,得到响应后,我们先输出,然后使用Message.decode()完成解码工作。
let loadMessage = ()=> {
axios.get('/api/messages', {responseType: 'arraybuffer'})
.then(function (response) {
console.log('Response from the server: ', response)
let msg = Message.decode(response.data)
console.log('Decoded message', msg)
document.getElementById('content').innerText = JSON.stringify(msg, null, 2)
})
.catch(function (response) {
console.log(response)
})
}
GET请求的结果可以在开发者工具(DevTools)中看到。你也可以在application/actet-stream中看到这个响应。
如果想要发送一个protocol buffers的数据给服务器,那么需要创建一个对象:new Message(data),然后调用msg.toArrayBuffer()。最好能够在application/octet-stream中设置Content-Type头,这样服务器就能知道接收到的数据是什么格式的数据。
let sendMessage = ()=>{
let msg = new Message({text: 'yo', lang: 'slang'})
axios.post('/api/messages', msg.toArrayBuffer(),
{ responseType: 'arraybuffer',
headers: {'Content-Type': 'application/octet-stream'}}
).then(function (response) {
console.log(response)
})
.catch(function (response) {
console.log(response)
})
}
带有正确Content-Type的POST请求结果和加载过程也可以在开发者工具中找到。
至此,我们的前端工作就已经做完了,简单吧!接下来我们需要用Node/Express来发布代码,这样才能运转起来。
首先,得创建一个package.json,可以直接拷贝下面含有依赖库的文件:
{
"name": "proto-buffer-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Azat Mardan",
"license": "MIT",
"dependencies": {
"express": "^4.13.4",
"protobufjs": "^5.0.1"
}
}
有了这个package.json文件之后,我们可以通过npm install,然后就会安装用来建立HTTP服务的express和在服务器上使用Protocol Buffers的protobufjs。
我们先在服务器端写一个js文件,命名为index.js,在这个文件里,我们导入一些库,创建express对象,并且将express对象应用到public文件夹中:
let path = require('path')
let express = require('express')
let app = express()
let publicFolderName = 'public'
app.use(express.static(publicFolderName))
接下来,为了简化整个项目,我们会开辟一段内存来存储数据。换句话说,上面所述的message会存储在一个数组中:
let messages = [
{text: 'hey', lang: 'english'},
{text: 'isänme', lang: 'tatar'},
{text: 'hej', lang: 'swedish'}
]
一般情况下,最典型的应用是我们会使用body-parser来解析JSON请求。为了正确地处理protobuf数据,我们需要按照buffers数组的方式进行解析。将之前自定义的解析处理函数应用在解析protobufs中,解析出来的结果存储在body.raw中(decorator模式)。当且仅当Content-Type为application/octet-stream并且有解析出来的数据(data.length > 0)时,会创建一个body.raw来存储数据。
app.use (function(req, res, next) {
if (!req.is('application/octet-stream')) return next()
var data = [] // List of Buffer objects
req.on('data', function(chunk) {
data.push(chunk) // Append Buffer object
})
req.on('end', function() {
if (data.length <= 0 ) return next()
data = Buffer.concat(data) // Make one large Buffer of it
console.log('Received buffer', data)
req.raw = data
next()
})
})
现在,我们就可以创建builder对象,然后从prototype文件中“build”我们的Message了。还是用之前前端里在public文件夹中的public/message.proto文件。
let ProtoBuf = require('protobufjs')
let builder = ProtoBuf.loadProtoFile(
path.join(__dirname,
publicFolderName,
'message.proto')
)
let Message = builder.build('Message')
这样,我们在GET中就可以创建一个message了,在发送到前端之前,对其进行编码,并将其转化为Buffer类型。Express的response.send()负责添加正确的’Content-Type’头,当然,你也可以使用response.end()。
app.get('/api/messages', (req, res, next)=>{
let msg = new Message(messages[Math.round(Math.random()*2)])
console.log('Encode and decode: ',
Message.decode(msg.encode().toBuffer()))
console.log('Buffer we are sending: ', msg.encode().toBuffer())
// res.end(msg.encode().toBuffer(), 'binary') // alternative
res.send(msg.encode().toBuffer())
// res.end(Buffer.from(msg.toArrayBuffer()), 'binary') // alternative
})
对于POST请求的处理,我们等于是把前面的过程逆过来。也就是说,从body.raw(之前定义的一个中间件(middleware))。
app.post('/api/messages', (req, res, next)=>{
if (req.raw) {
try {
// Decode the Message
var msg = Message.decode(req.raw)
console.log('Received "%s" in %s', msg.text, msg.lang)
} catch (err) {
console.log('Processing failed:', err)
next(err)
}
} else {
console.log("Not binary data")
}
})
app.all('*', (req, res)=>{
res.status(400).send('Not supported')
})
app.listen(3000)
如果跟着我敲了一遍代码,或者你直接从Github上粘了我的这个项目azat-co/proto-buffer-api,你就可以在web page上看到来自服务器随机发送的message。当你点击按钮的时候,你可以在正在运行的服务器端(node的命令行,或者其他什么地方)看到”yo”。
以上,就是protobuf应用的全部。我们使用GET和POST的方式来在服务器端的Node.js/Express.js和浏览器端的Protobuf.js之间传递protocol buffers。我们用Axios来发从浏览器端发送请求,Axios允许我们使用promises进行处理,并且能够抽象一些the low-level XMLHttpRequest interface来处理我们的二进制数据。
谷歌使用Protocol Buffers来作为他们的API。Protobufs在很多方面都比JSON和XML文件要高级,这篇文章主要是帮助你快速入门Protobuf.js,希望能够帮你在自己的项目中构建自己的Protocol Buffers的API。
------------------
作者信息:
Azat Mardan
Microsoft MVP | Book and Course Author | Software Engineering Leader
https://www.linkedin.com/in/azatm