From 926c0a09cda0442532cd894581e8800f416715a5 Mon Sep 17 00:00:00 2001 From: xion Date: Sat, 16 Nov 2024 12:34:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0sign=E7=9A=84?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++- demo/simple/src/apps-https/app.ts | 35 +++++++++++++++++ demo/simple/src/apps-https/create-sign.ts | 6 +++ package.json | 9 ++++- rollup.config.js | 22 +++++++++++ src/app.ts | 3 ++ src/server/server.ts | 41 +++++++++++++++++--- src/sign.ts | 46 +++++++++++++++++++++++ 8 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 demo/simple/src/apps-https/app.ts create mode 100644 demo/simple/src/apps-https/create-sign.ts create mode 100644 src/sign.ts diff --git a/.gitignore b/.gitignore index 3561a0e..ee1f5dd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ src/app.config.json5 dist .npmrc -.turbo \ No newline at end of file +.turbo + +https-cert.pem +https-key.pem \ No newline at end of file diff --git a/demo/simple/src/apps-https/app.ts b/demo/simple/src/apps-https/app.ts new file mode 100644 index 0000000..68d0bdb --- /dev/null +++ b/demo/simple/src/apps-https/app.ts @@ -0,0 +1,35 @@ +import { Route, App } from '@kevisual/router'; +import { readFileSync } from 'fs'; +const app = new App({ + serverOptions: { + cors: {}, + isHTTPS: true, + httpsKey: readFileSync('https-key.pem', 'utf8'), + httpsCert: readFileSync('https-cert.pem', 'utf-8'), + }, +}); +app.listen(4003, '0.0.0.0', () => { + console.log(`http://localhost:4003/api/router?path=demo&key=02`); + console.log(`http://localhost:4003/api/router?path=demo&key=01`); + console.log(`https://192.168.31.220:4003/api/router?path=demo&key=01`); +}); +const route01 = new Route('demo', '01'); +route01.run = async (ctx) => { + ctx.body = '01'; + return ctx; +}; +app.use( + 'demo', + async (ctx) => { + ctx.body = '01'; + return ctx; + }, + { key: '01' }, +); + +const route02 = new Route('demo', '02'); +route02.run = async (ctx) => { + ctx.body = '02'; + return ctx; +}; +app.addRoute(route02); diff --git a/demo/simple/src/apps-https/create-sign.ts b/demo/simple/src/apps-https/create-sign.ts new file mode 100644 index 0000000..b8d2029 --- /dev/null +++ b/demo/simple/src/apps-https/create-sign.ts @@ -0,0 +1,6 @@ +import { createCert } from '@kevisual/router/sign'; +import { writeFileSync } from 'fs'; +const { key, cert } = createCert(); + +writeFileSync('https-key.pem', key); +writeFileSync('https-cert.pem', cert); \ No newline at end of file diff --git a/package.json b/package.json index 376cccb..ececaa3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@kevisual/router", - "version": "0.0.4-alpha-8", + "version": "0.0.4", "description": "", "main": "dist/index.js", "module": "dist/index.js", @@ -41,6 +41,7 @@ "url": "git+https://github.com/abearxiong/kevisual-router.git" }, "dependencies": { + "selfsigned": "^2.4.1", "ws": "^8.18.0" }, "publishConfig": { @@ -54,6 +55,10 @@ "./browser": { "import": "./dist/router-browser.js", "require": "./dist/router-browser.js" + }, + "./sign": { + "import": "./dist/router-sign.js", + "require": "./dist/router-sign.js" } } -} +} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index 00c60b0..3f3c1c8 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -51,4 +51,26 @@ export default [ }, plugins: [dts()], }, + { + input: 'src/sign.ts', + output: { + file: 'dist/router-sign.js', + format: 'es', + }, + plugins: [ + resolve({ + browser: false, + }), + commonjs(), + typescript(), + ], + }, + { + input: 'src/sign.ts', + output: { + file: 'dist/router-sign.d.ts', + format: 'es', + }, + plugins: [dts()], + }, ]; diff --git a/src/app.ts b/src/app.ts index c81d3c2..ef2ca18 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,6 +14,9 @@ type AppOptions = { path?: string; cors?: Cors; handle?: any; + isHTTPS?: boolean; + httpsKey?: string; + httpsCert?: string; }; io?: boolean; ioOpts?: { routerHandle?: RouterHandle; routerContext?: RouteContext; path?: string }; diff --git a/src/server/server.ts b/src/server/server.ts index e69cb5f..ac75881 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,6 +1,6 @@ import http, { IncomingMessage, ServerResponse } from 'http'; +import https from 'https'; import { handleServer } from './handle-server.ts'; - export type Listener = (...args: any[]) => void; export type Cors = { @@ -9,12 +9,15 @@ export type Cors = { */ origin?: string | undefined; }; -type ServerOpts = { +export type ServerOpts = { /**path default `/api/router` */ path?: string; /**handle Fn */ handle?: (msg?: { path: string; key?: string; [key: string]: any }) => any; cors?: Cors; + isHTTPS?: boolean; + httpsKey?: string; + httpsCert?: string; }; export const resultError = (error: string, code = 500) => { const r = { @@ -31,10 +34,20 @@ export class Server { private _callback: any; private cors: Cors; private hasOn = false; + private isHTTPS = false; + private options = { + key: '', + cert: '', + }; constructor(opts?: ServerOpts) { this.path = opts?.path || '/api/router'; this.handle = opts?.handle; this.cors = opts?.cors; + this.isHTTPS = opts?.isHTTPS || false; + this.options = { + key: opts?.httpsKey || '', + cert: opts?.httpsCert || '', + }; } listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void): void; listen(port: number, hostname?: string, listeningListener?: () => void): void; @@ -45,11 +58,29 @@ export class Server { listen(handle: any, backlog?: number, listeningListener?: () => void): void; listen(handle: any, listeningListener?: () => void): void; listen(...args: any[]) { - this._server = http.createServer(); + this._server = this.createServer(); const callback = this.createCallback(); this._server.on('request', callback); this._server.listen(...args); } + createServer() { + let server: http.Server | https.Server; + if (this.isHTTPS) { + if (this.options.key && this.options.cert) { + server = https.createServer({ + key: this.options.key, + cert: this.options.cert, + }); + console.log('https server'); + return server; + } else { + console.error('https key and cert is required'); + console.log('downgrade to http'); + } + } + server = http.createServer(); + return server; + } setHandle(handle?: any) { this.handle = handle; } @@ -65,7 +96,7 @@ export class Server { if (req.url === '/favicon.ico') { return; } - + if (res.headersSent) { // 程序已经在其他地方响应了 return; @@ -130,7 +161,7 @@ export class Server { * @param listener */ on(listener: Listener | Listener[]) { - this._server = this._server || http.createServer(); + this._server = this._server || this.createServer(); this._server.removeAllListeners('request'); this.hasOn = true; if (Array.isArray(listener)) { diff --git a/src/sign.ts b/src/sign.ts new file mode 100644 index 0000000..d07ed24 --- /dev/null +++ b/src/sign.ts @@ -0,0 +1,46 @@ +import { generate } from 'selfsigned'; + +type Attributes = { + name: string; + value: string; +}; +type AltNames = { + type: number; + value?: string; + ip?: string; +}; +export const createCert = (attrs: Attributes[] = [], altNames: AltNames[] = []) => { + let attributes = [ + { name: 'commonName', value: '*' }, // 通配符域名 + { name: 'countryName', value: 'CN' }, // 国家代码 + { name: 'stateOrProvinceName', value: 'ZheJiang' }, // 州名 + { name: 'localityName', value: 'Hangzhou' }, // 城市名 + { name: 'organizationName', value: 'Envision' }, // 组织名 + { name: 'organizationalUnitName', value: 'IT' }, // 组织单位 + ...attrs, + ]; + // attribute 根据name去重复, 后面的覆盖前面的 + attributes = attributes.filter((item, index, self) => { + return self.findIndex((t) => t.name === item.name) === index; + }); + + const options = { + days: 365, // 证书有效期(天) + extensions: [ + { + name: 'subjectAltName', + altNames: [ + { type: 2, value: '*' }, // DNS 名称 + { type: 2, value: 'localhost' }, // DNS + { type: 7, ip: '127.0.0.1' }, // IP 地址 + ...altNames, + ], + }, + ], + }; + const pems = generate(attributes, options); + return { + key: pems.private, + cert: pems.cert, + }; +};