rsocket / rsocket-js

JavaScript implementation of RSocket

Home Page:https://github.com/rsocket/rsocket-js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Better server documentation

regbo opened this issue · comments

commented

There is almost no documentation for getting a server up and running, here or anywhere on the internet. I had to dig through the docs to find out that I can use yarn install and yarn run to get to the simpl-cli. The "docs" only cover the client side of things.

It would be great if a simple example of a running server that implements the rsocket methods could be added.

commented

FYI, it took me 2+ hours to get a Nodejs server running. I am a noob when it comes to Node, but I can't believe I'm the only one who would struggle here.

I tried using yarn install && yarn run simple-cli but that didn't work. I then used a translated Japanese blog post (that's how little documentation there is) to figure it out:
https://kamoqq.info/post/rsocket-rpc-javascript/

FWIW, here's a server implementation;

const {
	RSocketServer,
	BufferEncoders
} = require('rsocket-core');
const RSocketTCPServer = require('rsocket-tcp-server').default;
const {
	Single
} = require('rsocket-flowable');

const host = "127.0.0.1";
const port = 3000;

const transportOpts = {
	host: host,
	port: port
};
const transport = new RSocketTCPServer(transportOpts, BufferEncoders);

const getRequestHandler = (requestingRSocket, setupPayload) => {
	console.log(`setupPayload sent}`)
	return {
		fireAndForget: (payload) => {
			console.log(`fireAndForget payload:${payload.data.toString()}`)
		},
		requestResponse: (payload) => {
			console.log(`requestResponse payload:${payload.data.toString()}`)
			const single = new Single(subscriber => {
					setTimeout((msg) => {
						console.log(`requestResponse response msg:${msg}`)
						try {
							subscriber.onComplete({
								data: Buffer.from(JSON.stringify(msg)),
								metadata: null, // or new Buffer(...)
							})
						} catch (e) {
							subscriber.onError(e)
						}

					}, 3 * 1000, `the current time is:${new Date()}`);
					subscriber.onSubscribe();
				});
			return single;
		}
	};
}

const rSocketServer = new RSocketServer({
		transport,
		getRequestHandler
	});

rSocketServer.start();
console.log(`Server started on port ${port}`);

Which requires:

npm install rsocket-core rsocket-tcp-server rsocket-flowable

Hi @regbo,

You are entirely correct that the documentation is currently in a state that requires improvement. Improving the documentation for multiple RSocket projects (including rsocket-java, and rsocket-js) is something we are passionate about, but also something that will take time to accomplish.

If there are any specific areas, along with server examples, that you believe would have been helpful to you, please do let us know in detail, as that will assist us in producing documentation useful to new users of RSocket.

Thanks for providing this feedback, and apologies for the frustration.

Hi @regbo -- please see rsocket/rsocket-website#52. I would be interested to hear if you have any feedback on the content that has been added to the rsocket-js guide there.

Thanks

commented

Thanks for providing this feedback, and apologies for the frustration.

No need to apologize! You are doing a lot of hard work on an open source project. I will look into this tomorrow, but after a cursory look now it looks great.

commented

Sorry if I chime in, but I struggle with similar problems.
First of all I applaud this effort because the project is great.

I would like to try it but I was unable to setup a simple project where the server is using spring boot (it has native rsocket support) and the client runs on the command line (no browser) with node.

If there is something similar I kindly ask for a pointer, thanks!

@Polve, @regbo, just wondering if you have seen this set of examples -> https://github.com/rsocket/rsocket-js/tree/master/packages/rsocket-examples/src. They demonstrate both - client used with Composite Metadata (gives full compatibility with Spring) as well as the same handler written in pure node using RSocket for server-side

commented

@Polve, @regbo, just wondering if you have seen this set of examples -> https://github.com/rsocket/rsocket-js/tree/master/packages/rsocket-examples/src. They demonstrate both - client used with Composite Metadata (gives full compatibility with Spring) as well as the same handler written in pure node using RSocket for server-side

Yes, I saw that example, and I am able to run it now, but there was no documentation as to how to start the server. Nothing in the yarn install/run, or in any of the docs. That's where the major disconnect was.

@regbo thanks for the feedback. We will have that improved!

commented

Hi @regbo -- please see rsocket/rsocket-website#52. I would be interested to hear if you have any feedback on the content that has been added to the rsocket-js guide there.

