@@ -20,6 +20,7 @@ interface ConnectionInfo {
2020interface  ActiveConnection  extends  ConnectionInfo  { 
2121  readonly  clientSocket : net . Socket ; 
2222  readonly  serverSocket : net . Socket ; 
23+   inflightRequestsCount : number 
2324} 
2425
2526type  SendResult  = 
@@ -49,11 +50,16 @@ interface ProxyEvents {
4950  'close' : ( )  =>  void ; 
5051} 
5152
53+ export  type  Interceptor  =  ( data : Buffer )  =>  Promise < Buffer > ; 
54+ export  type  InterceptorFunction  =  ( data : Buffer ,  next : Interceptor )  =>  Promise < Buffer > ; 
55+ type  InterceptorInitializer  =  ( init : Interceptor )  =>  Interceptor ; 
56+ 
5257export  class  RedisProxy  extends  EventEmitter  { 
5358  private  readonly  server : net . Server ; 
5459  public  readonly  config : Required < ProxyConfig > ; 
5560  private  readonly  connections : Map < string ,  ActiveConnection > ; 
5661  private  isRunning : boolean ; 
62+   private  interceptorInitializer ?: InterceptorInitializer ; 
5763
5864  constructor ( config : ProxyConfig )  { 
5965    super ( ) ; 
@@ -113,6 +119,13 @@ export class RedisProxy extends EventEmitter {
113119    } ) ; 
114120  } 
115121
122+   public  setInterceptors ( interceptors : Array < InterceptorFunction > )  { 
123+     this . interceptorInitializer  =  ( init )  =>  interceptors . reduceRight < Interceptor > ( 
124+       ( next ,  mw )  =>  ( data )  =>  mw ( data ,  next ) , 
125+       init 
126+     ) ; 
127+   } 
128+ 
116129  public  getStats ( ) : ProxyStats  { 
117130    const  connections  =  Array . from ( this . connections . values ( ) ) ; 
118131
@@ -218,19 +231,22 @@ export class RedisProxy extends EventEmitter {
218231  } 
219232
220233  private  handleClientConnection ( clientSocket : net . Socket ) : void { 
221-     const   connectionId   =   this . generateConnectionId ( ) ; 
234+     clientSocket . pause ( ) ; 
222235    const  serverSocket  =  net . createConnection ( { 
223236      host : this . config . targetHost , 
224237      port : this . config . targetPort 
225238    } ) ; 
239+     serverSocket . once ( 'connect' ,  clientSocket . resume . bind ( clientSocket ) ) ; 
226240
241+     const  connectionId  =  this . generateConnectionId ( ) ; 
227242    const  connectionInfo : ActiveConnection  =  { 
228243      id : connectionId , 
229244      clientAddress : clientSocket . remoteAddress  ||  'unknown' , 
230245      clientPort : clientSocket . remotePort  ||  0 , 
231246      connectedAt : new  Date ( ) , 
232247      clientSocket, 
233-       serverSocket
248+       serverSocket, 
249+       inflightRequestsCount : 0 
234250    } ; 
235251
236252    this . connections . set ( connectionId ,  connectionInfo ) ; 
@@ -243,12 +259,38 @@ export class RedisProxy extends EventEmitter {
243259      this . emit ( 'connection' ,  connectionInfo ) ; 
244260    } ) ; 
245261
246-     clientSocket . on ( 'data' ,  ( data )  =>  { 
262+     clientSocket . on ( 'data' ,  async   ( data )  =>  { 
247263      this . emit ( 'data' ,  connectionId ,  'client->server' ,  data ) ; 
248-       serverSocket . write ( data ) ; 
264+ 
265+       if ( ! this . interceptorInitializer )  { 
266+         serverSocket . write ( data ) ; 
267+         return ; 
268+       } 
269+ 
270+       connectionInfo . inflightRequestsCount ++ ; 
271+ 
272+       // next1 -> next2 -> ... -> last -> server 
273+       // next1 <- next2 <- ... <- last <- server 
274+       const  last  =  ( data : Buffer ) : Promise < Buffer >  =>  { 
275+         return  new  Promise ( ( resolve ,  reject )  =>  { 
276+           serverSocket . write ( data ) ; 
277+           serverSocket . once ( 'data' ,  ( data )  =>  { 
278+             connectionInfo . inflightRequestsCount -- ; 
279+             assert ( connectionInfo . inflightRequestsCount  >=  0 ,  `inflightRequestsCount for connection ${ connectionId }  ) ; 
280+             this . emit ( 'data' ,  connectionId ,  'server->client' ,  data ) ; 
281+             resolve ( data ) ; 
282+           } ) ; 
283+           serverSocket . once ( 'error' ,  reject ) ; 
284+         } ) ; 
285+       } ; 
286+ 
287+       const  interceptorChain  =  this . interceptorInitializer ( last ) ; 
288+       const  response  =  await  interceptorChain ( data ) ; 
289+       clientSocket . write ( response ) ; 
249290    } ) ; 
250291
251292    serverSocket . on ( 'data' ,  ( data )  =>  { 
293+       if  ( connectionInfo . inflightRequestsCount  >  0 )  return ; 
252294      this . emit ( 'data' ,  connectionId ,  'server->client' ,  data ) ; 
253295      clientSocket . write ( data ) ; 
254296    } ) ; 
@@ -273,6 +315,7 @@ export class RedisProxy extends EventEmitter {
273315    } ) ; 
274316
275317    serverSocket . on ( 'error' ,  ( error )  =>  { 
318+       if  ( connectionInfo . inflightRequestsCount  >  0 )  return ; 
276319      this . log ( `Server error for connection ${ connectionId } ${ error . message }  ) ; 
277320      this . emit ( 'error' ,  error ,  connectionId ) ; 
278321      clientSocket . destroy ( ) ; 
@@ -306,6 +349,7 @@ export class RedisProxy extends EventEmitter {
306349  } 
307350} 
308351import  {  createServer  }  from  'net' ; 
352+ import  assert  from  'node:assert' ; 
309353
310354export  function  getFreePortNumber ( ) : Promise < number >  { 
311355  return  new  Promise ( ( resolve ,  reject )  =>  { 
@@ -326,4 +370,3 @@ export function getFreePortNumber(): Promise<number> {
326370
327371export  {  RedisProxy  as  RedisTransparentProxy  } ; 
328372export  type  {  ProxyConfig ,  ConnectionInfo ,  ProxyEvents ,  SendResult ,  DataDirection ,  ProxyStats  } ; 
329- 
0 commit comments