Efficient schematized serialization and RPC for QtQuick2 applications through Protocol Buffers and gRPC bindings.
For installation, see INSTALL.md.
For Protocol Buffers itself, see upstream official site:
https://developers.google.com/protocol-buffers/
Suppose you write following .proto file, say my_foo.proto
:
message Foo {
string text = 1;
repeated uint32 nums = 2;
}
you can generate QML/Javascript message type using compiler plugin:
$ protoc --qml_out gen my_foo.proto
This will yield gen/my_foo.pb.js
file. Let's import this file from any QML using relative path.
If you want to try it out without building protobuf-qml, a docker image for code generator is available.
# docker pull nsuke/protobuf-qml
# docker run -it -v $(pwd):/opt/protobuf-qml nsuke/protobuf-qml -I. \
--qml_out=<relative path to output dir> \
<relative path to your .proto file>
Note that the relative paths cannot contain parent or sibling directories, i.e., they need to be within current or sub directories.
import 'gen/my_foo.pb.js' as Types
then you can use the message type inside signal handlers, property bindings or functions:
var foo = new Types.Foo({
text: 'hello world',
nums: [ 3, 4, 5 ],
});
var buf = foo.serialize();
here, the buf
variable is an ArrayBuffer
object.
Deserialization is quite simple too:
var foo2 = Types.Foo.parse(buf);
console.log(foo2.text)
// output: hello world
console.log(foo2.nums(1))
// output: 4
TBD: Link to full sample code
For gRPC itself, see upstream official page: http://www.grpc.io/
gRPC binding is still experimental.
Suppose you add service definition to the my_foo.proto
above:
service MyService {
rpc ExchangeFoo(Foo) returns(Foo) {}
}
compiler plugin will additionally yield MyService.qml
and MyServiceClient.qml
files besides my_foo.pb.js
file.
Let's import the directory containing those QML files:
import 'gen'
then you can instantiate QML elements:
MyServiceClient {
id: client
}
MyService {
id: service
}
To make the client element functional, plug it to a gRPC channel. (In fact, you can plug to custom RPC implementation but gRPC works out of the box)
import 'gen'
import gRPC
GrpcChannel {
id: gchannel
target: 'example.org:44448'
credentials: InsecureCredentials {}
}
MyServiceClienit {
id: client
channel: gchannel
}
Then inside signal handlers, property bindings or functions :
client.exchangeFoo({
text: 'hi',
nums: [1, 2, 3],
}, function(err, responseFoo) {
if (err) {
console.warn('Received error response: ' + err);
return;
}
// Do some useful stuff with "responseFoo" content
});
This will make a RPC call to example.org port 44448.
You can create gRPC server in QML app if you want to. It's handy for P2P communication and/or notifications, but not suitable for heavy computation.
import 'gen'
import gRPC
GrpcServer {
id: gserver
address: '0.0.0.0:44448'
credentials: ServerInsecureCredentials {}
}
MyService {
id: server
server: gserver
// This function is called for each incoming request.
exchangeFoo: function(requestFoo, callback) {
// First argument is error.
callback(null, {
text: 'In response to ' + requestFoo.text,
nums: [42],
});
}
}
From Qt 5.8 (QtWebSockets 1.1 QML module) on, we can send array buffers through websockets.
import QtWebSockets 1.1
WebSocket {
id: socket
url: 'ws://your.remote.server'
active: true
}
// Inside function or handler
var msg = new Types.Foo({
text: 'hello world',
nums: [ 3, 4, 5 ],
});
var buf = msg.serialize();
socket.sendBinaryMessage(buf);
WebSocket {
id: socket
url: 'ws://your.remote.server'
active: true
onBinaryMessageReceived: {
var msg = Types.Foo.parse(message);
console.log(msg.text);
console.log(msg.nums(0));
console.log(msg.nums(1));
// ... Do whatever with the data
}
}
The complete code is available under examples/WebSockets directory.
In the future, it might become possible to send ArrayBuffer using XMLHttpRequest too.
I've submitted a patch for XMLHttpRequest to Qt project which enables following usage.
// Inside function or handler
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://your.remote.server/');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var response = Types.Foo.parse(xhr.response);
// ... Do whatever with the data
}
};
xhr.responseType = 'arraybuffer';
var msg = new Types.Foo({
text: 'hello world',
// ...,
});
var buf = msg.serialize();
xhr.send(new DataView(buf));
The complete code is available under examples/XMLHttpRequest directory.