I have a Flask server that sends a serialized protocol buffer message. I've de-serialized this message in a few environments successfully, including a C++ client. However, when I attempt to de-serialize my message in js, nothing works. I am using protobuf.js. According to the documentation, I think I am doing it correctly.
Here is the protobuff file vtkMessage.proto
:
syntax = "proto3";
package vtkMessage;
message hash_element {
string name = 1;
repeated float v = 2;
}
message hash {
repeated hash_element elem = 1;
}
message vtkMsg {
repeated int32 tris=1;
repeated float verts=2;
hash vals=3;
}
Here is my Flask server test.py
:
from flask import Flask
from flask import render_template, request
import vtkMessage_pb2
app = Flask(__name__, static_folder='')
@app.route('/test',methods = ['POST'])
def test():
data = request.get_json()
ret = vtkMessage_pb2.vtkMsg()
ret.tris.extend([1,2,3])
ret.verts.extend([0.45,0.35,0.11, 0.66,0.78,0.23, 0.11,0.01,0.14])
a = ret.vals.elem.add()
a.name = 'test1'
a.v.extend([0.1, 0.2])
a = ret.vals.elem.add()
a.name = 'test2'
a.v.extend([0.3, 0.5])
print(ret)
return ret.SerializeToString() # this decodes successfully when I use C++
@app.route('/protoTest')
def protoTest():
return render_template('protoTest.html')
Here is templates/protoTest.html
:
<html>
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="//cdn.jsdelivr.net/npm/protobufjs@7.2.5/dist/protobuf.js"></script>
<script type="text/javascript">
window.onload = main;
function main() {
var dat = JSON.stringify({
step: 0,
frames: 1,
fname: "openfoam.vtk"
});
protobuf.load('vtkMessage.proto', function(err,root) {
window.vtkMsg = root.lookupType("vtkMessage.vtkMsg");
var proto_obj = {};
$.ajax({
url:'/test',
data: dat,
type: "POST",
contentType: "application/json;charset=utf-8",
async: false,
success: function(res) {
console.log(res);
var buffer = new TextEncoder("utf-8").encode(res);
proto_obj = vtkMsg.decode(buffer); // WHY IS THIS FAILING???
console.log(proto_obj);
},
error: function(res) {
console.error(res); // sometimes this also happens
}
});
console.log(proto_obj);
});
}
</script>
<body>
<h1>Protobuf test, view in console</h1>
</body>
</html>
To get the vtkMessage_pb2.py
you will need to run:
protoc -I=. --python_out=. vtkMessage.proto
I suspect that var buffer = new TextEncoder("utf-8").encode(res);
is incorrect, but I don't see any documentation on how this should be done. Also, my fear is that flask and/or jquery does something funky in handling the POST
request.
It is reasonable to want to ship Protobuf serialized messages using RESTful mechanisms as you're trying to do but, it's unclear to me why you wouldn't just use pure JSON and REST in this case.
That said, your code attempts to mix incorrectly JSON (you POST
JSON to the test
method (which is then discarded) and you attempt to return binary (invalid JSON) from the server.
I suspect (but don't show here) that it would be possible to post application/grpc
(binary) content to a server.
Instead, I show an approach that combines JSON w/ Protobuf binary messages. In order to ship a Protobuf binary message, you must base64-encode it.
{
data: "{base64-encoded serialized Protobuf message}"
}
I'm not very familiar with Flask nor with JavaScript so the following code would benefit from improvement but it works:
from flask import Flask
from flask import jsonify, render_template, request
import base64
import vtkMessage_pb2
app = Flask(__name__, static_folder='')
@app.route('/test',methods = ['POST'])
def test():
# Discard !?
data = request.get_json()
msg = vtkMessage_pb2.vtkMsg()
msg.tris.extend([1,2,3])
msg.verts.extend([0.45,0.35,0.11, 0.66,0.78,0.23, 0.11,0.01,0.14])
a = msg.vals.elem.add()
a.name = 'test1'
a.v.extend([0.1, 0.2])
a = msg.vals.elem.add()
a.name = 'test2'
a.v.extend([0.3, 0.5])
print(msg)
# Convert Protobuf message to binary string
b = msg.SerializeToString()
# Base64 encode it to bundle as JSON
data = base64.b64encode(b).decode("ascii")
# Return JSON message
return jsonify({"data": data})
@app.route('/protoTest')
def protoTest():
return render_template('protoTest.html')
And:
<html>
<meta charset="UTF-8">
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="//cdn.jsdelivr.net/npm/protobufjs@7.2.5/dist/protobuf.js"></script>
<script type="text/javascript">
window.onload = main;
// Base64 decode and return Uint8Array
// See: https://stackoverflow.com/a/21797381/609290
function base64ToBytes(base64) {
var binaryString = atob(base64);
var bytes = new Uint8Array(binaryString.length);
for (var i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
function main() {
// POSTed but ignored by the server
var data = JSON.stringify({
step: 0,
frames: 1,
fname: "openfoam.vtk"
});
protobuf.load('vtkMessage.proto', function(err,root) {
window.vtkMsg = root.lookupType("vtkMessage.vtkMsg");
var proto_obj = {};
$.ajax({
url:'/test',
data: data,
type: "POST",
contentType: "application/json;charset=utf-8",
async: false,
success: function(res) {
// JSON response {"data":...}
console.log(res);
// Extract the Protobuf binary data
b = base64ToBytes(res.data);
// Deserialize the message
msg = vtkMsg.decode(b);
console.log(msg);
},
error: function(res) {
console.error(res);
}
});
console.log(msg);
});
}
</script>
<body>
<h1>Protobuf test, view in console</h1>
</body>
</html>
The console output includes the base64-encoded message:
{
"data": "CgMBAgMSJGZm5j4zM7M+rkfhPcP1KD8Urkc/H4VrPq5H4T0K1yM8KVwPPhomChEKBXRlc3QxEgjNzMw9zcxMPgoRCgV0ZXN0MhIImpmZPgAAAD8="
}
You can use e.g. Bash to extract the hex content:
printf "CgMBAgMSJGZm5j4zM7M+rkfhPcP1KD8Urkc/H4VrPq5H4T0K1yM8KVwPPhomChEKBXRlc3QxEgjN
zMw9zcxMPgoRCgV0ZXN0MhIImpmZPgAAAD8=" \
| base64 --decode \
| xxd -c 128 -g 128
0a0301020312246666e63e3333b33eae47e13dc3f5283f14ae473f1f856b3eae47e13d0ad7233c295c0f3e1a260a110a0574657374311208cdcccc3dcdcc4c3e0a110a05746573743212089a99993e0000003f
You can paste this into e.g. Protobuf Decoder to confirm that it is correct.
And if you browse localhost:5000/protoTest
, you should see the vtkMsg
correctly recreated by the JavaScript client.