import * as http from 'http' ; import type { AddressInfo } from 'net' ; import { getIp } from '../../../utils' ; import { log } from './logger' ; const MAX_SERVER_CREATION_RETRY = 5 ; const PORT_MIN = 9200 ; const PORT_MAX = 9400 ; type ServerApp = ( req : http . IncomingMessage , res : http . ServerResponse ) => void ; export interface Server { url : string ; bindServerApp ( serverApp ) : void ; waitForIdle ( ) : Promise < void > ; } export interface Servers { base : Server ; intake : Server ; crossOrigin : Server ; } let serversSingleton ; export async function getTestServers ( ) { if ( ! serversSingleton ) { serversSingleton = { base : await createServer ( ) , crossOrigin : await createServer ( ) , intake : await createServer ( ) , } ; } return serversSingleton ; } export async function waitForServersIdle ( ) { const servers = await getTestServers ( ) ; return Promise . all ( [ servers . base . waitForIdle ( ) , servers . crossOrigin . waitForIdle ( ) , servers . intake . waitForIdle ( ) ] ) ; } async function createServer ( ) { const server = await instantiateServer ( ) ; const { address , port } = server . address ( ) as AddressInfo ; let serverApp ; server . on ( 'request' , ( req , res ) => { if ( serverApp ) { serverApp ( req , res ) ; } } ) ; server . on ( 'request' , ( req , res ) => { res . on ( 'close' , ( ) => { const requestUrl = `${ req . headers . host ! }${ req . url ! }` ; log ( `${ req . method ! } ${ requestUrl } ${ res . statusCode }` ) ; } ) ; } ) ; return { bindServerApp ( newServerApp ) { serverApp = newServerApp ; } , url : `http://${ address }:${ port }` , waitForIdle : createServerIdleWaiter ( server ) , } ; } async function instantiateServer ( ) { for ( let tryNumber = 0 ; tryNumber < MAX_SERVER_CREATION_RETRY ; tryNumber += 1 ) { const port = PORT_MIN + Math . floor ( Math . random ( ) * ( PORT_MAX - PORT_MIN + 1 ) ) ; try { return await instantiateServerOnPort ( port ) ; } catch ( error ) { if ( error . code === 'EADDRINUSE' ) { continue ; } throw error ; } } throw new Error ( `Failed to create a server after ${ MAX_SERVER_CREATION_RETRY } retries` ) ; } async function instantiateServerOnPort ( port ) { return new Promise ( ( resolve , reject ) => { const server = http . createServer ( ) ; server . on ( 'error' , reject ) ; server . listen ( port , getIp ( ) , ( ) => { resolve ( server ) ; } ) ; } ) ; } function createServerIdleWaiter ( server ) { const idleWaiter = createIdleWaiter ( ) ; server . on ( 'request' , ( _ , res ) => { idleWaiter . pushActivity ( new Promise ( ( resolve ) => res . on ( 'close' , resolve ) ) ) ; } ) ; return async ( ) => idleWaiter . idlePromise ; } const IDLE_WAIT_DURATION = 500 ; function createIdleWaiter ( ) { let idlePromise = Promise . resolve ( ) ; const pendingActivities = new Set < Promise < void > > ( ) ; let waitTimeoutId ; let resolveIdlePromise ; return { pushActivity ( activity ) { if ( ! resolveIdlePromise ) { idlePromise = new Promise ( ( resolve ) => { resolveIdlePromise = resolve ; } ) ; } clearTimeout ( waitTimeoutId ) ; pendingActivities . add ( activity ) ; void activity . then ( ( ) => { pendingActivities . delete ( activity ) ; if ( pendingActivities . size === 0 ) { waitTimeoutId = setTimeout ( ( ) => { resolveIdlePromise ! ( ) ; resolveIdlePromise = undefined ; } , IDLE_WAIT_DURATION ) ; } } ) ; } , get idlePromise ( ) { return idlePromise ; } , } ; }