How gRPC-web handles bytes data from server-side streaming? - node.js

I want to transmit a sample video file from backend grpc service to the browser using grpc-web, I did some tweaks based on official hello world tutorial. Btw, nothing changed in the envoy configuration. The video file is split into 17 chunks, I can receive 17 messages in browser, however there is nothing inside, what should do so I can get the data?
protobuf definition:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (stream HelloReply);
}
message HelloRequest {}
message HelloReply {
bytes message = 1;
}
server.js:
var PROTO_PATH = __dirname + '/helloworld.proto';
var grpc = require('grpc');
var fs = require('fs');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
var helloworld = protoDescriptor.helloworld;
function doSayHello(call) {
let count = 0;
let videoDataStream = fs.createReadStream('./sample.mp4');
videoDataStream.on('data',function(chunk){
console.log(chunk);
console.log(++count);
call.write({videoStream: chunk});
// call.write(chunk);
}).on('end',function(){
call.end();
});
}
function getServer() {
var server = new grpc.Server();
server.addService(helloworld.Greeter.service, {
sayHello: doSayHello,
});
return server;
}
if (require.main === module) {
var server = getServer();
server.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure());
server.start();
}
exports.getServer = getServer;
client.js:
const {HelloRequest, HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');
var client = new GreeterClient('http://localhost:8080');
var request = new HelloRequest();
client.sayHello(request).on('data', function(chunk){
//console.log(chunk.getMessage());
console.log(chunk);
});
Anyway, in case there is problem with proxy, below is my envoy.yaml:
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address: { address: 0.0.0.0, port_value: 9901 }
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: greeter_service
max_grpc_timeout: 0s
cors:
allow_origin_string_match:
- prefix: "*"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
http_filters:
- name: envoy.grpc_web
- name: envoy.cors
- name: envoy.router
clusters:
- name: greeter_service
connect_timeout: 0.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]
the bytes logged on server side:
and below console output in browser:

Have you tried testing with a gRPC (rather than gRPC Web) client to eliminate the possibility that the proxy is the problem?
I'm not super familiar with the Node.JS implementation but...
Should it not be server.addProtoService(...)?
Also the message streamed by the server is:
message HelloReply {
bytes message = 1;
}
But you:
call.write({videoStream: chunk});
Should it not be:
call.write({message: chunk});

Related

GRPC client using with node js facing issue

I am facing an issue when I am trying to create a grpc client call using node js. when I use import "google/api/annotations.proto" in proto file I get an below error. if I remove it it works file. May I know what I am missing from my client.js
Error: unresolvable extensions: 'extend google.protobuf.MethodOptions' in .google.api
at Root.resolveAll (src/github.com/workspace/explorer/node_modules/protobufjs/src/root.js:255:15)
at Object.loadSync (/src/github.com/workspace/explorer/node_modules/#grpc/proto-loader/build/src/index.js:224:16)
at Object. (/src/github.com/workspace/explorer/server/grpc/client.js:3:37)
syntax = 'proto3';
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
package chain;
service chain {
rpc GetHeight(HeightRequest) returns(HeightResponse) { option (google.api.http).get = "/api/height/{height}";}
}
message HeightRequest {
string hash = 1;
}
message HeightResponse {
int64 height=1;
}
client.js
var PROTO_PATH = __dirname + '/proto/chain.proto';
var parseArgs = require('minimist');
var grpc = require('#grpc/grpc-js');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
var chain_proto = grpc.loadPackageDefinition(packageDefinition).chain;
function main() {
var argv = parseArgs(process.argv.slice(2), {
string: 'target'
});
var target;
if (argv.target) {
target = argv.target;
} else {
target = 'localhost:9040';
}
var client = new chain_proto.chain(target,
grpc.credentials.createInsecure());
client.GetHeight(function (err, response) {
console.log('height:', response);
});
}
main();
I found the solution to the above error, you need to create a folder inside the project directory googleapis->google->api then need to add an annotation.proto file from grpc-gateway GitHub like mention in this link
Grpc-gateway
Next need to add a path as shown below.
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: [
__dirname + '/googleapis',
]
});

