Add foreground service, some cleanup, and start websocket server
This commit is contained in:
parent
4d7769de56
commit
6476416c67
@ -25,6 +25,7 @@
|
|||||||
"@types/rimraf": "^3.0.0",
|
"@types/rimraf": "^3.0.0",
|
||||||
"@types/ssh2": "^0.5.46",
|
"@types/ssh2": "^0.5.46",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
|
"@types/ws": "^8.5.3",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"busboy": "^0.3.1",
|
"busboy": "^0.3.1",
|
||||||
"cli-table": "^0.3.6",
|
"cli-table": "^0.3.6",
|
||||||
@ -48,6 +49,7 @@
|
|||||||
"typedoc-plugin-sourcefile-url": "^1.0.6",
|
"typedoc-plugin-sourcefile-url": "^1.0.6",
|
||||||
"typescript": "^4.2.3",
|
"typescript": "^4.2.3",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
|
"ws": "^8.8.0",
|
||||||
"zod": "^3.11.6"
|
"zod": "^3.11.6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -21,6 +21,7 @@ specifiers:
|
|||||||
'@types/sinon': ^10.0.6
|
'@types/sinon': ^10.0.6
|
||||||
'@types/ssh2': ^0.5.46
|
'@types/ssh2': ^0.5.46
|
||||||
'@types/uuid': ^8.3.0
|
'@types/uuid': ^8.3.0
|
||||||
|
'@types/ws': ^8.5.3
|
||||||
'@types/wtfnode': ^0.7.0
|
'@types/wtfnode': ^0.7.0
|
||||||
'@typescript-eslint/eslint-plugin': ^4.26.0
|
'@typescript-eslint/eslint-plugin': ^4.26.0
|
||||||
'@typescript-eslint/parser': ^4.26.0
|
'@typescript-eslint/parser': ^4.26.0
|
||||||
@ -51,6 +52,7 @@ specifiers:
|
|||||||
typedoc-plugin-sourcefile-url: ^1.0.6
|
typedoc-plugin-sourcefile-url: ^1.0.6
|
||||||
typescript: ^4.2.3
|
typescript: ^4.2.3
|
||||||
uuid: ^8.3.2
|
uuid: ^8.3.2
|
||||||
|
ws: ^8.8.0
|
||||||
wtfnode: ^0.9.1
|
wtfnode: ^0.9.1
|
||||||
zod: ^3.11.6
|
zod: ^3.11.6
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ dependencies:
|
|||||||
'@types/rimraf': 3.0.0
|
'@types/rimraf': 3.0.0
|
||||||
'@types/ssh2': 0.5.46
|
'@types/ssh2': 0.5.46
|
||||||
'@types/uuid': 8.3.0
|
'@types/uuid': 8.3.0
|
||||||
|
'@types/ws': 8.5.3
|
||||||
bcrypt: 5.0.1
|
bcrypt: 5.0.1
|
||||||
busboy: 0.3.1
|
busboy: 0.3.1
|
||||||
cli-table: 0.3.6
|
cli-table: 0.3.6
|
||||||
@ -95,6 +98,7 @@ dependencies:
|
|||||||
typedoc-plugin-sourcefile-url: 1.0.6_typedoc@0.20.36
|
typedoc-plugin-sourcefile-url: 1.0.6_typedoc@0.20.36
|
||||||
typescript: 4.2.3
|
typescript: 4.2.3
|
||||||
uuid: 8.3.2
|
uuid: 8.3.2
|
||||||
|
ws: 8.8.0
|
||||||
zod: 3.11.6
|
zod: 3.11.6
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
@ -433,7 +437,7 @@ packages:
|
|||||||
/@types/ssh2-streams/0.1.8:
|
/@types/ssh2-streams/0.1.8:
|
||||||
resolution: {integrity: sha512-I7gixRPUvVIyJuCEvnmhr3KvA2dC0639kKswqD4H5b4/FOcnPtNU+qWLiXdKIqqX9twUvi5j0U1mwKE5CUsrfA==}
|
resolution: {integrity: sha512-I7gixRPUvVIyJuCEvnmhr3KvA2dC0639kKswqD4H5b4/FOcnPtNU+qWLiXdKIqqX9twUvi5j0U1mwKE5CUsrfA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 14.17.1
|
'@types/node': 14.17.6
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/ssh2/0.5.46:
|
/@types/ssh2/0.5.46:
|
||||||
@ -451,6 +455,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==}
|
resolution: {integrity: sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/ws/8.5.3:
|
||||||
|
resolution: {integrity: sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 14.17.6
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/wtfnode/0.7.0:
|
/@types/wtfnode/0.7.0:
|
||||||
resolution: {integrity: sha512-kdBHgE9+M1Os7UqWZtiLhKye5reFl8cPBYyCsP2fatwZRz7F7GdIxIHZ20Kkc0hYBfbXE+lzPOTUU1I0qgjtHA==}
|
resolution: {integrity: sha512-kdBHgE9+M1Os7UqWZtiLhKye5reFl8cPBYyCsP2fatwZRz7F7GdIxIHZ20Kkc0hYBfbXE+lzPOTUU1I0qgjtHA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -3120,6 +3130,19 @@ packages:
|
|||||||
/wrappy/1.0.2:
|
/wrappy/1.0.2:
|
||||||
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
|
||||||
|
|
||||||
|
/ws/8.8.0:
|
||||||
|
resolution: {integrity: sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: ^5.0.2
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/wtfnode/0.9.1:
|
/wtfnode/0.9.1:
|
||||||
resolution: {integrity: sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA==}
|
resolution: {integrity: sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -107,7 +107,7 @@ export class Request extends ScopedContainer implements DataContainer {
|
|||||||
protected clientRequest: IncomingMessage,
|
protected clientRequest: IncomingMessage,
|
||||||
|
|
||||||
/** The native Node.js response. */
|
/** The native Node.js response. */
|
||||||
protected serverResponse: ServerResponse,
|
protected serverResponse?: ServerResponse,
|
||||||
) {
|
) {
|
||||||
super(Container.getContainer())
|
super(Container.getContainer())
|
||||||
this.registerSingletonInstance(Request, this)
|
this.registerSingletonInstance(Request, this)
|
||||||
|
@ -66,7 +66,7 @@ export class Response {
|
|||||||
public readonly request: Request,
|
public readonly request: Request,
|
||||||
|
|
||||||
/** The native Node.js ServerResponse. */
|
/** The native Node.js ServerResponse. */
|
||||||
protected readonly serverResponse: ServerResponse,
|
protected readonly serverResponse?: ServerResponse,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
protected get logging(): Logging {
|
protected get logging(): Logging {
|
||||||
@ -173,6 +173,11 @@ export class Response {
|
|||||||
*/
|
*/
|
||||||
public sendHeaders(): this {
|
public sendHeaders(): this {
|
||||||
this.logging.verbose(`Sending headers...`)
|
this.logging.verbose(`Sending headers...`)
|
||||||
|
if ( !this.serverResponse ) {
|
||||||
|
throw new ErrorWithContext('Unable to send headers: Response has no underlying connection.', {
|
||||||
|
suggestion: 'This usually means the Request was created by an alternative server, like WebsocketServer. You should use that server to handle the request.',
|
||||||
|
})
|
||||||
|
}
|
||||||
const headers = {} as any
|
const headers = {} as any
|
||||||
|
|
||||||
const setCookieHeaders = this.cookies.getSetCookieHeaders()
|
const setCookieHeaders = this.cookies.getSetCookieHeaders()
|
||||||
@ -220,8 +225,14 @@ export class Response {
|
|||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
public async write(data: string | Buffer | Uint8Array | Readable): Promise<void> {
|
public async write(data: string | Buffer | Uint8Array | Readable): Promise<void> {
|
||||||
this.logging.verbose(`Writing headers & data to response... (destroyed? ${this.serverResponse.destroyed})`)
|
this.logging.verbose(`Writing headers & data to response... (destroyed? ${!this.serverResponse || this.serverResponse.destroyed})`)
|
||||||
return new Promise<void>((res, rej) => {
|
return new Promise<void>((res, rej) => {
|
||||||
|
if ( !this.serverResponse ) {
|
||||||
|
throw new ErrorWithContext('Unable to write response: Response has no underlying connection.', {
|
||||||
|
suggestion: 'This usually means the Request was created by an alternative server, like WebsocketServer. You should use that server to handle the request.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if ( this.responseEnded || this.serverResponse.destroyed ) {
|
if ( this.responseEnded || this.serverResponse.destroyed ) {
|
||||||
throw new ErrorWithContext('Tried to write to Response after lifecycle ended.')
|
throw new ErrorWithContext('Tried to write to Response after lifecycle ended.')
|
||||||
}
|
}
|
||||||
@ -274,7 +285,7 @@ export class Response {
|
|||||||
* or the connection has been destroyed.
|
* or the connection has been destroyed.
|
||||||
*/
|
*/
|
||||||
public canSend(): boolean {
|
public canSend(): boolean {
|
||||||
return !(this.responseEnded || this.serverResponse.destroyed)
|
return !(this.responseEnded || !this.serverResponse || this.serverResponse.destroyed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -286,7 +297,7 @@ export class Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.sentHeaders = true
|
this.sentHeaders = true
|
||||||
this.serverResponse.end()
|
this.serverResponse?.end()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,6 +109,10 @@ ${Object.keys(context).map(key => ` - ${key} : ${JSON.stringify(context[key])
|
|||||||
return 'It looks like this route relies on the security framework. Is the route you are accessing inside a middleware (e.g. SessionAuthMiddleware)?'
|
return 'It looks like this route relies on the security framework. Is the route you are accessing inside a middleware (e.g. SessionAuthMiddleware)?'
|
||||||
} else if ( this.thrownError.message.startsWith('Unable to resolve schema for validator') ) {
|
} else if ( this.thrownError.message.startsWith('Unable to resolve schema for validator') ) {
|
||||||
return 'Make sure the directory in which the interface file is located is listed in extollo.cc.zodify in package.json, and that it ends with the proper .type.ts suffix.'
|
return 'Make sure the directory in which the interface file is located is listed in extollo.cc.zodify in package.json, and that it ends with the proper .type.ts suffix.'
|
||||||
|
} else if ( this.thrownError instanceof ErrorWithContext ) {
|
||||||
|
if ( typeof this.thrownError.context.suggestion === 'string' ) {
|
||||||
|
return this.thrownError.context.suggestion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
@ -136,6 +136,14 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
|
|||||||
return new Route(method, endpoint)
|
return new Route(method, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new WebSocket route on the given endpoint.
|
||||||
|
* @param endpoint
|
||||||
|
*/
|
||||||
|
public static socket(endpoint: string): Route<void, [void]> {
|
||||||
|
return new Route<void, [void]>('ws', endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new GET route on the given endpoint.
|
* Create a new GET route on the given endpoint.
|
||||||
*/
|
*/
|
||||||
@ -188,10 +196,14 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
|
|||||||
protected displays: Collection<{stage: 'pre'|'post'|'handler', display: string}> = new Collection()
|
protected displays: Collection<{stage: 'pre'|'post'|'handler', display: string}> = new Collection()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected method: HTTPMethod | HTTPMethod[],
|
protected method: 'ws' | HTTPMethod | HTTPMethod[],
|
||||||
protected route: string,
|
protected route: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public isForWebSocket(): boolean {
|
||||||
|
return this.method === 'ws'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a programmatic name for this route.
|
* Set a programmatic name for this route.
|
||||||
* @param name
|
* @param name
|
||||||
@ -212,6 +224,10 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
|
|||||||
* Get the string-form methods supported by the route.
|
* Get the string-form methods supported by the route.
|
||||||
*/
|
*/
|
||||||
public getMethods(): HTTPMethod[] {
|
public getMethods(): HTTPMethod[] {
|
||||||
|
if ( this.method === 'ws' ) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
if ( !Array.isArray(this.method) ) {
|
if ( !Array.isArray(this.method) ) {
|
||||||
return [this.method]
|
return [this.method]
|
||||||
}
|
}
|
||||||
@ -250,10 +266,14 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
|
|||||||
* @param method
|
* @param method
|
||||||
* @param potential
|
* @param potential
|
||||||
*/
|
*/
|
||||||
public match(method: HTTPMethod, potential: string): boolean {
|
public match(method: 'ws' | HTTPMethod, potential: string): boolean {
|
||||||
if ( Array.isArray(this.method) && !this.method.includes(method) ) {
|
if ( method === 'ws' && !this.isForWebSocket() ) {
|
||||||
return false
|
return false
|
||||||
} else if ( !Array.isArray(this.method) && this.method !== method ) {
|
} else if ( method !== 'ws' && this.isForWebSocket() ) {
|
||||||
|
return false
|
||||||
|
} else if ( method !== 'ws' && Array.isArray(this.method) && !this.method.includes(method) ) {
|
||||||
|
return false
|
||||||
|
} else if ( method !== 'ws' && !Array.isArray(this.method) && this.method !== method ) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,9 +80,11 @@ export * from './service/Config'
|
|||||||
export * from './service/Controllers'
|
export * from './service/Controllers'
|
||||||
export * from './service/Files'
|
export * from './service/Files'
|
||||||
export * from './service/HTTPServer'
|
export * from './service/HTTPServer'
|
||||||
|
export * from './service/WebsocketServer'
|
||||||
export * from './service/Routing'
|
export * from './service/Routing'
|
||||||
export * from './service/Middlewares'
|
export * from './service/Middlewares'
|
||||||
export * from './service/Discovery'
|
export * from './service/Discovery'
|
||||||
|
export * from './service/Foreground'
|
||||||
|
|
||||||
export * from './support/redis/Redis'
|
export * from './support/redis/Redis'
|
||||||
export * from './support/cache/MemoryCache'
|
export * from './support/cache/MemoryCache'
|
||||||
|
@ -8,7 +8,6 @@ import {Inject} from '../di'
|
|||||||
import * as nodePath from 'path'
|
import * as nodePath from 'path'
|
||||||
import {Unit} from '../lifecycle/Unit'
|
import {Unit} from '../lifecycle/Unit'
|
||||||
import {isCanonicalReceiver} from '../support/CanonicalReceiver'
|
import {isCanonicalReceiver} from '../support/CanonicalReceiver'
|
||||||
import {env} from '../lifecycle/Application'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface describing a definition of a single canonical item loaded from the app.
|
* Interface describing a definition of a single canonical item loaded from the app.
|
||||||
|
24
src/service/Foreground.ts
Normal file
24
src/service/Foreground.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {Unit} from '../lifecycle/Unit'
|
||||||
|
import {Inject} from '../di'
|
||||||
|
import {Logging} from './Logging'
|
||||||
|
import * as process from 'process'
|
||||||
|
|
||||||
|
export class Foreground extends Unit {
|
||||||
|
@Inject()
|
||||||
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
protected resolver?: () => unknown
|
||||||
|
|
||||||
|
public up(): Promise<void> {
|
||||||
|
return new Promise<void>(res => {
|
||||||
|
this.resolver = res
|
||||||
|
this.logging.success(`Application started! Press ^C or send SIGINT to stop.`)
|
||||||
|
process.stdin.resume()
|
||||||
|
process.on('SIGINT', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public down(): void {
|
||||||
|
this.resolver?.()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import {Inject, Singleton} from '../di'
|
import {Inject, Singleton} from '../di'
|
||||||
import {ErrorWithContext, HTTPStatus, withTimeout} from '../util'
|
import {ErrorWithContext} from '../util'
|
||||||
import {Unit} from '../lifecycle/Unit'
|
import {Unit} from '../lifecycle/Unit'
|
||||||
import {createServer, IncomingMessage, RequestListener, Server, ServerResponse} from 'http'
|
import {createServer, IncomingMessage, RequestListener, Server, ServerResponse} from 'http'
|
||||||
import {Logging} from './Logging'
|
import {Logging} from './Logging'
|
||||||
@ -67,10 +67,9 @@ export class HTTPServer extends Unit {
|
|||||||
this.server = createServer(this.handler)
|
this.server = createServer(this.handler)
|
||||||
|
|
||||||
this.server.listen(port, () => {
|
this.server.listen(port, () => {
|
||||||
this.logging.success(`Server listening on port ${port}. Press ^C to stop.`)
|
this.logging.success(`Server listening on port ${port}.`)
|
||||||
|
res()
|
||||||
})
|
})
|
||||||
|
|
||||||
process.on('SIGINT', res)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,37 +84,19 @@ export class HTTPServer extends Unit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get handler(): RequestListener {
|
public getServer(): Server {
|
||||||
// const timeout = this.config.get('server.timeout', 10000)
|
if ( !this.server ) {
|
||||||
// const timeout = 0 // temporarily disable this because it is causing problems
|
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) => {
|
return async (request: IncomingMessage, response: ServerResponse) => {
|
||||||
const extolloReq = new Request(request, response)
|
const extolloReq = new Request(request, response)
|
||||||
|
|
||||||
await this.requestLocalStorage.run(extolloReq, async () => {
|
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}`)
|
this.logging.info(`${extolloReq.method} ${extolloReq.path}`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -12,7 +12,6 @@ import {PackageDiscovered} from '../support/PackageDiscovered'
|
|||||||
import {staticServer} from '../http/servers/static'
|
import {staticServer} from '../http/servers/static'
|
||||||
import {Bus} from '../support/bus'
|
import {Bus} from '../support/bus'
|
||||||
import {RequestLocalStorage} from '../http/RequestLocalStorage'
|
import {RequestLocalStorage} from '../http/RequestLocalStorage'
|
||||||
import {env} from '../lifecycle/Application'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
|
* Application unit that loads the various route files from `app/http/routes` and pre-compiles the route handlers.
|
||||||
@ -106,7 +105,7 @@ export class Routing extends Unit {
|
|||||||
* @param method
|
* @param method
|
||||||
* @param path
|
* @param path
|
||||||
*/
|
*/
|
||||||
public match(method: HTTPMethod, path: string): Route<unknown, unknown[]> | undefined {
|
public match(method: 'ws' | HTTPMethod, path: string): Route<unknown, unknown[]> | undefined {
|
||||||
return this.compiledRoutes.firstWhere(route => {
|
return this.compiledRoutes.firstWhere(route => {
|
||||||
return route.match(method, path)
|
return route.match(method, path)
|
||||||
})
|
})
|
||||||
|
50
src/service/WebsocketServer.ts
Normal file
50
src/service/WebsocketServer.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import {Unit, UnitStatus} from '../lifecycle/Unit'
|
||||||
|
import {Inject, Singleton} from '../di'
|
||||||
|
import * as WebSocket from 'ws'
|
||||||
|
import {HTTPServer} from './HTTPServer'
|
||||||
|
import {Logging} from './Logging'
|
||||||
|
import {ErrorWithContext} from '../util'
|
||||||
|
import {Request} from '../http/lifecycle/Request'
|
||||||
|
|
||||||
|
@Singleton()
|
||||||
|
export class WebsocketServer extends Unit {
|
||||||
|
@Inject()
|
||||||
|
protected readonly http!: HTTPServer
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
|
protected server?: WebSocket.Server
|
||||||
|
|
||||||
|
public async up(): Promise<void> {
|
||||||
|
// Make sure the HTTP server is started. Otherwise, this is going to fail anyway
|
||||||
|
if ( this.http.status !== UnitStatus.Started ) {
|
||||||
|
throw new ErrorWithContext('Cannot start WebsocketServer without HTTPServer.', {
|
||||||
|
suggestion: 'Make sure the HTTPServer is registered in your Units.extollo.ts file, and it is listed before the WebsocketServer.',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the websocket server
|
||||||
|
this.logging.info('Starting WebSocket server...')
|
||||||
|
this.server = new WebSocket.Server<WebSocket.WebSocket>({
|
||||||
|
server: this.http.getServer(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Register the websocket handler
|
||||||
|
this.server.on('connection', (ws, request) => {
|
||||||
|
this.logging.info('Got WebSocket connection! ' + request.method)
|
||||||
|
const extolloReq = new Request(request)
|
||||||
|
this.logging.debug(ws)
|
||||||
|
this.logging.debug(request)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public down(): Promise<void> {
|
||||||
|
return new Promise(res => {
|
||||||
|
// Stop the websocket server, if it exists
|
||||||
|
if ( this.server ) {
|
||||||
|
this.server.close(() => res())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user