Thanks

I think this format is absolutely fantastic. Great work. This is exactly the type of thing that would get a new user up and running in no time. Thanks again.

This may be considered a separate issue, and it may be documented elsewhere, and it may not be worth the effort, but I would also consider having a couple of "real world" use cases provided in the different languages. For example, I've used RSocket java for a few years, and it's worked great for bi directional RPC calls. I was then struggling to find a fast way to execute Node/Python calls from Java, and I realized that implementing small RSocket servers would work well (below is the WIP node code).

const {
	RSocketServer,
	BufferEncoders
} = require('rsocket-core');
const RSocketTCPServer = require('rsocket-tcp-server').default;
const {
	Flowable
} = require('rsocket-flowable');
const {
	Worker
} = require('worker_threads');

//utils

const bufferToString = (buffer) => {
	if (buffer == null)
		return null;
	return buffer.toString()
}

const objectToBuffer = (obj) => {
	if (obj == null)
		return null;
	return Buffer.from(JSON.stringify(obj))
}

//arguments
//console.log(JSON.stringify(process.argv))
const host = process.argv[process.argv.length - 3];
const port = Number.parseInt(process.argv[process.argv.length - 2]);
const password = process.argv[process.argv.length - 1];

const transportOpts = {
	host: host,
	port: port
};
const transport = new RSocketTCPServer(transportOpts, BufferEncoders);

//helpers

const authenticate = (payload) => {
	if (!password)
		return true;
	return password === bufferToString(payload.metadata)
}

const logError = function (err) {
	if (err != null)
		console.log(err);
	return err;
};

const getWorker = (payload) => {
	var code = `
		const {
			parentPort
		} = require('worker_threads');
		const resultPromise = new Promise((resolve, reject) => {
				var func = () => {
					//eval code {bufferToString(payload.data)}
					${bufferToString(payload.data)}
				};
				resolve(func());
			});
		resultPromise.then((v) => parentPort.postMessage(v), (e) => parentPort.postMessage(e));
				`;
	//console.log(code)
	var worker = new Worker(code, {
		eval: true
	});
	return worker;
}

const getWorkerPromise = (payload) => {
	var worker = getWorker(payload);
	var workerPromise = new Promise((resolve, reject) => {
		worker.on('message', v => {
			if (v instanceof Error)
				reject(v);
			else
				resolve(v);
		});
		worker.on('error', reject);
	});
	workerPromise.terminate = () => {
		console.log(`killing worker:${worker.threadId}`)
		worker.terminate();
	};
	worker.on('exit', () => {
		workerPromise.terminate = () => { }
	});
	return workerPromise;
};

const fireAndForget = (payload) => {
	if (!authenticate(payload))
		throw logError(new Error('fireAndForget unauthorized'));
	var workerPromise = getWorkerPromise(payload);
	workerPromise.then(v => {
		//console.log(`complete:${v}`)
	}, e => {
		logError(e);
	});
}

const requestStream = (payload) => {
	if (!authenticate(payload))
		return Single.error(logError(new Error('requestStream unauthorized')));
	var flowable = new Flowable(subscriber => {
		var workerPromise = null;
		var onRequest = n => {
			if (n <= 0 || workerPromise != null)
				return;
			workerPromise = getWorkerPromise(payload);
			workerPromise.then(v => {
				subscriber.onNext({
					data: objectToBuffer(v),
					metadata: null, // or new Buffer(...)
				});
				subscriber.onComplete();
			}, e => {
				subscriber.onError(logError(e));
			});
		};
		var onCancel = () => {
			if (workerPromise != null)
				workerPromise.terminate()
		};
		subscriber.onSubscribe({
			request: onRequest,
			cancel: onCancel
		});
	});
	return flowable;
}

const getRequestHandler = (requestingRSocket, setupPayload) => {
	//console.log(`setupPayload.data:${bufferToString(setupPayload.data)} setupPayload.metadata:${bufferToString(setupPayload.metadata)}`)
	return {
		fireAndForget: fireAndForget,
		requestStream: requestStream
	};
}

const rSocketServer = new RSocketServer({
	transport,
	getRequestHandler
});

rSocketServer.start();
console.log(`rSocket node server started on port ${port}`);

Other examples could include: chat, stock ticker, etc