Flatbuffer gRPC streaming definitions (bidi, server, client...) - flatbuffers

I'm trying to implement a Flatbuffer gRPC server and was original confused by the streaming: "server" definition. After much digging and frustration because of the lack of documentation on the topic I did manage to figure out that there are a few streaming types that can be declared:
rpc_service MonsterStorage {
Store(Monster):Stat (streaming: "none");
Retrieve(Stat):Monster (streaming: "server", idempotent);
GetMaxHitPoint(Monster):Stat (streaming: "client");
GetMinMaxHitPoints(Monster):Stat (streaming: "bidi");
}
Now I'm even more curious. It seems that bidi was the one that I needed, but what do none, server and client mean? What does idempotent do to the stream?
Is this actually documented somewhere and I'm just terrible at searching? lol.

gRPC's introduction to the four types of streaming is https://www.grpc.io/docs/languages/cpp/basics/#defining-the-service
Translating that example into flatbuffers yields:
rpc_service RouteGuide {
// A simple RPC where the client sends a request to the server using the stub
// and waits for a response to come back, just like a normal function call.
/// Obtains the feature at a given position.
GetFeature(Point): Feature (streaming: "none");
// A server-side streaming RPC where the client sends a request to the server
// and gets a stream to read a sequence of messages back. The client reads
// from the returned stream until there are no more messages.
/// Obtains the Features available within the given Rectangle. Results are
/// streamed rather than returned at once (e.g. in a response message with a
/// repeated field), as the rectangle may cover a large area and contain a
/// huge number of features.
ListFeatures(Rectangle): Feature (streaming: "server");
// A client-side streaming RPC where the client writes a sequence of
// messages and sends them to the server, again using a provided stream.
// Once the client has finished writing the messages, it waits for the server to
// read them all and return its response.
/// Accepts a stream of Points on a route being traversed, returning a
/// RouteSummary when traversal is completed.
RecordRoute(Point): RouteSummary (streaming: "client");
// A bidirectional streaming RPC where both sides send a sequence of
// messages using a read-write stream. The two streams operate
// independently, so clients and servers can read and write in whatever order
// they like: for example, the server could wait to receive all the client
// messages before writing its responses, or it could alternately read a
// message then write a message, or some other combination of reads and
// writes. The order of messages in each stream is preserved.
/// Accepts a stream of RouteNotes sent while a route is being traversed,
/// while receiving other RouteNotes (e.g. from other users).
RouteChat(RouteNote): RouteNote (streaming: "bidi");
}

Related

How to flush IN bulk endpoint buffer on device

