import * as http from "http" ; import { BaseServiceType , ServiceProto } from 'tsrpc-proto' ; import { HttpUtil } from '../../models/HttpUtil' ; import { TSRPC_VERSION } from "../../models/version" ; import { BaseServer , BaseServerOptions , defaultBaseServerOptions , ServerStatus } from '../base/BaseServer' ; import { HttpConnection } from './HttpConnection' ; typeParam export class HttpServer < ServiceType extends BaseServiceType = any > extends BaseServer < ServiceType > { readonly options ! : HttpServerOptions < ServiceType > ; constructor ( proto , options ? ) { super ( proto , { ... defaultHttpServerOptions , ... options } ) ; this . options . jsonHostPath = this . options . jsonHostPath ? ( this . options . jsonHostPath . startsWith ( '/' ) ? '' : '/' ) + this . options . jsonHostPath + ( this . options . jsonHostPath . endsWith ( '/' ) ? '' : '/' ) : '/' ; } /** Native `http.Server` of NodeJS */ httpServer ? : http . Server ; /**
     * {@inheritDoc BaseServer.start}
     */ start ( ) { if ( this . httpServer ) { throw new Error ( 'Server already started' ) ; } return new Promise ( rs => { this . _status = ServerStatus . Opening ; this . logger . log ( `Starting HTTP server ...` ) ; this . httpServer = http . createServer ( ( httpReq , httpRes ) => { if ( this . status !== ServerStatus . Opened ) { httpRes . statusCode = 503 ; httpRes . end ( ) ; return ; } let ip = HttpUtil . getClientIp ( httpReq ) ; httpRes . statusCode = 200 ; httpRes . setHeader ( 'X-Powered-By' , `TSRPC ${ TSRPC_VERSION }` ) ; if ( this . options . cors ) { httpRes . setHeader ( 'Access-Control-Allow-Origin' , this . options . cors ) ; httpRes . setHeader ( 'Access-Control-Allow-Headers' , 'Content-Type,*' ) ; if ( httpReq . method === 'OPTIONS' ) { httpRes . writeHead ( 200 ) ; httpRes . end ( ) ; return ; } } ; let chunks = [ ] ; httpReq . on ( 'data' , data => { chunks . push ( data ) ; } ) ; let conn ; httpReq . on ( 'end' , async ( ) => { let isJSON = this . options . jsonEnabled && httpReq . headers [ "content-type" ] ?. toLowerCase ( ) . includes ( 'application/json' ) && httpReq . method === 'POST' && httpReq . url ?. startsWith ( this . options . jsonHostPath ) ; conn = new HttpConnection ( { server : this , id : '' + this . _connIdCounter . getNext ( ) , ip : ip , httpReq : httpReq , httpRes : httpRes , dataType : isJSON ? 'text' : 'buffer' } ) ; await this . flows . postConnectFlow . exec ( conn , conn . logger ) ; let buf = chunks . length === 1 ? chunks [ 0 ] : Buffer . concat ( chunks ) ; if ( isJSON ) { let url = conn . httpReq . url ! ; let urlEndPos = url . indexOf ( '?' ) ; if ( urlEndPos > - 1 ) { url = url . slice ( 0 , urlEndPos ) ; } let serviceName = url . slice ( this . options . jsonHostPath . length ) ; this . _onRecvData ( conn , buf . toString ( ) , serviceName ) ; } else { this . _onRecvData ( conn , buf ) ; } } ) ; httpRes . on ( 'close' , async ( ) => { if ( httpReq . aborted ) { if ( conn ) { if ( conn . call ) { conn . call . logger . log ( '[ReqAborted]' ) ; } else { conn . logger . log ( '[ReqAborted]' ) ; } } else { this . logger . log ( '[ReqAborted]' , { url : httpReq . url , method : httpReq . method , ip : ip , chunksLength : chunks . length , chunksSize : chunks . sum ( v => v . byteLength ) , reqComplete : httpReq . complete , headers : httpReq . rawHeaders } ) ; } } else if ( ! conn ) { this . logger . warn ( 'Socket closed before request end' , { url : httpReq . url , method : httpReq . method , ip : ip , chunksLength : chunks . length , chunksSize : chunks . sum ( v => v . byteLength ) , reqComplete : httpReq . complete , headers : httpReq . rawHeaders } ) ; } else if ( ! httpRes . writableEnded ) { ( conn . call ?. logger || conn . logger ) . warn ( 'Socket closed without response' ) ; } if ( conn ) { await this . flows . postDisconnectFlow . exec ( { conn : conn } , conn . logger ) ; } } ) ; } ) ; if ( this . options . socketTimeout ) { this . httpServer . timeout = this . options . socketTimeout ; } if ( this . options . keepAliveTimeout ) { this . httpServer . keepAliveTimeout = this . options . keepAliveTimeout ; } this . httpServer . listen ( this . options . port , ( ) => { this . _status = ServerStatus . Opened ; this . logger . log ( `Server started at ${ this . options . port }.` ) ; rs ( ) ; } ) ; } ) ; } /**
     * {@inheritDoc BaseServer.stop}
     */ async stop ( ) { if ( ! this . httpServer ) { return ; } this . logger . log ( 'Stopping server...' ) ; return new Promise < void > ( ( rs ) => { this . _status = ServerStatus . Closing ; this . httpServer ?. close ( err => { this . _status = ServerStatus . Closed ; this . httpServer = undefined ; if ( err ) { this . logger . error ( err ) ; } this . logger . log ( 'Server stopped' ) ; rs ( ) ; } ) ; } ) ; } } export interface HttpServerOptions < ServiceType extends BaseServiceType > extends BaseServerOptions < ServiceType > { /** Which port the HTTP server listen to */ port : number ; defaultValue see https socketTimeout ? : number ; defaultValue see https keepAliveTimeout ? : number ; defaultValue cors ? : string ; defaultValue jsonHostPath : string ; } export const defaultHttpServerOptions = { ... defaultBaseServerOptions , port : 3000 , cors : '*' , jsonHostPath : '/' , } ;