@tunframework/tun
Version:
tun framework for node with typescript
110 lines (109 loc) • 3.9 kB
JavaScript
import { EventEmitter } from 'events';
import { createServer } from 'http';
import { RAW_RESPONSE } from './constants/http/symbol.js';
import { HttpError } from './HttpError.js';
import { compose } from './TunComposable.js';
import { TunContext } from './TunContext.js';
import { _stringifyTunCookie } from './TunCookie.js';
export class TunApplication extends EventEmitter {
middleware = [];
mountObj = {};
constructor() {
super();
}
use(fn) {
Array.isArray(fn) ? this.middleware.push(...fn) : this.middleware.push(fn);
return this;
}
mount(name, obj) {
this.mountObj[name] = obj;
}
// unmount(name: string) {
// delete this.mountObj[name];
// }
callback() {
return (_req, _res) => {
const fn = compose(this.middleware);
const ctx = new TunContext(_req, _res);
// 挂载收集到的属性到ctx.state上
Object.assign(ctx.state, this.mountObj);
return (Promise.resolve(fn(ctx))
.catch(genError)
// finish 处理 设置到 应用上下文 的数据
// body 响应内容
.then((body) => dealResponse(ctx, body))
.catch((err) => dealResponse(ctx, err)));
};
}
listen(option) {
const server = createServer(this.callback());
server.on('error', (err) => this.emit('error', err, server));
const host = process.env.HOST ? process.env.HOST : 'localhost';
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000;
let _option = {
host,
port,
// path: '/',
...(option || {})
};
server.listen(_option);
// Handle listening manual:
// server.on('listening', () => {
// let addr = (server.address() || {}) as AddressInfo
// const url = 'http://' + [addr.address, addr.port].filter(Boolean).join(':')
// console.log(`app listening: ${url}`)
// })
return server;
}
}
function genError(err) {
if (err instanceof HttpError) {
return err;
}
else {
return new HttpError({ error: err });
}
}
async function dealResponse(ctx, _body) {
const _res = ctx.res[RAW_RESPONSE];
// Return if response handled by other middleware
if (_res.headersSent || _res.writableEnded)
return;
if (typeof _body !== 'undefined') {
ctx.body = _body;
}
ctx.body = await ctx.body;
if (ctx.body instanceof HttpError) {
ctx.res.status = ctx.body.status;
ctx.body = { status: ctx.res.status, message: ctx.body.message };
}
else if (ctx.body instanceof Error) {
ctx.res.status = 500;
ctx.body = { status: ctx.res.status, message: ctx.body.message };
}
// Write response header
if (!(_res.headersSent || _res.writableEnded)) {
if (typeof ctx.body !== 'undefined' &&
typeof ctx.body === 'object' &&
!ctx.res.type.rawType) {
ctx.res.type = { rawType: 'application/json; charset=utf-8' };
}
// Set response header for collected cookies
const cookies = ctx.res.cookies.map(_stringifyTunCookie);
_res.setHeader.call(_res, 'set-cookie', cookies);
_res.writeHead(ctx.res.status, ctx.res.message);
}
// Write response body
if (!_res.writableEnded) {
if (ctx.body !== null && typeof ctx.body !== 'undefined') {
// fixme: Shouldcheck other types. e.g. Buffer.
if (typeof ctx.body === 'object') {
_res.write(JSON.stringify(ctx.body));
}
else {
_res.write(ctx.body);
}
}
_res.end();
}
}