feat: 更新cookie模块和添加res和req在app的请求当中

This commit is contained in:
熊潇 2024-12-11 14:28:46 +08:00
parent c99d03550e
commit dc2f282f4b
8 changed files with 159 additions and 20 deletions

View File

@ -14,6 +14,7 @@
"@kevisual/router": "link:../.." "@kevisual/router": "link:../.."
}, },
"devDependencies": { "devDependencies": {
"cookie": "^1.0.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.5.4" "typescript": "^5.5.4"
} }

View File

@ -0,0 +1,78 @@
import { App } from '@kevisual/router';
import { QueryRouterServer } from '@kevisual/router';
const app = new App();
const queryApp = new QueryRouterServer();
// queryApp
// .route({
// path: 'api',
// })
// .define(async (ctx) => {
// ctx.throw(404, 'Not Found');
// ctx.throw(500, 'Internal Server Error');
// })
// .addTo(app);
app
.route({
path: 'hello',
})
.define(async (ctx) => {
// console.log('hello', ctx);
// console.log('hello', ctx.res);
console.log('hello', ctx.query.cookies);
// ctx.res?.cookie?.('token', 'abc', {
// domain: '*', // 设置为顶级域名,允许跨子域共享
// // httpOnly: true,
// // secure: true,
// // sameSite: 'Lax',
// });
ctx.res.cookie('token', 'abc', {
// domain: '*', // 设置为顶级域名,允许跨子域共享
// httpOnly: true,
// secure: true,
// sameSite: 'Lax',
});
ctx.res.cookie('test_cookie', 'abc', {
maxAge: 0,
path: '/api/router',
});
ctx.res.cookie('test_cookie', 'abc', {
maxAge: 0,
path: '/',
});
ctx.res.cookie('user', 'abc', {
maxAge: 0,
});
ctx.res.cookie('session', 'abc', {
maxAge: 0,
});
ctx.res.cookie('preferences', 'abc', {
maxAge: 0,
});
// const cookies = [
// cookie.serialize('user', 'john_doe', {
// httpOnly: true,
// maxAge: 60 * 60 * 24 * 7, // 1 week
// sameSite: 'lax',
// }),
// cookie.serialize('session', 'xyz123', {
// httpOnly: true,
// maxAge: 60 * 60 * 24, // 1 day
// }),
// cookie.serialize('preferences', JSON.stringify({ theme: 'dark' }), {
// httpOnly: false, // Accessible via JavaScript
// maxAge: 60 * 60 * 24 * 30, // 1 month
// }),
// ];
// ctx.res.setHeader('Set-Cookie', cookies);
ctx.res.end('hello' + Math.random().toString(32).slice(2));
ctx.end = true;
return;
ctx.body = 'world';
})
.addTo(app);
app.listen(3100, () => {
console.log('listening on port http://localhost:3100');
});

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://json.schemastore.org/package", "$schema": "https://json.schemastore.org/package",
"name": "@kevisual/router", "name": "@kevisual/router",
"version": "0.0.6-alpha-2", "version": "0.0.6-alpha-3",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.js", "module": "dist/index.js",
@ -26,6 +26,7 @@
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^22.8.6", "@types/node": "^22.8.6",
"@types/ws": "^8.5.13", "@types/ws": "^8.5.13",
"cookie": "^1.0.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nanoid": "^5.0.8", "nanoid": "^5.0.8",
"rollup": "^4.24.3", "rollup": "^4.24.3",

View File

@ -16,7 +16,7 @@ export default [
}, },
plugins: [ plugins: [
resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块 resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块
// commonjs(), commonjs(),
typescript(), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件 typescript(), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
], ],
external: ['ws'], external: ['ws'],

View File

