UNPKG

rsocket-rxjs

Version:
305 lines (304 loc) 16.3 kB
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); }); } }