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.
141 lines
5.9 KiB
141 lines
5.9 KiB
import {Inject, Singleton} from '../di'
|
|
import {ErrorWithContext, HTTPStatus, withTimeout} 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}. Press ^C to stop.`)
|
|
})
|
|
|
|
process.on('SIGINT', 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 get handler(): RequestListener {
|
|
// const timeout = this.config.get('server.timeout', 10000)
|
|
// const timeout = 0 // temporarily disable this because it is causing problems
|
|
|
|
return async (request: IncomingMessage, response: ServerResponse) => {
|
|
const extolloReq = new Request(request, response)
|
|
|
|
await this.requestLocalStorage.run(extolloReq, async () => {
|
|
/* withTimeout(timeout, extolloReq.response.sent$.toPromise())
|
|
.onTime(() => {
|
|
this.logging.verbose(`Request lifecycle finished on time. (Path: ${extolloReq.path})`)
|
|
})
|
|
.late(() => {
|
|
if ( !extolloReq.bypassTimeout ) {
|
|
this.logging.warn(`Request lifecycle finished late, so an error response was returned! (Path: ${extolloReq.path})`)
|
|
}
|
|
})
|
|
.timeout(() => {
|
|
if ( extolloReq.bypassTimeout ) {
|
|
this.logging.info(`Request lifecycle has timed out, but bypassRequest was set. (Path: ${extolloReq.path})`)
|
|
return
|
|
}
|
|
|
|
this.logging.error(`Request lifecycle has timed out. Will send error response instead. (Path: ${extolloReq.path})`)
|
|
extolloReq.response.setStatus(HTTPStatus.REQUEST_TIMEOUT)
|
|
extolloReq.response.body = 'Sorry, your request timed out.'
|
|
extolloReq.response.send()
|
|
})
|
|
.run()
|
|
.catch(e => this.logging.error(e))*/
|
|
|
|
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()
|
|
})
|
|
}
|
|
}
|
|
}
|