rsocket-rxjs
Version:
RSocket Protocol Client Implementation
305 lines (304 loc) • 16.3 kB
JavaScript
import { BehaviorSubject, interval, merge, Notification, Observable, race, Subject, throwError } from "rxjs";
import { concatMap, delayWhen, dematerialize, filter, map, materialize, mergeMap, repeatWhen, skipWhile, switchMap, take, takeUntil, takeWhile, tap, timeout } from "rxjs/operators";
import { BackpressureStrategy, RSocketState } from '../api/rsocket.api';
import { factory } from "./config-log4j";
import { FragmentContext } from './protocol/fragments';
import { ErrorCode, FrameType } from "./protocol/frame";
import { FrameBuilder } from "./protocol/frame-factory";
const protocolLog = factory.getLogger('protocol.RSocketClient');
const log = factory.getLogger('.RSocketClient');
export class RSocketClient {
constructor(transport, responder, _config) {
this.transport = transport;
this.responder = responder;
this._config = _config;
this._state = new BehaviorSubject(RSocketState.Disconnected);
this._incoming = new Subject();
this.streamIdsHolder = [];
this.streamIdCounter = 0;
this.$destroy = new Subject();
this._closedByUser = false;
this.incomingHandlerSetup();
}
setSetupConfig(config) {
this._config = config;
}
getSetupConfig() {
return this._config;
}
state() {
return this._state;
}
establish() {
this.transport.incoming().pipe(takeUntil(this.$destroy)).subscribe({
next: n => this._incoming.next(n),
error: err => {
protocolLog.debug("Websocket signaled error: " + JSON.stringify(err));
this._state.next(RSocketState.Error);
this._state.next(RSocketState.Disconnected);
},
complete: () => {
protocolLog.debug("Websocket completed");
if (this._closedByUser == false) {
this._state.next(RSocketState.Error);
}
this._state.next(RSocketState.Disconnected);
}
});
const setupFrame = FrameBuilder.setup().buildFromConfig(this._config);
if (this._config.honorsLease == false) {
protocolLog.debug('Sending Setup frame without honoring lease...');
this.transport.send(setupFrame);
this._state.next(RSocketState.Connected);
}
this.setupKeepaliveSupport();
}
close() {
this._closedByUser = true;
this.$destroy.next(true);
return this.transport.close();
}
incomingHandlerSetup() {
this._incoming.pipe(filter(f => f.type() == FrameType.REQUEST_RESPONSE), mergeMap(f => this.incomingRequestResponse(f)), takeUntil(this.$destroy)).subscribe(frames => frames.forEach(frame => this.transport.send(frame), this));
this._incoming.pipe(filter(f => f.type() == FrameType.REQUEST_STREAM), mergeMap(f => this.incomingRequestStream(f)), takeUntil(this.$destroy)).subscribe(frames => frames.forEach(frame => this.transport.send(frame), this));
this._incoming.pipe(filter(f => f.type() == FrameType.REQUEST_FNF), takeUntil(this.$destroy)).subscribe(f => this.incomingRequestFNF(f));
}
requestResponse(payload) {
const obs = new Observable(emitter => {
protocolLog.debug("Executing Request Response");
const streamId = this.getNewStreamId();
const fragmentsContext = new FragmentContext();
const subscription = this._incoming.pipe(filter(f => f.streamId() == streamId)).subscribe(f => {
if (f.type() == FrameType.PAYLOAD) {
if (f.isNext()) {
fragmentsContext.add(f.payload());
if (f.fragmentFollows() == false) {
emitter.next(fragmentsContext.get());
}
}
if (f.isStreamComplete()) {
subscription.unsubscribe();
emitter.complete();
}
}
else if (f.type() == FrameType.ERROR) {
let message = "No Error message given.";
let messagePayload = f.payload();
if (messagePayload != undefined) {
if (messagePayload.data.byteLength > 0) {
message = String.fromCharCode.apply(null, new Uint8Array(messagePayload.data));
}
}
subscription.unsubscribe();
emitter.error(new Error(`Error: ${f.errorCode()}. Message: ${message}`));
}
else {
subscription.unsubscribe();
emitter.error(new Error('Unexpected frame type in request response interaction: ' + f.type()));
}
}, error => {
emitter.error(new Error('Unexpected error form transport. ' + error.message));
subscription.unsubscribe();
}, () => {
emitter.complete();
subscription.unsubscribe();
});
const sendContext = new FragmentContext();
FrameBuilder.requestResponse().streamId(streamId).payload(payload, this._config.fragmentSize).build().forEach(frame => this.transport.send(frame), this);
return () => {
if (subscription.closed == false) {
subscription.unsubscribe();
FrameBuilder.cancel().streamId(streamId).build().forEach(frame => this.transport.send(frame), this);
}
};
});
return this._state.pipe(filter(s => s == RSocketState.Connected), take(1), switchMap(s => race(obs, this.connectionFailedObservable())));
}
requestStream(payload, requester) {
const obs = new Observable(emitter => {
protocolLog.debug("Executing Request Stream");
const streamId = this.getNewStreamId();
const fragmentsContext = new FragmentContext();
const $requestDestroy = new Subject();
const subscription = this._incoming.pipe(takeUntil($requestDestroy), filter(f => f.streamId() == streamId)).subscribe(f => {
if (f.type() == FrameType.PAYLOAD) {
if (f.isNext()) {
fragmentsContext.add(f.payload());
if (f.fragmentFollows() == false) {
emitter.next(fragmentsContext.get());
}
}
if (f.isStreamComplete()) {
subscription.unsubscribe();
emitter.complete();
}
}
else if (f.type() == FrameType.ERROR) {
let message = "No Error message given.";
let messagePayload = f.payload();
if (messagePayload != undefined) {
if (messagePayload.data.byteLength > 0) {
message = String.fromCharCode.apply(null, new Uint8Array(messagePayload.data));
}
}
$requestDestroy.next(0);
emitter.error(new Error(`Error: ${f.errorCode()}. Message: ${message}`));
}
else {
$requestDestroy.next(0);
emitter.error(new Error('Unexpected frame type in request response interaction: ' + f.type()));
}
}, error => {
$requestDestroy.next(0);
emitter.error(new Error('Unexpected error form transport. ' + error.message));
}, () => {
$requestDestroy.next(0);
emitter.complete();
});
if (requester === undefined) {
FrameBuilder.requestStream().streamId(streamId).payload(payload, this._config.fragmentSize).requests(Math.pow(2, 31) - 1).build().forEach(frame => this.transport.send(frame), this);
}
else {
let initialRequest = true;
requester.pipe(takeUntil($requestDestroy)).subscribe(requests => {
if (initialRequest == true) {
initialRequest = false;
FrameBuilder.requestStream().streamId(streamId).payload(payload, this._config.fragmentSize).requests(requests).build().forEach(frame => this.transport.send(frame), this);
}
else {
FrameBuilder.requestN().streamId(streamId).requests(requests).build().forEach(frame => this.transport.send(frame), this);
}
});
}
return () => {
$requestDestroy.next(0);
FrameBuilder.cancel().streamId(streamId).build().forEach(frame => this.transport.send(frame), this);
};
});
return this._state.pipe(filter(s => s == RSocketState.Connected), take(1), mergeMap(s => merge(obs.pipe(materialize()), this.connectionFailedObservable().pipe(materialize()))), takeWhile(notification => notification.hasValue), dematerialize());
}
requestFNF(payload) {
this._state.pipe(filter(s => s == RSocketState.Connected), take(1)).subscribe(s => {
protocolLog.debug("Executing Request FNF");
const streamId = this.getNewStreamId();
FrameBuilder.requestFNF().streamId(streamId).payload(payload, this._config.fragmentSize).build().forEach(frame => this.transport.send(frame), this);
});
}
incomingRequestResponse(f) {
protocolLog.debug('Handling incoming Request Response request');
return this.responder.handleRequestResponse(f.payload()).pipe(takeUntil(this._incoming.pipe(filter(incFrame => incFrame.streamId() == f.streamId()), // Further checks are not required as the requester may not send other frames but cancel
tap(n => protocolLog.debug("Request Response has been canceled by requester. StreamdId: " + n.streamId())))), materialize(), map(p => {
switch (p.kind) {
case "N":
return Notification.createNext(FrameBuilder.payload().streamId(f.streamId()).payload(p.value, this._config.fragmentSize).flagComplete().flagNext().build());
case "E":
return Notification.createNext(FrameBuilder.error().errorCode(ErrorCode.APPLICATION_ERROR).message(p.error.message).streamId(f.streamId()).build());
case "C":
return Notification.createComplete();
}
}), dematerialize());
}
incomingRequestStream(f) {
protocolLog.debug('Handling incoming Request Stream request');
const handler = this.responder.handleRequestStream(f.payload());
let requests = f.initialRequests();
let pending = [];
const backpressureHonorer = new Observable(emitter => {
if (requests > 0) {
emitter.next(requests);
}
else {
pending.push(emitter);
}
});
const signalComplete = new Subject();
const streamCanceler = this._incoming.pipe(takeUntil(signalComplete), filter(incFrame => incFrame.streamId() == f.streamId()), filter(incFrame => incFrame.type() == FrameType.CANCEL), tap(n => protocolLog.debug("Request Response has been canceled by requester. StreamdId: " + n.streamId())));
this._incoming.pipe(takeUntil(streamCanceler), filter(incFrame => incFrame.streamId() == f.streamId()), filter(incFrame => incFrame.type() == FrameType.REQUEST_N), map(incFrame => incFrame.requests())).subscribe(n => {
while (n > 0) {
const req = pending.shift();
if (req == null) {
break;
}
req.next(0);
n--;
}
requests = n;
});
let backpressureHandler;
if (handler.backpressureStrategy == BackpressureStrategy.BufferDelay) {
backpressureHandler = delayWhen((v, id) => backpressureHonorer);
}
else if (handler.backpressureStrategy == BackpressureStrategy.Drop) {
backpressureHandler = skipWhile(p => requests == 0);
}
else {
backpressureHandler = map(a => a);
}
return handler.stream.pipe(backpressureHandler, takeUntil(streamCanceler), materialize(), map(p => {
switch (p.kind) {
case "N":
requests--;
return Notification.createNext(FrameBuilder.payload().streamId(f.streamId()).payload(p.value, this._config.fragmentSize).flagNext().build());
case "E":
signalComplete.next(0);
FrameBuilder.error().errorCode(ErrorCode.APPLICATION_ERROR).message(p.error.message).streamId(f.streamId()).build().forEach(frame => this.transport.send(frame), this);
return Notification.createComplete();
case "C":
signalComplete.next(0);
FrameBuilder.payload().streamId(f.streamId()).flagComplete().build().forEach(frame => this.transport.send(frame), this);
return Notification.createComplete();
}
}), dematerialize());
}
incomingRequestFNF(f) {
protocolLog.debug("Handling incoming FNF request");
this.responder.handleFNF(f.payload());
}
getNewStreamId() {
const i = this.streamIdCounter;
this.streamIdCounter++;
const id = i * 2 + 1;
this.streamIdsHolder.push(id);
return id;
}
connectionFailedObservable() {
return this._state.pipe(filter(s => s != RSocketState.Connected), concatMap(s => throwError(() => new Error("RSocket connection closed"))));
}
setupKeepaliveSupport() {
new Observable(emitter => {
const data = new Uint8Array(20);
for (let i = 0; i < 20; i++) {
data[i] = Math.floor(Math.random() * 100);
}
const sub = this._incoming.pipe(filter(p => p.type() == FrameType.KEEPALIVE && p.respondWithKeepalive() == false), filter(p => {
const returnData = new Uint8Array(p.payload().data);
if (returnData.length != data.length) {
protocolLog.warn("Keepalive answer length did not match");
return false;
}
else {
for (let i = 0; i < 20; i++) {
if (data[i] != returnData[i]) {
protocolLog.warn("Keepalive answer length data did not match");
return false;
}
}
protocolLog.debug("Keepalive answer received");
return true;
}
}), take(1), timeout(this._config.maxLifetime)).subscribe({
next: n => {
emitter.complete();
},
error: error => emitter.error(error),
});
const keepaliveFrame = FrameBuilder.keepalive().flagRespond().lastReceivedPosition(this.transport.recvPosition()).data(data).build();
keepaliveFrame.forEach(frame => this.transport.send(frame), this);
return () => sub.unsubscribe();
}).pipe(takeUntil(this.$destroy), repeatWhen(() => interval(this._config.keepaliveTime))).subscribe();
this._incoming.pipe(takeUntil(this.$destroy), filter(p => p.type() == FrameType.KEEPALIVE && p.respondWithKeepalive() == true)).subscribe(p => {
const keepaliveAnswer = FrameBuilder.keepalive().lastReceivedPosition(this.transport.recvPosition()).data(p.payload().data).build();
keepaliveAnswer.forEach(frame => this.transport.send(frame), this);
});
}
}