You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/service/HTTPServer.ts

122 lines
4.7 KiB

import {Inject, Singleton} from '../di'
import {ErrorWithContext} from '../util'
import {Unit} from '../lifecycle/Unit'
import {createServer, IncomingMessage, RequestListener, Server, ServerResponse} from 'http'
import {Logging} from './Logging'
import {Request} from '../http/lifecycle/Request'
import {HTTPKernel} from '../http/kernel/HTTPKernel'
import {PoweredByHeaderInjectionHTTPModule} from '../http/kernel/module/PoweredByHeaderInjectionHTTPModule'
import {SetSessionCookieHTTPModule} from '../http/kernel/module/SetSessionCookieHTTPModule'
import {InjectSessionHTTPModule} from '../http/kernel/module/InjectSessionHTTPModule'
import {PersistSessionHTTPModule} from '../http/kernel/module/PersistSessionHTTPModule'
import {MountActivatedRouteHTTPModule} from '../http/kernel/module/MountActivatedRouteHTTPModule'
import {ExecuteResolvedRouteHandlerHTTPModule} from '../http/kernel/module/ExecuteResolvedRouteHandlerHTTPModule'
import {error} from '../http/response/ErrorResponseFactory'
import {ExecuteResolvedRoutePreflightHTTPModule} from '../http/kernel/module/ExecuteResolvedRoutePreflightHTTPModule'
import {ExecuteResolvedRoutePostflightHTTPModule} from '../http/kernel/module/ExecuteResolvedRoutePostflightHTTPModule'
import {ParseIncomingBodyHTTPModule} from '../http/kernel/module/ParseIncomingBodyHTTPModule'
import {Config} from './Config'
import {InjectRequestEventBusHTTPModule} from '../http/kernel/module/InjectRequestEventBusHTTPModule'
import {Routing} from './Routing'
import {RequestLocalStorage} from '../http/RequestLocalStorage'
import {Bus} from '../support/bus'
/**
* Application unit that starts the HTTP/S server, creates Request and Response objects
* for it, and handles those requests using the HTTPKernel.
*/
@Singleton()
export class HTTPServer extends Unit {
@Inject()
protected readonly logging!: Logging
@Inject()
protected readonly config!: Config
@Inject()
protected readonly kernel!: HTTPKernel
@Inject()
protected readonly routing!: Routing
@Inject()
protected readonly bus!: Bus
@Inject()
protected readonly requestLocalStorage!: RequestLocalStorage
/** The underlying native Node.js server. */
protected server?: Server
public async up(): Promise<void> {
const port = this.config.get('server.port', 8000)
// TODO register these by config
PoweredByHeaderInjectionHTTPModule.register(this.kernel)
SetSessionCookieHTTPModule.register(this.kernel)
InjectSessionHTTPModule.register(this.kernel)
PersistSessionHTTPModule.register(this.kernel)
MountActivatedRouteHTTPModule.register(this.kernel)
ExecuteResolvedRouteHandlerHTTPModule.register(this.kernel)
ExecuteResolvedRoutePreflightHTTPModule.register(this.kernel)
ExecuteResolvedRoutePostflightHTTPModule.register(this.kernel)
ParseIncomingBodyHTTPModule.register(this.kernel)
InjectRequestEventBusHTTPModule.register(this.kernel)
await new Promise<void>(res => {
this.server = createServer(this.handler)
this.server.listen(port, () => {
this.logging.success(`Server listening on port ${port}.`)
res()
})
})
}
public async down(): Promise<void> {
if ( this.server ) {
this.server.close(err => {
if ( err ) {
this.logging.error(`Error encountered while closing HTTP server: ${err.message}`)
this.logging.debug(err)
}
})
}
}
public getServer(): Server {
if ( !this.server ) {
throw new ErrorWithContext('Unable to access server: it has not yet been created')
}
return this.server
}
public get handler(): RequestListener {
return async (request: IncomingMessage, response: ServerResponse) => {
const extolloReq = new Request(request, response)
await this.requestLocalStorage.run(extolloReq, async () => {
this.logging.info(`${extolloReq.method} ${extolloReq.path}`)
try {
await this.kernel.handle(extolloReq)
} catch (e) {
if ( e instanceof Error ) {
await error(e).write(extolloReq)
}
await error(new ErrorWithContext('Unknown error occurred.', { e }))
}
if ( extolloReq.response.canSend() ) {
await extolloReq.response.send()
}
}).finally(() => {
this.logging.verbose('Destroying request container...')
extolloReq.destroy()
})
}
}
}