how do I make requests with node js grpc server vue js

I am having such a problem when sending a request to the grpc server with vuejs, how can I do?
How can I create a nodejs grpc server and use vue js as a client to send a request and get a response.
MyServer Code here;
const grpc = require("grpc");
const loader = require("#grpc/proto-loader")
const packageDef = loader.loadSync("user.proto", {});
const object = grpc.loadPackageDefinition(packageDef);
const userPackage = object.userPackage;
const server = new grpc.Server()
server.addService(userPackage.User.service, {
"createUser": createUser,
})
const users = []
server.bind('0.0.0.0:5000', grpc.ServerCredentials.createInsecure())
console.log('Server running at http://127.0.0.1:5000')
function createUser(call, callback) {
const userItem = {
id: users.length + 1,
name: call.request.name,
lastname: call.request.lastname
}
users.push(userItem)
console.log('_', call)
console.log('cb', callback)
callback(null, userItem);
}
server.start()
My user.proto here;
syntax = "proto3";
package userPackage;
service User{
rpc createUser(UserItem) returns (UserItem); //unary
}
message UserItem{
int32 id = 1;
string name= 2;
string lastname= 3;
}
and My Client Code here;
<script>
import {UserClient} from './proto/proto-gen/user_grpc_web_pb'
import { UserItem} from './proto/proto-gen/user_pb'
created(){
this.client=new UserClient('http://0.0.0.0:5000',{})
const item=new UserItem()
item.setId(1)
item.setName('aaa')
item.setLastname('bbb')
this.client.createUser(item,{'Access-Control-Allow-Origin': '*'},(err,response)=>{
console.log(`error`, err)
console.log(`response`, response)
})
},
</script>
Error is here;
error {code: 2, message: "Http response at 400 or 500 level", metadata: {…}}
http://0.0.0.0:5000/userPackage.User/createUser net::ERR_ADDRESS_INVALID

How to correctly handle callbacks between Node.js gRPC client & server

