@@ -140,7 +140,15 @@ function isSerializedBuffer(obj: any): obj is SerializedBuffer {
140140}
141141
142142export interface RequestHandler extends RequestHandlerDefinition {
143- handle ( request : OngoingRequest , response : OngoingResponse ) : Promise < void > ;
143+ handle (
144+ request : OngoingRequest ,
145+ response : OngoingResponse ,
146+ options : RequestHandlerOptions
147+ ) : Promise < void > ;
148+ }
149+
150+ export interface RequestHandlerOptions {
151+ emitEventCallback ?: ( type : string , event : unknown ) => void ;
144152}
145153
146154export class SimpleHandler extends SimpleHandlerDefinition {
@@ -380,7 +388,11 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
380388 return this . _trustedCACertificates ;
381389 }
382390
383- async handle ( clientReq : OngoingRequest , clientRes : OngoingResponse ) {
391+ async handle (
392+ clientReq : OngoingRequest ,
393+ clientRes : OngoingResponse ,
394+ options : RequestHandlerOptions
395+ ) {
384396 // Don't let Node add any default standard headers - we want full control
385397 dropDefaultHeaders ( clientRes ) ;
386398
@@ -735,8 +747,24 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
735747 let serverStatusCode = serverRes . statusCode ! ;
736748 let serverStatusMessage = serverRes . statusMessage
737749 let serverRawHeaders = pairFlatRawHeaders ( serverRes . rawHeaders ) ;
750+
751+ // This is only set if we need to read the body here, for a callback or similar. If so,
752+ // we keep the buffer in case we need it afterwards (if the cb doesn't replace it).
753+ let originalBody : Buffer | undefined ;
754+
755+ // This is set when we override the body data. Note that this doesn't mean we actually
756+ // read & buffered the original data! With a fixed replacement body we can skip that.
738757 let resBodyOverride : Uint8Array | undefined ;
739758
759+ if ( options . emitEventCallback ) {
760+ options . emitEventCallback ( 'passthrough-response-head' , {
761+ statusCode : serverStatusCode ,
762+ statusMessage : serverStatusMessage ,
763+ httpVersion : serverRes . httpVersion ,
764+ rawHeaders : serverRawHeaders
765+ } ) ;
766+ }
767+
740768 if ( isH2Downstream ) {
741769 serverRawHeaders = h1HeadersToH2 ( serverRawHeaders ) ;
742770 }
@@ -775,8 +803,8 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
775803 } else if ( replaceBodyFromFile ) {
776804 resBodyOverride = await fs . readFile ( replaceBodyFromFile ) ;
777805 } else if ( updateJsonBody ) {
778- const rawBody = await streamToBuffer ( serverRes ) ;
779- const realBody = buildBodyReader ( rawBody , serverRes . headers ) ;
806+ originalBody = await streamToBuffer ( serverRes ) ;
807+ const realBody = buildBodyReader ( originalBody , serverRes . headers ) ;
780808
781809 if ( await realBody . getJson ( ) === undefined ) {
782810 throw new Error ( "Can't transform non-JSON response body" ) ;
@@ -795,26 +823,27 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
795823
796824 resBodyOverride = asBuffer ( JSON . stringify ( updatedBody ) ) ;
797825 } else if ( matchReplaceBody ) {
798- const rawBody = await streamToBuffer ( serverRes ) ;
799- const realBody = buildBodyReader ( rawBody , serverRes . headers ) ;
826+ originalBody = await streamToBuffer ( serverRes ) ;
827+ const realBody = buildBodyReader ( originalBody , serverRes . headers ) ;
800828
801- const originalBody = await realBody . getText ( ) ;
802- if ( originalBody === undefined ) {
829+ const originalBodyText = await realBody . getText ( ) ;
830+ if ( originalBodyText === undefined ) {
803831 throw new Error ( "Can't match & replace non-decodeable response body" ) ;
804832 }
805833
806- let replacedBody = originalBody ;
834+ let replacedBody = originalBodyText ;
807835 for ( let [ match , result ] of matchReplaceBody ) {
808836 replacedBody = replacedBody ! . replace ( match , result ) ;
809837 }
810838
811- if ( replacedBody !== originalBody ) {
839+ if ( replacedBody !== originalBodyText ) {
812840 resBodyOverride = asBuffer ( replacedBody ) ;
813841 }
814842 }
815843
816844 if ( resBodyOverride ) {
817- // We always re-encode the body to match the resulting content-encoding header:
845+ // In the above cases, the overriding data is assumed to always be in decoded form,
846+ // so we re-encode the body to match the resulting content-encoding header:
818847 resBodyOverride = await encodeBodyBuffer (
819848 resBodyOverride ,
820849 serverHeaders
@@ -833,9 +862,8 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
833862 serverRawHeaders = objectHeadersToRaw ( serverHeaders ) ;
834863 } else if ( this . beforeResponse ) {
835864 let modifiedRes : CallbackResponseResult | void ;
836- let body : Buffer ;
837865
838- body = await streamToBuffer ( serverRes ) ;
866+ originalBody = await streamToBuffer ( serverRes ) ;
839867 let serverHeaders = rawHeadersToObject ( serverRawHeaders ) ;
840868
841869 modifiedRes = await this . beforeResponse ( {
@@ -844,19 +872,14 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
844872 statusMessage : serverRes . statusMessage ,
845873 headers : serverHeaders ,
846874 rawHeaders : _ . cloneDeep ( serverRawHeaders ) ,
847- body : buildBodyReader ( body , serverHeaders )
875+ body : buildBodyReader ( originalBody , serverHeaders )
848876 } ) ;
849877
850878 if ( modifiedRes === 'close' ) {
851- // Dump the real response data and kill the client socket:
852- serverRes . resume ( ) ;
853879 ( clientReq as any ) . socket . end ( ) ;
854880 throw new AbortError ( 'Connection closed intentionally by rule' ) ;
855881 } else if ( modifiedRes === 'reset' ) {
856882 requireSocketResetSupport ( ) ;
857-
858- // Dump the real response data and kill the client socket:
859- serverRes . resume ( ) ;
860883 resetOrDestroy ( clientReq ) ;
861884 throw new AbortError ( 'Connection reset intentionally by rule' ) ;
862885 }
@@ -880,11 +903,6 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
880903 modifiedRes ?. headers ,
881904 method === 'HEAD' // HEAD responses are allowed mismatched content-length
882905 ) ;
883- } else {
884- // If you don't specify a body override, we need to use the real
885- // body anyway, because as we've read it already streaming it to
886- // the response won't work
887- resBodyOverride = body ;
888906 }
889907
890908 serverRawHeaders = objectHeadersToRaw ( serverHeaders ) ;
@@ -910,14 +928,40 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
910928 if ( resBodyOverride ) {
911929 // Return the override data to the client:
912930 clientRes . end ( resBodyOverride ) ;
913- // Dump the real response data:
914- serverRes . resume ( ) ;
915931
932+ // Dump the real response data, in case that body wasn't read yet:
933+ serverRes . resume ( ) ;
934+ resolve ( ) ;
935+ } else if ( originalBody ) {
936+ // If the original body was read, and not overridden, then send it
937+ // onward directly:
938+ clientRes . end ( originalBody ) ;
916939 resolve ( ) ;
917940 } else {
941+ // Otherwise the body hasn't been read - stream it live:
918942 serverRes . pipe ( clientRes ) ;
919943 serverRes . once ( 'end' , resolve ) ;
920944 }
945+
946+ if ( options . emitEventCallback ) {
947+ if ( ! ! resBodyOverride ) {
948+ ( originalBody
949+ ? Promise . resolve ( originalBody )
950+ : streamToBuffer ( serverRes )
951+ ) . then ( ( upstreamBody ) => {
952+ options . emitEventCallback ! ( 'passthrough-response-body' , {
953+ overridden : true ,
954+ rawBody : upstreamBody
955+ } ) ;
956+ } ) ;
957+ } else {
958+ options . emitEventCallback ( 'passthrough-response-body' , {
959+ overridden : false
960+ // We don't bother buffering & re-sending the body if
961+ // it's the same as the one being sent to the client.
962+ } ) ;
963+ }
964+ }
921965 } ) ( ) . catch ( reject ) ) ;
922966
923967 serverReq . once ( 'socket' , ( socket : net . Socket ) => {
@@ -979,6 +1023,30 @@ export class PassThroughHandler extends PassThroughHandlerDefinition {
9791023
9801024 // For similar reasons, we don't want any buffering on outgoing data at all if possible:
9811025 serverReq . setNoDelay ( true ) ;
1026+
1027+ // Fire rule events, to allow in-depth debugging of upstream traffic & modifications,
1028+ // so anybody interested can see _exactly_ what we're sending upstream here:
1029+ if ( options . emitEventCallback ) {
1030+ options . emitEventCallback ( 'passthrough-request-head' , {
1031+ method,
1032+ protocol : protocol ! . replace ( / : $ / , '' ) ,
1033+ hostname,
1034+ port,
1035+ path,
1036+ rawHeaders
1037+ } ) ;
1038+
1039+ if ( ! ! reqBodyOverride ) {
1040+ options . emitEventCallback ( 'passthrough-request-body' , {
1041+ overridden : true ,
1042+ rawBody : reqBodyOverride
1043+ } ) ;
1044+ } else {
1045+ options . emitEventCallback ! ( 'passthrough-request-body' , {
1046+ overridden : false
1047+ } ) ;
1048+ }
1049+ }
9821050 } ) ( ) . catch ( reject )
9831051 ) . catch ( ( e : ErrorLike ) => {
9841052 // All errors anywhere above (thrown or from explicit reject()) should end up here.
0 commit comments