@ -1,5 +1,5 @@
import { QueryRouter, Route, RouteContext, RouteOpts } from './route.ts'; import { QueryRouter, Route, RouteContext, RouteOpts } from './route.ts';
import { Server, Cors, ServerOpts } from './server/server.ts'; import { Server, Cors, ServerOpts, HandleCtx } from './server/server.ts';
import { WsServer } from './server/ws-server.ts'; import { WsServer } from './server/ws-server.ts';
import { CustomError } from './result/error.ts'; import { CustomError } from './result/error.ts';
@ -14,7 +14,12 @@ type AppOptions<T = {}> = {
io?: boolean; io?: boolean;
ioOpts?: { routerHandle?: RouterHandle; routerContext?: RouteContext<T>; path?: string }; ioOpts?: { routerHandle?: RouterHandle; routerContext?: RouteContext<T>; path?: string };
}; };
export class App<T = {}> { export type AppReqRes = HandleCtx;
/**
* Router Server App http的请求和响应 Cookie Token res
*/
export class App<T = {}, U = AppReqRes> {
router: QueryRouter; router: QueryRouter;
server: Server; server: Server;
io: WsServer; io: WsServer;
@ -55,7 +60,7 @@ export class App<T = {}> {
add = this.addRoute; add = this.addRoute;
Route = Route; Route = Route;
route(opts: RouteOpts): Route; route(opts: RouteOpts): Route<U>;
route(path: string, key?: string): Route; route(path: string, key?: string): Route;
route(path: string, opts?: RouteOpts): Route; route(path: string, opts?: RouteOpts): Route;
route(path: string, key?: string, opts?: RouteOpts): Route; route(path: string, key?: string, opts?: RouteOpts): Route;

View File

@ -69,7 +69,7 @@ export type RouteOpts = {
export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'verify' | 'verifyKey' | 'nextRoute'>; export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'verify' | 'verifyKey' | 'nextRoute'>;
const pickValue = ['path', 'key', 'id', 'description', 'type', 'validator', 'middleware'] as const; const pickValue = ['path', 'key', 'id', 'description', 'type', 'validator', 'middleware'] as const;
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>; export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
export class Route { export class Route<U = { [key: string]: any }> {
path?: string; path?: string;
key?: string; key?: string;
id?: string; id?: string;
@ -207,9 +207,9 @@ export class Route {
return this; return this;
} }
define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this; define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this;
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T>): this; define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & U>): this;
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T>): this; define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & U>): this;
define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T>): this; define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & U>): this;
define(...args: any[]) { define(...args: any[]) {
const [path, key, opts] = args; const [path, key, opts] = args;
// 全覆盖所以opts需要准确不能由idUsePath 需要check的变量 // 全覆盖所以opts需要准确不能由idUsePath 需要check的变量
@ -516,15 +516,19 @@ export class QueryRouter {
}); });
} }
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn<T>, ctx?: RouteContext) { getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn<T>, ctx?: RouteContext) {
return async (msg: { path: string; key?: string; [key: string]: any }) => { return async (msg: { path: string; key?: string; [key: string]: any }, handleContext?: RouteContext) => {
const context = { ...ctx }; try {
const res = await router.parse(msg, context); const context = { ...ctx, ...handleContext };
if (wrapperFn) { const res = await router.parse(msg, context);
res.data = res.body; if (wrapperFn) {
return wrapperFn(res, context); res.data = res.body;
return wrapperFn(res, context);
}
const { code, body, message } = res;
return { code, data: body, message };
} catch (e) {
return { code: 500, message: e.message };
} }
const { code, body, message } = res;
return { code, data: body, message };
}; };
} }
exportRoutes() { exportRoutes() {

View File

@ -1,6 +1,7 @@
import http, { IncomingMessage, Server, ServerResponse } from 'http'; import http, { IncomingMessage, Server, ServerResponse } from 'http';
import { parseBody } from './parse-body.ts'; import { parseBody } from './parse-body.ts';
import url from 'url'; import url from 'url';
import { createHandleCtx } from './server.ts';
/** /**
* get params and body * get params and body
@ -20,9 +21,16 @@ export const handleServer = async (req: IncomingMessage, res: ServerResponse) =>
const parsedUrl = url.parse(req.url, true); const parsedUrl = url.parse(req.url, true);
// 获取token // 获取token
let token = req.headers['authorization'] || ''; let token = req.headers['authorization'] || '';
const handle = createHandleCtx(req, res);
const cookies = handle.req.cookies;
if (!token) {
token = cookies.token;
}
if (token) { if (token) {
token = token.replace('Bearer ', ''); token = token.replace('Bearer ', '');
} }
//@ts-ignore
console.log('token', req.cookies, res.cookie);
// 获取查询参数 // 获取查询参数
const param = parsedUrl.query; const param = parsedUrl.query;
let body: Record<any, any>; let body: Record<any, any>;
@ -41,6 +49,7 @@ export const handleServer = async (req: IncomingMessage, res: ServerResponse) =>
token, token,
...param, ...param,
...body, ...body,
cookies,
}; };
return data; return data;
}; };

View File

@ -2,8 +2,46 @@ import http, { IncomingMessage, ServerResponse } from 'http';
import https from 'https'; import https from 'https';
import http2 from 'http2'; import http2 from 'http2';
import { handleServer } from './handle-server.ts'; import { handleServer } from './handle-server.ts';
import * as cookie from 'cookie';
export type Listener = (...args: any[]) => void; export type Listener = (...args: any[]) => void;
type CookieFn = (name: string, value: string, options?: cookie.SerializeOptions, end?: boolean) => void;
export type HandleCtx = {
req: IncomingMessage & { cookies: Record<string, string> };
res: ServerResponse & {
/**
* cookie end cookie再设置会覆盖前面的
*/
cookie: CookieFn; //
};
};
// 实现函数
export function createHandleCtx(req: IncomingMessage, res: ServerResponse): HandleCtx {
// 用于存储所有的 Set-Cookie 字符串
const cookies: string[] = [];
let handReq = req as HandleCtx['req'];
let handRes = res as HandleCtx['res'];
// 扩展 res.cookie 方法
const cookieFn: CookieFn = (name, value, options = {}, end = true) => {
// 序列化新的 Cookie
const serializedCookie = cookie.serialize(name, value, options);
cookies.push(serializedCookie); // 将新的 Cookie 添加到数组
if (end) {
// 如果设置了 end 参数,则立即设置到响应头
res.setHeader('Set-Cookie', cookies);
}
};
// 解析请求中的现有 Cookie
const parsedCookies = cookie.parse(req.headers.cookie || '');
handReq.cookies = parsedCookies;
handRes.cookie = cookieFn;
// 返回扩展的上下文
return {
req: handReq,
res: handRes,
};
}
export type Cors = { export type Cors = {
/** /**
* @default '*'' * @default '*''
@ -14,7 +52,7 @@ export type ServerOpts = {
/**path default `/api/router` */ /**path default `/api/router` */
path?: string; path?: string;
/**handle Fn */ /**handle Fn */
handle?: (msg?: { path: string; key?: string; [key: string]: any }) => any; handle?: (msg?: { path: string; key?: string; [key: string]: any }, ctx?: { req: http.IncomingMessage; res: http.ServerResponse }) => any;
cors?: Cors; cors?: Cors;
httpType?: 'http' | 'https' | 'http2'; httpType?: 'http' | 'https' | 'http2';
httpsKey?: string; httpsKey?: string;
@ -131,7 +169,7 @@ export class Server {
return; return;
} }
} }
res.writeHead(200); // 设置响应头给予其他任何listen 知道headersSent它已经被响应了 // res.writeHead(200); // 设置响应头给予其他任何listen 知道headersSent它已经被响应了
const url = req.url; const url = req.url;
if (!url.startsWith(path)) { if (!url.startsWith(path)) {
@ -144,7 +182,10 @@ export class Server {
return; return;
} }
try { try {
const end = await handle(messages as any); const end = await handle(messages as any, { req, res });
if (res.writableEnded) {
return;
}
if (typeof end === 'string') { if (typeof end === 'string') {
res.end(end); res.end(end);
} else { } else {