leejh ee1938799e [[중요중요!!!]]
Issue : 방이름 전체 변경시 같은 requestId 를 가지고 SSVC_TYPE_ROOM_UPD_REQ (10/31) 요청시 RES(10/32) 도착 전에 EVENT_SEND_RES (20/12) 가 먼저 도착함.
service Id 와 sub service id 를 같이 체크하면 좋지만 우선 service ID 를 같이 체크하는 로직을 넣어두고, 서버에서 순서를 바꿔줄 수 없는지 요청해 봄.

궁극적으로는 내가 요청한 건에 대한 응답 프로토콜에만 requestID 를 실어 보내줘야 하고, noti 나 noti 성격의 res 에는 request id 를 달아 보내주면 안됨.
더 궁극적으로는 noti 는 noti 로,, noti 성격의 res 를 noti 로 보낼 수 있도록 서버가 수정되어 줘야 함.
2019-11-06 14:06:25 +09:00

347 lines
9.6 KiB
TypeScript

import { Injectable, Inject } from '@angular/core';
import { Subscription, Observable, Subject } from 'rxjs';
import { share, switchMap, retryWhen, delay, finalize } from 'rxjs/operators';
import { QueueingSubject } from 'queueing-subject';
import {
makeWebSocketObservable,
GetWebSocketResponses,
NormalClosureMessage
} from '@ucap-webmessenger/web-socket';
import { _MODULE_CONFIG } from '../types/token';
import { ModuleConfig } from '../types/module-config';
import { PacketBody } from '../protocols/packet';
import {
PacketBodyValueDivider,
PacketBodyDivider
} from '../types/packet-body-divider';
import { PacketBodyValue } from '../types/packet-body-value.type';
import { SSVC_TYPE_ERROR_RES, ServerErrorCode } from '../types/service';
import { ProtocolMessage } from '../protocols/protocol';
import { NGXLogger } from 'ngx-logger';
interface RequestState {
subject: Subject<ProtocolMessage>;
request: {
serviceType: number;
subServiceType: number;
bodyList: PacketBody[];
};
}
@Injectable({
providedIn: 'root'
})
export class ProtocolService {
// tslint:disable-next-line: variable-name
private _requestId: number | null = null;
private readonly pendingRequests: Map<number, RequestState>;
private readonly input$: QueueingSubject<string>;
private socket$: Observable<GetWebSocketResponses<any>>;
private messages$: Observable<any>;
private messagesSubscription: Subscription | null = null;
private serverMessageSubject: Subject<ProtocolMessage> | null = null;
private serverMessage$: Observable<ProtocolMessage> | null = null;
constructor(
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
private logger: NGXLogger
) {
this.pendingRequests = new Map();
this.input$ = new QueueingSubject<string>();
this.serverMessageSubject = new Subject();
this.serverMessage$ = this.serverMessageSubject
.asObservable()
.pipe(share());
}
public connect(serverIp: string | null = null): Observable<void> {
return new Observable<void>(subscriber => {
try {
this.socket$ = makeWebSocketObservable(
`${this.moduleConfig.urls.base}${serverIp ? serverIp : ''}`
);
this.messages$ = this.socket$.pipe(
switchMap(getResponses => {
subscriber.next();
return getResponses(this.input$);
}),
retryWhen(errors =>
errors.pipe(delay(this.moduleConfig.reconnect.delay))
),
share()
);
this.messagesSubscription = this.messages$.subscribe(
(message: string) => {
const arg = message.split(PacketBodyDivider);
if (2 > arg.length) {
// OnError(3);
return;
}
const res = this.decodePacket(arg);
let requestState: RequestState | null = null;
if (res.requestId) {
requestState = this.pendingRequests.get(res.requestId);
}
if (SSVC_TYPE_ERROR_RES === res.message.subServiceType) {
const errorCode: ServerErrorCode = res.message
.bodyList[0] as ServerErrorCode;
if (requestState) {
requestState.subject.error(errorCode);
}
return;
}
if (
requestState &&
requestState.request.serviceType === res.message.serviceType
) {
requestState.subject.next(res.message);
return;
}
this.serverMessageSubject.next(res.message);
},
(error: Error) => {
const { message } = error;
if (message === NormalClosureMessage) {
this.logger.info(
'server closed the websocket connection normally'
);
} else {
this.logger.error(
'socket was disconnected due to error:',
message
);
}
},
() => {
// The clean termination only happens in response to the last
// subscription to the observable being unsubscribed, any
// other closure is considered an error.
this.logger.info(
'the connection was closed in response to the user'
);
}
);
} catch (error) {
subscriber.error(error);
}
});
}
public disconnect(): void {
this.messagesSubscription.unsubscribe();
}
public get serverMessage(): Observable<ProtocolMessage> {
return this.serverMessage$;
}
public call(
serviceType: number | null,
subServiceType: number | null,
...bodyList: PacketBody[]
): Observable<ProtocolMessage> {
return this.sendInternal(true, serviceType, subServiceType, bodyList);
}
public send(
serviceType: number,
subServiceType: number,
...bodyList: PacketBody[]
): void {
this.sendInternal(false, serviceType, subServiceType, bodyList);
}
private sendInternal(
hasResponse: boolean,
serviceType: number,
subServiceType: number,
bodyList: PacketBody[]
): Observable<ProtocolMessage> | undefined {
let packet: string;
let responseSubject: Subject<ProtocolMessage> | null = null;
if (hasResponse) {
const requestId = this.requestId;
packet = this.encodePacket(serviceType, subServiceType, [
...bodyList,
{ type: PacketBodyValue.RequestId, value: requestId }
]);
responseSubject = new Subject<ProtocolMessage>().pipe(
finalize(() => {
if (this.pendingRequests.has(requestId)) {
this.pendingRequests.delete(requestId);
}
this.logger.debug(
'ProtocolService::pendingRequests.size',
this.pendingRequests.size
);
})
) as Subject<ProtocolMessage>;
this.pendingRequests.set(requestId, {
subject: responseSubject,
request: { serviceType, subServiceType, bodyList }
});
} else {
packet = this.encodePacket(serviceType, subServiceType, bodyList);
}
this.input$.next(packet);
return responseSubject ? responseSubject.asObservable() : undefined;
}
private get requestId() {
if (
null === this._requestId ||
this.moduleConfig.requestId.max < this._requestId
) {
this._requestId = this.moduleConfig.requestId.min;
}
return this._requestId++;
}
private decodePacket(
arg: string[]
): { requestId: number; message: ProtocolMessage } | null {
const cmdArg = arg[0].split(PacketBodyValueDivider);
if (2 > cmdArg.length) {
// OnError(3);
return null;
}
const serviceType = Number(cmdArg[0]);
const subServiceType = Number(cmdArg[1]);
const seqArg = arg[1].split(PacketBodyValueDivider);
if (2 > seqArg.length) {
// OnError(3);
return null;
}
const senderSeq = Number(seqArg[0]);
const bodyList: any[] = [];
let requestId: number | null = null;
for (let i = 2; i < arg.length; i++) {
const bodyArg = arg[i].split(PacketBodyValueDivider);
if (2 > bodyArg.length) {
// OnError(3);
return null;
}
const valueType: PacketBodyValue = bodyArg[0] as PacketBodyValue;
const value = bodyArg[1];
switch (valueType) {
case PacketBodyValue.None:
bodyList.push(value);
break;
case PacketBodyValue.Binary:
bodyList.push(value);
break;
case PacketBodyValue.Integer:
bodyList.push(Number(value));
break;
case PacketBodyValue.String:
bodyList.push(value);
break;
case PacketBodyValue.EncodedString:
bodyList.push(value);
break;
case PacketBodyValue.MultiByteString:
bodyList.push(value);
break;
case PacketBodyValue.WideString:
bodyList.push(value);
break;
case PacketBodyValue.IuId:
bodyList.push(value);
break;
case PacketBodyValue.RequestId:
requestId = parseInt(value, 10);
break;
default:
break;
}
}
return {
requestId,
message: {
serviceType,
subServiceType,
senderSeq,
bodyList
}
};
}
private encodePacket(
serviceType: number | null,
subServiceType: number | null,
bodyList: PacketBody[] = []
): string {
const packet: string[] = [];
packet.push(serviceType ? serviceType.toString() : '0');
packet.push(PacketBodyValueDivider);
packet.push(subServiceType ? subServiceType.toString() : '0');
packet.push(PacketBodyDivider);
for (const body of bodyList) {
packet.push(body.type);
packet.push(PacketBodyValueDivider);
switch (body.type) {
case PacketBodyValue.None:
packet.push(body.value);
break;
case PacketBodyValue.Binary:
packet.push(body.value);
break;
case PacketBodyValue.Integer:
packet.push(String(body.value));
break;
case PacketBodyValue.String:
packet.push(body.value);
break;
case PacketBodyValue.EncodedString:
packet.push(body.value);
break;
case PacketBodyValue.MultiByteString:
packet.push(body.value);
break;
case PacketBodyValue.WideString:
packet.push(body.value);
break;
case PacketBodyValue.IuId:
packet.push(body.value);
break;
case PacketBodyValue.RequestId:
packet.push(String(body.value));
break;
default:
break;
}
packet.push(PacketBodyDivider);
}
return packet.join('');
}
}