I'd like to make sure that on the device connected to Chrome (via WebUSB) IN endpoint doesn't contain messages from previous bulk transmission. I checked the API for the WebUSB:
https://wicg.github.io/webusb/
and I don't see any kind of flush function that would allow emptying buffer. I was thinking about reading data until device returns NAK - something like this:
/* #1 Make sure that IN endpoint contains no more data */
while (true) {
let result = await data.transferIn(1, 6);
if (result.length === 0) {
break;
}
}
/* #2 Send request */
await data.transferOut(0x81, message);
/* #3 Receive valid request */
let result = await data.transferIn(1, 6);
but unfortunately it looks that there is no good solution:
when there is no more data to read the transferIn() becomes blocking
function - so we cannot relay on async calling transferIn()
when transferIn() is called in the promise with timeout we can end
with more than one promise waiting for incoming data (which is bad
since we don't know which promise would receive data)
What would be the best approach for making sure the device IN endpoint contains no data?
The concept of an "IN bulk endpoint buffer" doesn't exist in the USB specification. Whether a device responds to an IN PID with DATA or NACK is entirely up the device. The answer may be generated on the fly based on the device state, or be fed from an internal buffer. There is no way for the host to know that the buffer is "empty". This is something that has to be defined at a higher protocol layer between the host and device.
For example, the host or device may indicate the amount of data that is expected to be transfered. The host then knows how much data it can expect to read from the IN endpoint before the current operation is complete. This is how the USB Mass Storage protocol works.
If the protocol between the host and device doesn't define these kinds of message boundaries the best way to flush to buffer is not to try. Instead, always be reading from the IN endpoint and interpret data as it is received. This may include using setTimeout() to checking whether a response has been received to a particular request within a given deadline. Data received not in response to a request could be discarded if it is uninteresting.

Creating a queue per remote method when using RabbitMQ?

Let's just accept for a moment that it is not a horrible idea to implement RPC over message queues (like RabbitMQ) -- sometimes it might be necessary when interfacing with legacy systems.
In case of RPC over RabbitMQ, clients send a message to the broker, broker routes the message to a worker, worker returns the result through the broker to the client. However, if a worker implements more than one remote method, then somehow the different calls need to be routed to different listeners.
What is the general practice in this case? All RPC over MQ examples show only one remote method. It would be nice and easy to just set the method name as the routing rule/queue name, but I don't know whether this is the right way to do it.
Let's just accept for a moment that it is not a horrible idea to implement RPC over message queues (like RabbitMQ)
it's not horrible at all! it's common, and recommended in many situations - not just legacy integration.
... ok, to your actual question now :)
from a very high level perspective, here is what you need to do.
Your request and response need to have two key pieces of information:
a correlation-id
a reply-to queue
These bits of information will allow you to correlate the original request and the response.
Before you send the request
have your requesting code create an exclusive queue for itself. This queue will be used to receive the replies.
create a new correlation id - typically a GUID or UUID to guarantee uniqueness.
When Sending The Request
Attach the correlation id that you generated, to the message properties. there is a correlationId property that you should use for this.
store the correlation id with the associated callback function (reply handler) for the request, somewhere inside of the code that is making the request. you will need to this when the reply comes in.
attach the name of the exclusive queue that you created, to the replyTo property of the message, as well.
with all this done, you can send the message across rabbitmq
when replying
the reply code needs to use both the correlationId and the replyTo fields from the original message. so be sure to grab those
the reply should be sent directly to the replyTo queue. don't use standard publishing through an exchange. instead, send the reply message directly to the queue using the "send to queue" feature of whatever library you're using, and send the response directly to the replyTo queue.
be sure to include the correlationId in the response, as well. this is the critical part to answer your question
when handling the reply
The code that made the original request will receive the message from the replyTo queue. it will then pull the correlationId out of the message properties.
use the correlation id to look up the callback method for the request... the code that handles the response. pass the message to this callback method, and you're pretty much done.
the implementation details
this works, from a high level perspective. when you get down into the code, the implementation details will vary depending on the language and driver / library you are using.
most of the good RabbitMQ libraries for any given language will have Request/Response built in to them. If yours doesn't, you might want to look for a different library. Unless you are writing a patterns based library on top of the AMQP protocol, you should look for a library that has common patterns implemented for you.
If you need more information on the Request/Reply pattern, including all of the details that I've provided here (and more), check out these resources:
My own RabbitMQ Patterns email course / ebook
RabbitMQ Tutorials
Enterprise Integration Patterns - be sure to buy the book for the complete description / implementation pattern. it's worth having this book
If you're working in Node.js, I recommend using the wascally library, which includes the Request/Reply feature you need. For Ruby, check out bunny. For Java or .NET, look at some of the many service bus implementations around. In .NET, I recommend NServiceBus or MassTransit.
I've found that using a new reply-to queue per request can get really inefficient, specially when running RabbitMQ on a cluster.
As suggested in the comments direct reply-to seems to be the way to go. I've documented here all the options I tried before settling to that one.
I wrote an npm package amq.rabbitmq.reply-to.js that:
Uses direct reply-to - a feature that allows RPC (request/reply) clients with a design similar to that demonstrated in tutorial 6 (https://www.rabbitmq.com/direct-reply-to.html) to avoid declaring a response queue per request.
Creates an event emitter where rpc responses will be published by correlationId
as suggested by https://github.com/squaremo/amqp.node/issues/259#issuecomment-230165144
Usage:
const rabbitmqreplyto = require('amq.rabbitmq.reply-to.js');
const serverCallbackTimesTen = (message, rpcServer) => {
const n = parseInt(message);
return Promise.resolve(`${n * 10}`);
};
let rpcServer;
let rpcClient;
Promise.resolve().then(() => {
const serverOptions = new rabbitmqreplyto.RpcServerOptions(
/* url */ undefined,
/* serverId */ undefined,
/* callback */ serverCallbackTimesTen);
return rabbitmqreplyto.RpcServer.Create(serverOptions);
}).then((rpcServerP) => {
rpcServer = rpcServerP;
return rabbitmqreplyto.RpcClient.Create();
}).then((rpcClientP) => {
rpcClient = rpcClientP;
const promises = [];
for (let i = 1; i <= 20; i++) {
promises.push(rpcClient.sendRPCMessage(`${i}`));
}
return Promise.all(promises);
}).then((replies) => {
console.log(replies);
return Promise.all([rpcServer.Close(), rpcClient.Close()]);
});
//['10',
// '20',
// '30',
// '40',
// '50',
// '60',
// '70',
// '80',
// '90',
// '100',
// '110',
// '120',
// '130',
// '140',
// '150',
// '160',
// '170',
// '180',
// '190',
// '200']

How do I subscribe to a reactive streams implementation running on a different JVM?

Let's assume we have two Akka Stream flows, each running on its own JVM.
// A reactive streams publisher running on JVM 1:
val stringPublisher: Publisher[String] = Source(() => "Lorem Ipsum".split("\\s").iterator).runWith(Sink.publisher[String])
// A reactive streams subscriber running on JVM 2:
def subscriber: Subscriber[String] = Sink.foreach[String](println(_)).runWith(Source.subscriber[String])
// Subscribe the second stream to the first stream
stringPublisher.subscribe(subscriber)
This example runs fine on one JVM, but how can I subscribe to a publisher running on a different JVM?
Do I have to use messaging/queueing middleware or can I use the reactive streams API to connect the two together?
The reactive-streams spec does not speak about distributed (crossing network) streams, and none of the current implementations of it (Akka Streams as an example) implement streams that cross network boundaries. It's a bit tricky to do (but can be done and possibly will be) as it requires transparent re-delivery in case of message loss.
Short answer: you (currently) can't. However since Akka HTTP is stream based and applies back-pressure via TCP you can connect streams via stream based TCP or HTTP and the back-pressure will work as expected.

Send files through RabbitMQ

Is it a good idea to send files with size about 1Mb through RabbitMQ? I want to send message in json format with binary fields corresponding to the files.
And how to do it properly using spring-amqp? Just by publishing object with next class?
class Message {
String field1;
byte[] fileField1;
byte[] fileField2;
}
I would suggest not only reading those links that were posted but also, doing some of your own experimentation. The thing I would be concerned about is performance at the service level and at the client level.
You might want to consider having a server host the files/data and allow rabbitmq just send the message to the consumer with the id of the message in it. So when your consumer gets the message, it sends an HTTP GET request to a service that requests the actual message payload. That way RabbitMQ stays lightweight. You can always add consumers and servers if you need.
That's my opinion without experimenting. You might find that it's still lighting fast with 1MB payloads. That's why I would say to experiment and find out for yourself.
Hope you find this helpful!

WCF Service wtih Stream response

I have a WCF service and one of the method returns Stream.
Now the question is while I try to consume that Stream object, am I trying to use the stream over the network or the client had received the full stream on its own side?
Will it make any difference if I would have used RESTful instead of WCF?
The whole point of using the streaming interface in WCF is that the client gets a stream from which it can read blocks of bytes. The whole return object (file, picture, video) will NOT be assembled in full on the server and sent back as once huge chunk, instead, the client can retrieve chunks at a time from the stream returned from the WCF service.
Your client gets back a "Stream" instance, from which it can then read the data, like from a FileStream or a MemoryStream. That way, the amount of memory needed at any given time is reduced to a manageable size (instead of potentially multiple gigabytes in the buffered mode, you'll transfer a large file in e.g. 1 MB chunks or something like that).
Marc