import debug from 'debug' ; import { Socket } from 'net' ; import Hessian from 'hessian.js' ; import { util } from 'apache-dubbo-common' ; import { DUBBO_FLAG_REQUEST , DUBBO_FLAG_TWOWAY , DUBBO_HEADER_LENGTH , DUBBO_FLAG_EVENT , HESSIAN2_SERIALIZATION_CONTENT_ID , DUBBO_MAGIC_HIGH , DUBBO_MAGIC_LOW } from './constants' ; import { IHeartBeatProps } from './types' ; const log = debug ( 'dubbo:heartbeat' ) ; const HEART_BEAT = 60 * 1000 ; const RETRY_HEARD_BEAT_TIME = 3 ; /**
 * Heartbeat Manager
 */ export default class HeartBeat { private readonly type : 'request' | 'response' ; private readonly onTimeout : Function ; private transport : Socket ; private heartBeatTimer : NodeJS . Timer ; private lastReadTimestamp : number = - 1 ; private lastWriteTimestamp : number = - 1 ; constructor ( props ) { const { transport , onTimeout , type } = props ; this . type = type ; this . transport = transport ; this . onTimeout = onTimeout || util . noop ; const who = this . type === 'request' ? 'dubbo-consumer' : 'dubbo-server' ; log ( '%s init heartbeat manager' , who ) ; this . init ( ) ; } private init = ( ) => { this . setWriteTimestamp ( ) ; this . setReadTimestamp ( ) ; this . heartBeatTimer = setInterval ( ( ) => { const now = Date . now ( ) ; if ( now - this . lastReadTimestamp > HEART_BEAT * RETRY_HEARD_BEAT_TIME ) { this . onTimeout ( ) ; } else if ( now - this . lastWriteTimestamp > HEART_BEAT || now - this . lastReadTimestamp > HEART_BEAT ) { this . emit ( ) ; } } , HEART_BEAT ) ; this . transport . on ( 'data' , ( ) => { this . setReadTimestamp ( ) ; } ) . on ( 'close' , ( ) => { this . destroy ( ) ; } ) ; } ; emit ( ) { const who = this . type === 'request' ? 'dubbo-consumer' : 'dubbo-server' ; log ( `${ who } emit heartbeat` ) ; this . setWriteTimestamp ( ) ; this . transport . write ( this . encode ( ) ) ; } private destroy = ( ) => { clearTimeout ( this . heartBeatTimer ) ; this . transport = null ; this . lastReadTimestamp = - 1 ; this . lastWriteTimestamp = - 1 ; } ; setReadTimestamp ( ) { this . lastReadTimestamp = Date . now ( ) ; } setWriteTimestamp ( ) { this . lastWriteTimestamp = Date . now ( ) ; } static from ( props ) { return new HeartBeat ( props ) ; } /**
     * encode heartbeat
     */ encode ( ) { const who = this . type === 'request' ? 'dubbo-consumer' : 'dubbo-server' ; log ( '%s encode heartbeat' , who ) ; const buffer = Buffer . alloc ( DUBBO_HEADER_LENGTH + 1 ) ; buffer [ 0 ] = DUBBO_MAGIC_HIGH ; buffer [ 1 ] = DUBBO_MAGIC_LOW ; if ( this . type === 'request' ) { buffer [ 2 ] = DUBBO_FLAG_REQUEST | HESSIAN2_SERIALIZATION_CONTENT_ID | DUBBO_FLAG_TWOWAY | DUBBO_FLAG_EVENT ; } else if ( this . type === 'response' ) { buffer [ 2 ] = HESSIAN2_SERIALIZATION_CONTENT_ID | DUBBO_FLAG_TWOWAY | DUBBO_FLAG_EVENT ; } buffer [ 15 ] = 1 ; buffer [ 16 ] = 0x4e ; return buffer ; } static isHeartBeat ( buf ) { const flag = buf [ 2 ] ; if ( ( flag & DUBBO_FLAG_EVENT ) !== 0 ) { const decoder = new Hessian . DecoderV2 ( buf . slice ( DUBBO_HEADER_LENGTH ) ) ; const data = decoder . read ( ) ; return data === null ; } return false ; } }