import debug from 'debug' ; import Hessian from 'hessian.js' ; import { byte , util } from 'apache-dubbo-common' ; import { DubboEncodeError } from './err' ; import { DUBBO_MAGIC_HEADER , DUBBO_FLAG_REQUEST , DUBBO_FLAG_TWOWAY , DUBBO_HEADER_LENGTH , DUBBO_DEFAULT_PAY_LOAD , HESSIAN2_SERIALIZATION_ID , HESSIAN2_SERIALIZATION_CONTENT_ID , DUBBO_RESPONSE_BODY_FLAG , DUBBO_MAGIC_HIGH , DUBBO_MAGIC_LOW , DUBBO_RESPONSE_STATUS } from './constants' ; import { IRequestContext , IResponseContext } from './types' ; const log = debug ( 'dubbo:hessian:encoderV2' ) ; const checkPayload = ( payload ) => { if ( payload > 0 && payload > DUBBO_DEFAULT_PAY_LOAD ) { throw new DubboEncodeError ( `Data length too large: ${ payload }, max payload: ${ DUBBO_DEFAULT_PAY_LOAD }` ) ; } } ; export class DubboRequestEncoder { private readonly ctx : IRequestContext ; constructor ( ctx ) { this . ctx = ctx ; log ( 'dubbo encode param request:%s' , JSON . stringify ( this . ctx . request , null , 2 ) ) ; } encode ( ) { const body = this . encodeBody ( ) ; const head = this . encodeHead ( body . length ) ; log ( `encode body length: ${ body . length } bytes` ) ; return Buffer . concat ( [ head , body ] ) ; } param payload private encodeHead ( payload ) { const header = Buffer . alloc ( DUBBO_HEADER_LENGTH ) ; header [ 0 ] = DUBBO_MAGIC_HEADER >>> 8 ; header [ 1 ] = DUBBO_MAGIC_HEADER & 0xff ; header [ 2 ] = DUBBO_FLAG_REQUEST | HESSIAN2_SERIALIZATION_CONTENT_ID | DUBBO_FLAG_TWOWAY ; this . setRequestId ( header ) ; header . writeUInt32BE ( payload , 12 ) ; return header ; } private setRequestId ( header ) { const { requestId } = this . ctx ; log ( `encode header requestId: ${ requestId }` ) ; const buffer = byte . toBytes8 ( requestId ) ; header [ 4 ] = buffer [ 0 ] ; header [ 5 ] = buffer [ 1 ] ; header [ 6 ] = buffer [ 2 ] ; header [ 7 ] = buffer [ 3 ] ; header [ 8 ] = buffer [ 4 ] ; header [ 9 ] = buffer [ 5 ] ; header [ 10 ] = buffer [ 6 ] ; header [ 11 ] = buffer [ 7 ] ; } private encodeBody ( ) { const encoder = new Hessian . EncoderV2 ( ) ; const { dubboVersion , dubboInterface , version , methodName , methodArgs } = this . ctx ; encoder . write ( dubboVersion ) ; encoder . write ( dubboInterface ) ; encoder . write ( version ) ; encoder . write ( methodName ) ; if ( this . ctx . isSupportedDubbox ) { encoder . write ( - 1 ) ; } encoder . write ( DubboRequestEncoder . getParameterTypes ( methodArgs ) ) ; if ( methodArgs && methodArgs . length ) { for ( let arg of methodArgs ) { encoder . write ( arg ) ; } } encoder . write ( this . getAttachments ( ) ) ; checkPayload ( encoder . byteBuffer . _offset ) ; return encoder . byteBuffer . _bytes . slice ( 0 , encoder . byteBuffer . _offset ) ; } private static getParameterTypes ( args ) { if ( ! ( args && args . length ) ) { return '' ; } const primitiveTypeRef = { void : 'V' , boolean : 'Z' , byte : 'B' , char : 'C' , double : 'D' , float : 'F' , int : 'I' , long : 'J' , short : 'S' } ; const desc = [ ] ; for ( let arg of args ) { let type = arg [ '$class' ] ; while ( type [ 0 ] === '[' ) { desc . push ( '[' ) ; type = type . slice ( 1 ) ; } if ( primitiveTypeRef [ type ] ) { desc . push ( primitiveTypeRef [ type ] ) ; } else { desc . push ( 'L' ) ; desc . push ( type . replace ( /\./gi , '/' ) ) ; desc . push ( ';' ) ; } } return desc . join ( '' ) ; } private getAttachments ( ) { const { requestId , path , dubboInterface , group , timeout , version , application : { name } , attachments } = this . ctx ; const map = { ... { path : path , interface : dubboInterface , version : version || '0.0.0' } , ... attachments } ; group && ( map [ 'group' ] = group ) ; timeout && ( map [ 'timeout' ] = timeout ) ; name && ( map [ 'application' ] = name ) ; let attachmentsHashMap = { $class : 'java.util.HashMap' , $ : map } ; log ( 'request#%d attachment %s' , requestId , JSON . stringify ( attachmentsHashMap , null , 2 ) ) ; return attachmentsHashMap ; } } export class DubboResponseEncoder { private readonly ctx : IResponseContext ; constructor ( ctx ) { this . ctx = ctx ; } encode ( ) { const body = this . encodeBody ( ) ; const head = this . encodeHead ( body . length ) ; return Buffer . concat ( [ head , body ] ) ; } encodeHead ( payload ) { const header = Buffer . alloc ( DUBBO_HEADER_LENGTH ) ; header [ 0 ] = DUBBO_MAGIC_HIGH ; header [ 1 ] = DUBBO_MAGIC_LOW ; header [ 2 ] = HESSIAN2_SERIALIZATION_ID ; header [ 3 ] = this . ctx . status ; const reqIdBuf = byte . toBytes8 ( this . ctx . request . requestId ) ; header [ 4 ] = reqIdBuf [ 0 ] ; header [ 5 ] = reqIdBuf [ 1 ] ; header [ 6 ] = reqIdBuf [ 2 ] ; header [ 7 ] = reqIdBuf [ 3 ] ; header [ 8 ] = reqIdBuf [ 4 ] ; header [ 9 ] = reqIdBuf [ 5 ] ; header [ 10 ] = reqIdBuf [ 6 ] ; header [ 11 ] = reqIdBuf [ 7 ] ; header . writeUInt32BE ( payload , 12 ) ; return header ; } encodeBody ( ) { const encoder = new Hessian . EncoderV2 ( ) ; const isSupportAttachment = util . Version . isSupportResponseAttachment ( this . ctx . request . version ) ; if ( this . ctx . status !== DUBBO_RESPONSE_STATUS . OK ) { encoder . write ( `${ DUBBO_RESPONSE_STATUS [ this . ctx . status ] }#${ this . ctx . body . err . message }` ) ; } else { if ( this . ctx . body . err ) { encoder . write ( isSupportAttachment ? DUBBO_RESPONSE_BODY_FLAG . RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : DUBBO_RESPONSE_BODY_FLAG . RESPONSE_WITH_EXCEPTION ) ; encoder . write ( this . ctx . body . err . message ) ; } else if ( this . ctx . body . res ) { encoder . write ( isSupportAttachment ? DUBBO_RESPONSE_BODY_FLAG . RESPONSE_VALUE_WITH_ATTACHMENTS : DUBBO_RESPONSE_BODY_FLAG . RESPONSE_VALUE ) ; encoder . write ( this . ctx . body . res ) ; } else { encoder . write ( isSupportAttachment ? DUBBO_RESPONSE_BODY_FLAG . RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : DUBBO_RESPONSE_BODY_FLAG . RESPONSE_NULL_VALUE ) ; } } if ( isSupportAttachment ) { const attachments = this . ctx . attachments ; attachments [ 'dubbo' ] = '2.0.2' ; encoder . write ( attachments ) ; } try { checkPayload ( encoder . byteBuffer . _offset ) ; } catch ( err ) { encoder . clear ( ) ; encoder . write ( isSupportAttachment ? DUBBO_RESPONSE_BODY_FLAG . RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : DUBBO_RESPONSE_BODY_FLAG . RESPONSE_WITH_EXCEPTION ) ; encoder . write ( `${ DUBBO_RESPONSE_STATUS [ this . ctx . status ] }#${ this . ctx . body . err . message }` ) ; } return encoder . byteBuffer . _bytes . slice ( 0 , encoder . byteBuffer . _offset ) ; } }