import type * as https from 'https' ; import type * as express from 'express' ; import type { Filter , Request , RequestHandler , Response , Options } from './types' ; import * as httpProxy from 'http-proxy' ; import { createConfig , Config } from './config-factory' ; import * as contextMatcher from './context-matcher' ; import * as handlers from './_handlers' ; import { getArrow , getInstance } from './logger' ; import * as PathRewriter from './path-rewriter' ; import * as Router from './router' ; export class HttpProxyMiddleware { private logger = getInstance ( ) ; private config : Config ; private wsInternalSubscribed = false ; private serverOnCloseSubscribed = false ; private proxyOptions : Options ; private proxy : httpProxy ; private pathRewriter ; constructor ( context , opts ? ) { this . config = createConfig ( context , opts ) ; this . proxyOptions = this . config . options ; this . proxy = httpProxy . createProxyServer ( { } ) ; this . logger . info ( `[HPM] Proxy created: ${ this . config . context }  -> ${ this . proxyOptions . target }` ) ; this . pathRewriter = PathRewriter . createPathRewriter ( this . proxyOptions . pathRewrite ) ; handlers . init ( this . proxy , this . proxyOptions ) ; this . proxy . on ( 'error' , this . logError ) ; ( this . middleware as any ) . upgrade = ( req , socket , head ) => { if ( ! this . wsInternalSubscribed ) { this . handleUpgrade ( req , socket , head ) ; } } ; } public middleware : RequestHandler = async ( req , res , next ) => { if ( this . shouldProxy ( this . config . context , req ) ) { try { const activeProxyOptions = await this . prepareProxyRequest ( req ) ; this . proxy . web ( req , res , activeProxyOptions ) ; } catch ( err ) { next ( err ) ; } } else { next ( ) ; } /**
         * Get the server object to subscribe to server events;
         * 'upgrade' for websocket and 'close' for graceful shutdown
         *
         * NOTE:
         * req.socket: node >= 13
         * req.connection: node < 13 (Remove this when node 12/13 support is dropped)
         */ const server = ( ( req . socket ?? req . connection ) as any ) ?. server ; if ( server && ! this . serverOnCloseSubscribed ) { server . on ( 'close' , ( ) => { this . logger . info ( '[HPM] server close signal received: closing proxy server' ) ; this . proxy . close ( ) ; } ) ; this . serverOnCloseSubscribed = true ; } if ( this . proxyOptions . ws === true ) { this . catchUpgradeRequest ( server ) ; } } ; private catchUpgradeRequest = ( server ) => { if ( ! this . wsInternalSubscribed ) { server . on ( 'upgrade' , this . handleUpgrade ) ; this . wsInternalSubscribed = true ; } } ; private handleUpgrade = async ( req , socket , head ) => { if ( this . shouldProxy ( this . config . context , req ) ) { const activeProxyOptions = await this . prepareProxyRequest ( req ) ; this . proxy . ws ( req , socket , head , activeProxyOptions ) ; this . logger . info ( '[HPM] Upgrading to WebSocket' ) ; } } ; private param { String } context param { Object } req return { Boolean } private shouldProxy = ( context , req ) : boolean => { const path = req . originalUrl || req . url ; return contextMatcher . match ( context , path , req ) ; } ; param { Object } req return { Object } private prepareProxyRequest = async ( req ) => { req . url = req . originalUrl || req . url ; const originalPath = req . url ; const newProxyOptions = Object . assign ( { } , this . proxyOptions ) ; await this . applyRouter ( req , newProxyOptions ) ; await this . applyPathRewrite ( req , this . pathRewriter ) ; if ( this . proxyOptions . logLevel === 'debug' ) { const arrow = getArrow ( originalPath , req . url , this . proxyOptions . target , newProxyOptions . target ) ; this . logger . debug ( '[HPM] %s %s %s %s' , req . method , originalPath , arrow , newProxyOptions . target ) ; } return newProxyOptions ; } ; private applyRouter = async ( req , options ) => { let newTarget ; if ( options . router ) { newTarget = await Router . getTarget ( req , options ) ; if ( newTarget ) { this . logger . debug ( '[HPM] Router new target: %s -> "%s"' , options . target , newTarget ) ; options . target = newTarget ; } } } ; private applyPathRewrite = async ( req , pathRewriter ) => { if ( pathRewriter ) { const path = await pathRewriter ( req . url , req ) ; if ( typeof path === 'string' ) { req . url = path ; } else { this . logger . info ( '[HPM] pathRewrite: No rewritten path found. (%s)' , req . url ) ; } } } ; private logError = ( err , req , res , target ? ) => { const hostname = req . headers ?. host || req . hostname || req . host ; const requestHref = `${ hostname }${ req . url }` ; const targetHref = `${ target ?. href }` ; const errorMessage = '[HPM] Error occurred while proxying request %s to %s [%s] (%s)' ; const errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors' ; this . logger . error ( errorMessage , requestHref , targetHref , err . code || err , errReference ) ; } ; }