init router
This commit is contained in:
46
src/server/handle-server.ts
Normal file
46
src/server/handle-server.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import http, { IncomingMessage, Server, ServerResponse } from 'http';
|
||||
import { parseBody } from './parse-body.ts';
|
||||
import url from 'url';
|
||||
|
||||
/**
|
||||
* get params and body
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const handleServer = async (req: IncomingMessage, res: ServerResponse) => {
|
||||
if (req.url === '/favicon.ico') {
|
||||
return;
|
||||
}
|
||||
const can = ['get', 'post'];
|
||||
const method = req.method.toLocaleLowerCase();
|
||||
if (!can.includes(method)) {
|
||||
return;
|
||||
}
|
||||
const parsedUrl = url.parse(req.url, true);
|
||||
// 获取token
|
||||
let token = req.headers['authorization'] || '';
|
||||
if (token) {
|
||||
token = token.replace('Bearer ', '');
|
||||
}
|
||||
// 获取查询参数
|
||||
const param = parsedUrl.query;
|
||||
let body: Record<any, any>;
|
||||
if (method === 'post') {
|
||||
body = await parseBody(req);
|
||||
}
|
||||
if (param?.payload && typeof param.payload === 'string') {
|
||||
try {
|
||||
const payload = JSON.parse(param.payload as string);
|
||||
param.payload = payload;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
const data = {
|
||||
token,
|
||||
...param,
|
||||
...body,
|
||||
};
|
||||
return data;
|
||||
};
|
||||
2
src/server/index.ts
Normal file
2
src/server/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { Server } from './server.ts';
|
||||
export { handleServer } from './handle-server.ts';
|
||||
18
src/server/parse-body.ts
Normal file
18
src/server/parse-body.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as http from 'http';
|
||||
|
||||
export const parseBody = async (req: http.IncomingMessage) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const arr: any[] = [];
|
||||
req.on('data', (chunk) => {
|
||||
arr.push(chunk);
|
||||
});
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const body = Buffer.concat(arr).toString();
|
||||
resolve(JSON.parse(body));
|
||||
} catch (e) {
|
||||
resolve({});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
148
src/server/server.ts
Normal file
148
src/server/server.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import http, { IncomingMessage, ServerResponse } from 'http';
|
||||
import { handleServer } from './handle-server.ts';
|
||||
|
||||
export type Listener = (...args: any[]) => void;
|
||||
|
||||
export type Cors = {
|
||||
/**
|
||||
* @default '*''
|
||||
*/
|
||||
origin?: string | undefined;
|
||||
};
|
||||
type ServerOpts = {
|
||||
/**path default `/api/router` */
|
||||
path?: string;
|
||||
/**handle Fn */
|
||||
handle?: (msg?: { path: string; key?: string; [key: string]: any }) => any;
|
||||
cors?: Cors;
|
||||
};
|
||||
export const resultError = (error: string, code = 500) => {
|
||||
const r = {
|
||||
code: code,
|
||||
message: error,
|
||||
};
|
||||
return JSON.stringify(r);
|
||||
};
|
||||
|
||||
export class Server {
|
||||
path = '/api/router';
|
||||
private _server: http.Server;
|
||||
public handle: ServerOpts['handle'];
|
||||
private _callback: any;
|
||||
private cors: Cors;
|
||||
private hasOn = false;
|
||||
constructor(opts?: ServerOpts) {
|
||||
this.path = opts?.path || '/api/router';
|
||||
this.handle = opts?.handle;
|
||||
this.cors = opts?.cors;
|
||||
}
|
||||
listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void): void;
|
||||
listen(port: number, hostname?: string, listeningListener?: () => void): void;
|
||||
listen(port: number, backlog?: number, listeningListener?: () => void): void;
|
||||
listen(port: number, listeningListener?: () => void): void;
|
||||
listen(path: string, backlog?: number, listeningListener?: () => void): void;
|
||||
listen(path: string, listeningListener?: () => void): void;
|
||||
listen(handle: any, backlog?: number, listeningListener?: () => void): void;
|
||||
listen(handle: any, listeningListener?: () => void): void;
|
||||
listen(...args: any[]) {
|
||||
this._server = http.createServer();
|
||||
const callback = this.createCallback();
|
||||
this._server.on('request', callback);
|
||||
this._server.listen(...args);
|
||||
}
|
||||
setHandle(handle?: any) {
|
||||
this.handle = handle;
|
||||
}
|
||||
/**
|
||||
* get callback
|
||||
* @returns
|
||||
*/
|
||||
createCallback() {
|
||||
const path = this.path;
|
||||
const handle = this.handle;
|
||||
const cors = this.cors;
|
||||
const _callback = async (req: IncomingMessage, res: ServerResponse) => {
|
||||
if (req.url === '/favicon.ico') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.headersSent) {
|
||||
// 程序已经在其他地方响应了
|
||||
return;
|
||||
}
|
||||
if (this.hasOn && !req.url.startsWith(path)) {
|
||||
// 其他监听存在,不判断不是当前路径的请求,
|
||||
// 也就是不处理!url.startsWith(path)这个请求了
|
||||
// 交给其他监听处理
|
||||
return;
|
||||
}
|
||||
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
if (cors) {
|
||||
res.setHeader('Access-Control-Allow-Origin', cors?.origin || '*'); // 允许所有域名的请求访问,可以根据需要设置具体的域名
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.writeHead(200); // 设置响应头,给予其他api知道headersSent,它已经被响应了
|
||||
|
||||
const url = req.url;
|
||||
if (!url.startsWith(path)) {
|
||||
res.end(resultError(`not path:[${path}]`));
|
||||
return;
|
||||
}
|
||||
const messages = await handleServer(req, res);
|
||||
if (!handle) {
|
||||
res.end(resultError('no handle'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const end = await handle(messages as any);
|
||||
if (typeof end === 'string') {
|
||||
res.end(end);
|
||||
} else {
|
||||
res.end(JSON.stringify(end));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e.code && typeof e.code === 'number') {
|
||||
res.end(resultError(e.message || `Router Server error`, e.code));
|
||||
} else {
|
||||
res.end(resultError('Router Server error'));
|
||||
}
|
||||
}
|
||||
};
|
||||
this._callback = _callback;
|
||||
return _callback;
|
||||
}
|
||||
get handleServer() {
|
||||
return this._callback;
|
||||
}
|
||||
set handleServer(fn: any) {
|
||||
this._callback = fn;
|
||||
}
|
||||
/**
|
||||
* 兜底监听,当除开 `/api/router` 之外的请求,框架只监听一个api,所以有其他的请求都执行其他的监听
|
||||
* @description 主要是为了兼容其他的监听
|
||||
* @param listener
|
||||
*/
|
||||
on(listener: Listener | Listener[]) {
|
||||
this._server = this._server || http.createServer();
|
||||
this._server.removeAllListeners('request');
|
||||
this.hasOn = true;
|
||||
if (Array.isArray(listener)) {
|
||||
listener.forEach((l) => this._server.on('request', l));
|
||||
} else {
|
||||
this._server.on('request', listener);
|
||||
}
|
||||
this._server.on('request', this._callback || this.createCallback());
|
||||
}
|
||||
get callback() {
|
||||
return this._callback || this.createCallback();
|
||||
}
|
||||
get server() {
|
||||
return this._server;
|
||||
}
|
||||
}
|
||||
156
src/server/ws-server.ts
Normal file
156
src/server/ws-server.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import { Server } from './server.ts';
|
||||
import { parseIfJson } from '../utils/parse.ts';
|
||||
|
||||
export const createWsServer = (server: Server) => {
|
||||
// 将 WebSocket 服务器附加到 HTTP 服务器
|
||||
const wss = new WebSocketServer({ server: server.server });
|
||||
return wss;
|
||||
};
|
||||
type WsServerBaseOpts = {
|
||||
wss?: WebSocketServer;
|
||||
path?: string;
|
||||
};
|
||||
export type ListenerFn = (message: { data: Record<string, any>; ws: WebSocket; end: (data: any) => any }) => Promise<any>;
|
||||
export type Listener<T = 'router' | 'chat' | 'ai'> = {
|
||||
type: T;
|
||||
listener: ListenerFn;
|
||||
};
|
||||
|
||||
export class WsServerBase {
|
||||
wss: WebSocketServer;
|
||||
path: string;
|
||||
listeners: { type: string; listener: ListenerFn }[] = [];
|
||||
listening: boolean = false;
|
||||
constructor(opts: WsServerBaseOpts) {
|
||||
this.wss = opts.wss || new WebSocketServer();
|
||||
this.path = opts.path || '';
|
||||
}
|
||||
setPath(path: string) {
|
||||
this.path = path;
|
||||
}
|
||||
listen() {
|
||||
if (this.listening) {
|
||||
console.error('WsServer is listening');
|
||||
return;
|
||||
}
|
||||
this.listening = true;
|
||||
|
||||
this.wss.on('connection', (ws) => {
|
||||
ws.on('message', async (message: string) => {
|
||||
const data = parseIfJson(message);
|
||||
if (typeof data === 'string') {
|
||||
ws.emit('string', data);
|
||||
return;
|
||||
}
|
||||
const { type, data: typeData, ...rest } = data;
|
||||
if (!type) {
|
||||
ws.send(JSON.stringify({ code: 500, message: 'type is required' }));
|
||||
}
|
||||
const listeners = this.listeners.find((item) => item.type === type);
|
||||
const res = {
|
||||
type,
|
||||
data: {} as any,
|
||||
...rest,
|
||||
};
|
||||
const end = (data: any, all?: Record<string, any>) => {
|
||||
const result = {
|
||||
...res,
|
||||
data,
|
||||
...all,
|
||||
};
|
||||
ws.send(JSON.stringify(result));
|
||||
};
|
||||
|
||||
if (!listeners) {
|
||||
const data = { code: 500, message: `${type} server is error` };
|
||||
end(data);
|
||||
return;
|
||||
}
|
||||
listeners.listener({
|
||||
data: typeData,
|
||||
ws,
|
||||
end: end,
|
||||
});
|
||||
});
|
||||
ws.on('string', (message: string) => {
|
||||
if (message === 'close') {
|
||||
ws.close();
|
||||
}
|
||||
if (message === 'ping') {
|
||||
ws.send('pong');
|
||||
}
|
||||
});
|
||||
ws.send('connected');
|
||||
});
|
||||
}
|
||||
addListener(type: string, listener: ListenerFn) {
|
||||
if (!type || !listener) {
|
||||
throw new Error('type and listener is required');
|
||||
}
|
||||
const find = this.listeners.find((item) => item.type === type);
|
||||
if (find) {
|
||||
this.listeners = this.listeners.filter((item) => item.type !== type);
|
||||
}
|
||||
this.listeners.push({ type, listener });
|
||||
}
|
||||
removeListener(type: string) {
|
||||
this.listeners = this.listeners.filter((item) => item.type !== type);
|
||||
}
|
||||
}
|
||||
// TODO: ws handle and path and routerContext
|
||||
export class WsServer extends WsServerBase {
|
||||
server: Server;
|
||||
constructor(server: Server, opts?: any) {
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const path = server.path;
|
||||
super({ wss });
|
||||
this.server = server;
|
||||
this.setPath(opts?.path || path);
|
||||
this.initListener();
|
||||
}
|
||||
initListener() {
|
||||
const server = this.server;
|
||||
const listener: Listener = {
|
||||
type: 'router',
|
||||
listener: async ({ data, ws, end }) => {
|
||||
if (!server) {
|
||||
end({ code: 500, message: 'server handle is error' });
|
||||
return;
|
||||
}
|
||||
const handle = this.server.handle;
|
||||
try {
|
||||
const result = await handle(data as any);
|
||||
end(result);
|
||||
} catch (e) {
|
||||
if (e.code && typeof e.code === 'number') {
|
||||
end({
|
||||
code: e.code,
|
||||
message: e.message,
|
||||
});
|
||||
} else {
|
||||
end({ code: 500, message: 'Router Server error' });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
this.addListener(listener.type, listener.listener);
|
||||
}
|
||||
listen() {
|
||||
super.listen();
|
||||
const server = this.server;
|
||||
const wss = this.wss;
|
||||
// HTTP 服务器的 upgrade 事件
|
||||
server.server.on('upgrade', (req, socket, head) => {
|
||||
if (req.url === this.path) {
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
// 这里手动触发 connection 事件
|
||||
// @ts-ignore
|
||||
wss.emit('connection', ws, req);
|
||||
});
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user