I have a simple Express/ gRPC project that's supposed to print a hard coded JSON object to the browser & console. The problem is that the object isn't returned to the client in time to receive it from the gRPC server.
My project is actually a modification of this Codelabs project. I've only implemented the listBooks method though, and have changed Go to Express. I was able to successfully get a response from the server in the Codelabs project.
At first I thought about avoiding callbacks, and trying promises (promises, promisfy...) instead. But I haven't had success. Also, from this answer, I now know that Node gRPC isn't implemented to have sync:
"Node gRPC does not have synchronous calls." - murgatroid99
With that said, the output is showing that the data isn't being received. What do I need to do to have the client wait for the data to be available to be received?
products.proto
syntax = "proto3";
package products;
service ProductService {
rpc ListProduct (Empty) returns (ProductList) {}
}
message Empty {}
message Product {
int32 id = 1;
string name = 2;
string price = 3;
}
message ProductList {
repeated Product products = 1;
}
server.js
var PROTO_PATH = __dirname + '/products.proto';
var grpc = require('grpc');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var productsProto = grpc.loadPackageDefinition(packageDefinition).products;
var products = [{
id: 123,
name: 'apple',
price: '$2'
}];
function listProduct(call, callback){
console.log(products);
callback(null, products);
}
function main(){
var server = new grpc.Server();
server.addService(productsProto.ProductService.service,
{ListProduct: listProduct}
);
server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());
console.log("server started");
server.start();
}
main();
client.js
var PROTO_PATH = __dirname + '/products.proto';
var grpc = require('grpc');
var protoLoader = require('#grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var products = grpc.loadPackageDefinition(packageDefinition).products;
var client = new products.ProductService('localhost:50051', grpc.credentials.createInsecure());
function printResponse(error, response){
if(error){
console.log('Error: ', error);
}
else{
console.log(response);
}
}
function listProducts() {
client.listProduct({}, function(error, products){
printResponse(error, products);
});
}
exports.listProducts = listProducts;
client-server.js
const express = require('express');
const app = express();
var client = require('./client');
app.get('/', (req, res) => {
console.log('Hello World!');
client.listProducts();
res.send('Hello World!');
});
app.listen(3000, () =>
console.log('Example app listening on port 3000!'),
);
Actual Result
The gRPC server obviously already has the object, but I print it to console just for fun. The client prints its callback result to the console, but is only printing an empty object.
server.js Output
[ { id: 123, name: 'apple', price: '$2' } ]
client.js Output
Hello World!
{ products: [] }
To Reproduce
Open 2 terminals, cd to project
Run node server.js in one terminal
Run node client-server.js in the other terminal
Open a browser to localhost:3000
The problem here has nothing to do with synchronous vs asynchronous actions.
The object you are trying to send from your server request handler does not match the response message type you declared in your products.proto file. The response message type ProductList is a message with a single field products that is a repeated list of ProductObjects. So to match that the object you send should be an object with a products field that contains an array of objects structured like ProductObject messages. The code you have is almost there. You have the array, so your server handler code should look like this:
function listProduct(call, callback){
console.log(products);
callback(null, {products: products});
}
The output you already have hints towards this. The object your client receives is {products: []}, which is the structure of the objects your server should be sending.

Distributed lock of Hazelcast using nodejs

I have the Hazelcast cluster server running in the 172.30.56.60, 61, 62
(i.e)
[ Member {
address: Address { host: '172.30.56.60', port: 5701, type: 4 },
uuid: 'bd6428f0-e888-453f-872f-6fe8296d751d',
isLiteMember: false,
attributes: {} },
Member {
address: Address { host: '172.30.56.61', port: 5701, type: 4 },
uuid: 'e0cd795a-0ca5-41ab-907a-492b61690a18',
isLiteMember: false,
attributes: {} },
Member {
address: Address { host: '172.30.56.62', port: 5701, type: 4 },
uuid: '0a834ae8-e707-4b5b-945b-362bfea08cf5',
isLiteMember: false,
attributes: {} } ]
I try to implement the Hazelcast distributed locking using nodejs using the following code,
// Initialize the hazelcast client instance.
var HazelcastClient = require('hazelcast-client').Client;
var Config = require('hazelcast-client').Config;
var config = new Config.ClientConfig();
config.networkConfig.addresses = [{host: '172.30.56.60', port: '5701'},{host: '172.30.56.61', port: '5701'}, {host: '172.30.56.62', port: '5701'}];
var lock = {};
var sleep = require('sleep');
HazelcastClient
.newHazelcastClient(config)
.then(function (hazelcastClient) {
lock = hazelcastClient.getLock("lock1");
// do stuff with lock
lock.lock();
console.log('Am locked in node with lock1...will be locked for 20 seconds');
sleep.sleep(20);
console.log('Unlocked now...');
lock.unlock();
process.exit();
});
I started the script node by node, I expected to establish the lock node by node, but instead it locks all the nodes in the same time. So it is not working as a distributed lock, so all the script started and ending in the same time (NOTE : For testing I provided 20 seconds sleep)
Please let me know, How to establish the distributed lock using node js in Hazelcast.
I found the answer myself, I didn't realize the return of promise, my bad (new to nodejs)
// Initialize the hazelcast client instance.
var HazelcastClient = require('hazelcast-client').Client;
var Config = require('hazelcast-client').Config;
var config = new Config.ClientConfig();
config.networkConfig.addresses = [{host: '172.30.56.60', port: '5701'},{host: '172.30.56.61', port: '5701'}, {host: '172.30.56.62', port: '5701'}];
var sleep = require('sleep');
// Test process
HazelcastClient
.newHazelcastClient(config)
.then(function (hazelcastClient) {
var lock = hazelcastClient.getLock('rowId_tablename');
// lock the code here
lock.lock().then(function() {
console.log('Am locked in node with lock3...will be locked for 20 seconds');
sleep.sleep(20);
// unlock after process
return lock.unlock();
}).then(function() {
console.log('unlocked now');
});
});

register new xmpp account with node-xmpp ( node.js )

I'm looking at 'XEP-0077 in-band registration' about how to register a new XMPP account. Here is my code. I use node-xmpp to connect my node.js application to an ejabberd server.
var net = require('net');
var xmpp = require('node-xmpp');
var cache = new Object();
net.createServer( function(socket) {
socket.setEncoding('utf8');
socket.addListener('data',function(data) {
data = data.substr(0,data.length-2);
if(cache.admin==undefined && data=='login') {
var ejabberd =new xmpp.Client({jid:"admin#mine",password:'12345',host:'192.168.7.202',port:'5222'});
cache.admin = ejabberd;
cache.admin.addListener('online',function() {
cache.admin.send(new xmpp.Element('presence',{type:'chat'}).c('show').c('status').t('mine status'));
cache.admin.send(new xmpp.Element('iq',{type:'get',id:'reg1'}).c('query',{xmlns:'jabber:iq:register'}));
})
cache.admin.addListener('stanza',function(stanza) {
if(stanza.is('iq')) {
console.log(stanza.children[1]);
}
})
cache.admin.addListener('end',function() {
cache.admin.end();
cache.admin = undefined;
})
}
if(cache.admin!=undefined && data=='logout') {
cache.admin.end();
cache.admin = undefined;
} else if(cache.admin!=undefined && data=='register') {
cache.admin.send(new xmpp.Element('iq',{type:'set',id:'reg1'}).c('query',{xmlns:'jabber:iq:register'}).c('username').t('alow').up().c('password').t('test'));
}
});
}).listen(5000);
If i run this code, I get this error:
{ name: 'error',
parent:
{ name: 'iq',
parent: null,
attrs:
{ from: 'admin#mine',
to: 'admin#mine/20108892991316770090454637',
id: 'reg1',
type: 'error',
xmlns: 'jabber:client',
'xmlns:stream': 'http://etherx.jabber.org/streams' },
children: [ [Object], [Circular] ] },
attrs: { code: '403', type: 'auth' },
children:
[ { name: '**forbidden**',
parent: [Circular],
attrs: [Object],
children: [] } ] }
In 'XEP-0077: In-Band Registration' it says that the forbidden reason means that "The sender does not have sufficient permissions to cancel the registration".
How can I get such permissions?
I have been strugling with something similar, I wanted to register a new user account via in-band registraton from nodejs to an ejabberd server running in ubuntu. Here is what I did and worked for me:
//Dependencies
var xmpp = require('node-xmpp');
//Host configuration
var host = "localhost";
var port = "5222";
var admin = "sebastian#localhost";
var adminPass = "adminPass";
var connection = new xmpp.Client({
jid: admin,
password: adminPass,
host: host,
port: port
});
//user to be registered name & pass
var newUserName = "pepe";
var newUserPass = "pepePass";
//Stream
var iq = "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:component:accept' to='localhost'><iq type='set' id='reg2'><query xmlns='jabber:iq:register'><username>" + newUserName + "</username><password>" + newUserPass + "</password></query></iq></stream>";
//Send
connection.send(iq);
//End
connection.end();
The var iq is kind of messy, I suppose that if you know how to use Strophe.js in a propper way that part could look a little bit nicer and cleaner. I was missing the section of the xml, it seems that if you want to send a stream, you have to provide a valid ejabberd namespace, that was what was failing for me. Hope this helps you sort your problem out.
Which server are you using? Are you sure it has XEP-77 enabled? Test with an existing client. Ensure that the account you're trying to create does not already exist. Ensure that the account has the correct domain name.

Resources