@lytjs/http-server 6.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/index.cjs +411 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +406 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @lytjs/http-server
|
|
2
|
+
|
|
3
|
+
> LytJS HTTP 服务器。
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@lytjs/http-server)
|
|
6
|
+
[](https://gitee.com/lytjs/lytjs/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
## 简介
|
|
9
|
+
|
|
10
|
+
`@lytjs/http-server` 是 LytJS 框架的 HTTP 服务器,支持路由、中间件、静态文件服务等功能。
|
|
11
|
+
|
|
12
|
+
### 核心特性
|
|
13
|
+
|
|
14
|
+
- **RESTful 路由**:支持动态路由和路径参数
|
|
15
|
+
- **中间件支持**:完整的中间件系统
|
|
16
|
+
- **静态文件服务**:支持静态资源服务
|
|
17
|
+
- **Fetch API 兼容**:使用标准的 Request/Response API
|
|
18
|
+
- **零依赖**:不引入任何外部依赖
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @lytjs/http-server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
或使用 pnpm:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @lytjs/http-server
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 许可证
|
|
33
|
+
|
|
34
|
+
MIT License - [查看许可证](https://gitee.com/lytjs/lytjs/blob/main/LICENSE)
|
|
35
|
+
|
|
36
|
+
## 贡献指南
|
|
37
|
+
|
|
38
|
+
欢迎提交 Issue 和 Pull Request!
|
|
39
|
+
|
|
40
|
+
- [Gitee 仓库](https://gitee.com/lytjs/lytjs)
|
|
41
|
+
- [问题反馈](https://gitee.com/lytjs/lytjs/issues)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var http = require('http');
|
|
4
|
+
var commonQuery = require('@lytjs/common-query');
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
|
|
8
|
+
// src/router.ts
|
|
9
|
+
var PARAM_RE = /^:(\w+)(\??)?(\.\.\.)?$/;
|
|
10
|
+
var WILDCARD_RE = /^\*$/;
|
|
11
|
+
function tokenizePath(path) {
|
|
12
|
+
const segments = path.split("/");
|
|
13
|
+
const tokens = [];
|
|
14
|
+
for (const segment of segments) {
|
|
15
|
+
if (!segment) continue;
|
|
16
|
+
const paramMatch = segment.match(PARAM_RE);
|
|
17
|
+
if (paramMatch) {
|
|
18
|
+
const [, name, optional, repeatable] = paramMatch;
|
|
19
|
+
if (name) {
|
|
20
|
+
tokens.push({
|
|
21
|
+
type: "param",
|
|
22
|
+
name,
|
|
23
|
+
repeatable: repeatable === "...",
|
|
24
|
+
optional: optional === "?"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (WILDCARD_RE.test(segment)) {
|
|
30
|
+
tokens.push({ type: "wildcard", value: "*" });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
tokens.push({ type: "static", value: segment });
|
|
34
|
+
}
|
|
35
|
+
return tokens;
|
|
36
|
+
}
|
|
37
|
+
function scoreRoute(tokens) {
|
|
38
|
+
let score = 0;
|
|
39
|
+
for (const token of tokens) {
|
|
40
|
+
switch (token.type) {
|
|
41
|
+
case "static":
|
|
42
|
+
score += 3;
|
|
43
|
+
break;
|
|
44
|
+
case "param":
|
|
45
|
+
score += token.optional ? 1 : 2;
|
|
46
|
+
break;
|
|
47
|
+
case "wildcard":
|
|
48
|
+
score += 0;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return score;
|
|
53
|
+
}
|
|
54
|
+
function matchPath(pathname, tokens, strict = false) {
|
|
55
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
56
|
+
const params = {};
|
|
57
|
+
let matched = true;
|
|
58
|
+
let i = 0;
|
|
59
|
+
for (const token of tokens) {
|
|
60
|
+
if (i >= pathSegments.length) {
|
|
61
|
+
if (token.type === "param" && token.optional) continue;
|
|
62
|
+
if (token.type === "wildcard") continue;
|
|
63
|
+
matched = false;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
const segment = pathSegments[i];
|
|
67
|
+
switch (token.type) {
|
|
68
|
+
case "static":
|
|
69
|
+
if (segment !== token.value) {
|
|
70
|
+
matched = false;
|
|
71
|
+
}
|
|
72
|
+
i++;
|
|
73
|
+
break;
|
|
74
|
+
case "param":
|
|
75
|
+
if (token.repeatable) {
|
|
76
|
+
params[token.name] = pathSegments.slice(i);
|
|
77
|
+
i = pathSegments.length;
|
|
78
|
+
} else if (segment !== void 0) {
|
|
79
|
+
params[token.name] = segment;
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
case "wildcard":
|
|
84
|
+
params[token.value] = pathSegments.slice(i).join("/");
|
|
85
|
+
i = pathSegments.length;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
if (!matched) break;
|
|
89
|
+
}
|
|
90
|
+
if (matched && i < pathSegments.length) {
|
|
91
|
+
matched = false;
|
|
92
|
+
}
|
|
93
|
+
if (!strict && matched && pathSegments.length === 0 && tokens.length === 0) {
|
|
94
|
+
matched = true;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
matched,
|
|
98
|
+
params,
|
|
99
|
+
path: "/" + pathSegments.slice(0, i).join("/"),
|
|
100
|
+
score: scoreRoute(tokens)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
var Router = class {
|
|
104
|
+
constructor() {
|
|
105
|
+
/** 路由列表 */
|
|
106
|
+
this.routes = [];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 添加路由
|
|
110
|
+
*
|
|
111
|
+
* @param method - HTTP 方法
|
|
112
|
+
* @param path - 路径
|
|
113
|
+
* @param handler - 处理器
|
|
114
|
+
* @returns 路由实例
|
|
115
|
+
*/
|
|
116
|
+
on(method, path, handler) {
|
|
117
|
+
const tokens = tokenizePath(path);
|
|
118
|
+
this.routes.push({ method, path, tokens, handler });
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 添加 GET 路由
|
|
123
|
+
*
|
|
124
|
+
* @param path - 路径
|
|
125
|
+
* @param handler - 处理器
|
|
126
|
+
* @returns 路由实例
|
|
127
|
+
*/
|
|
128
|
+
get(path, handler) {
|
|
129
|
+
return this.on("GET", path, handler);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 添加 POST 路由
|
|
133
|
+
*
|
|
134
|
+
* @param path - 路径
|
|
135
|
+
* @param handler - 处理器
|
|
136
|
+
* @returns 路由实例
|
|
137
|
+
*/
|
|
138
|
+
post(path, handler) {
|
|
139
|
+
return this.on("POST", path, handler);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 添加 PUT 路由
|
|
143
|
+
*
|
|
144
|
+
* @param path - 路径
|
|
145
|
+
* @param handler - 处理器
|
|
146
|
+
* @returns 路由实例
|
|
147
|
+
*/
|
|
148
|
+
put(path, handler) {
|
|
149
|
+
return this.on("PUT", path, handler);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 添加 PATCH 路由
|
|
153
|
+
*
|
|
154
|
+
* @param path - 路径
|
|
155
|
+
* @param handler - 处理器
|
|
156
|
+
* @returns 路由实例
|
|
157
|
+
*/
|
|
158
|
+
patch(path, handler) {
|
|
159
|
+
return this.on("PATCH", path, handler);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 添加 DELETE 路由
|
|
163
|
+
*
|
|
164
|
+
* @param path - 路径
|
|
165
|
+
* @param handler - 处理器
|
|
166
|
+
* @returns 路由实例
|
|
167
|
+
*/
|
|
168
|
+
delete(path, handler) {
|
|
169
|
+
return this.on("DELETE", path, handler);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 匹配路由
|
|
173
|
+
*
|
|
174
|
+
* @param method - HTTP 方法
|
|
175
|
+
* @param path - 路径
|
|
176
|
+
* @returns 匹配结果或 null
|
|
177
|
+
*/
|
|
178
|
+
match(method, path) {
|
|
179
|
+
const candidates = this.routes.filter((r) => r.method === method);
|
|
180
|
+
let bestMatch = null;
|
|
181
|
+
let bestScore = -1;
|
|
182
|
+
for (const route of candidates) {
|
|
183
|
+
const result = matchPath(path, route.tokens);
|
|
184
|
+
if (result.matched && result.score > bestScore) {
|
|
185
|
+
bestScore = result.score;
|
|
186
|
+
bestMatch = { handler: route.handler, params: result.params };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return bestMatch;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
function createRouter() {
|
|
193
|
+
return new Router();
|
|
194
|
+
}
|
|
195
|
+
var Server = class {
|
|
196
|
+
/**
|
|
197
|
+
* 构造函数
|
|
198
|
+
*/
|
|
199
|
+
constructor() {
|
|
200
|
+
/** 中间件列表 */
|
|
201
|
+
this.middlewares = [];
|
|
202
|
+
this.router = new Router();
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 添加中间件
|
|
206
|
+
*
|
|
207
|
+
* @param middleware - 中间件函数
|
|
208
|
+
* @returns 服务器实例
|
|
209
|
+
*/
|
|
210
|
+
use(middleware) {
|
|
211
|
+
this.middlewares.push(middleware);
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 添加路由
|
|
216
|
+
*
|
|
217
|
+
* @param method - HTTP 方法
|
|
218
|
+
* @param path - 路径
|
|
219
|
+
* @param handler - 处理器
|
|
220
|
+
* @returns 服务器实例
|
|
221
|
+
*/
|
|
222
|
+
on(method, path, handler) {
|
|
223
|
+
this.router.on(method, path, handler);
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* 添加 GET 路由
|
|
228
|
+
*
|
|
229
|
+
* @param path - 路径
|
|
230
|
+
* @param handler - 处理器
|
|
231
|
+
* @returns 服务器实例
|
|
232
|
+
*/
|
|
233
|
+
get(path, handler) {
|
|
234
|
+
return this.on("GET", path, handler);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* 添加 POST 路由
|
|
238
|
+
*
|
|
239
|
+
* @param path - 路径
|
|
240
|
+
* @param handler - 处理器
|
|
241
|
+
* @returns 服务器实例
|
|
242
|
+
*/
|
|
243
|
+
post(path, handler) {
|
|
244
|
+
return this.on("POST", path, handler);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 添加 PUT 路由
|
|
248
|
+
*
|
|
249
|
+
* @param path - 路径
|
|
250
|
+
* @param handler - 处理器
|
|
251
|
+
* @returns 服务器实例
|
|
252
|
+
*/
|
|
253
|
+
put(path, handler) {
|
|
254
|
+
return this.on("PUT", path, handler);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* 添加 PATCH 路由
|
|
258
|
+
*
|
|
259
|
+
* @param path - 路径
|
|
260
|
+
* @param handler - 处理器
|
|
261
|
+
* @returns 服务器实例
|
|
262
|
+
*/
|
|
263
|
+
patch(path, handler) {
|
|
264
|
+
return this.on("PATCH", path, handler);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 添加 DELETE 路由
|
|
268
|
+
*
|
|
269
|
+
* @param path - 路径
|
|
270
|
+
* @param handler - 处理器
|
|
271
|
+
* @returns 服务器实例
|
|
272
|
+
*/
|
|
273
|
+
delete(path, handler) {
|
|
274
|
+
return this.on("DELETE", path, handler);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* 启动服务器监听
|
|
278
|
+
*
|
|
279
|
+
* @param port - 端口
|
|
280
|
+
* @param hostname - 主机名
|
|
281
|
+
* @returns Promise
|
|
282
|
+
*/
|
|
283
|
+
listen(port, hostname) {
|
|
284
|
+
return new Promise((resolve) => {
|
|
285
|
+
this.server = http.createServer(this.handleRequest.bind(this));
|
|
286
|
+
this.server.listen(port, hostname, () => {
|
|
287
|
+
resolve();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* 关闭服务器
|
|
293
|
+
*
|
|
294
|
+
* @returns Promise
|
|
295
|
+
*/
|
|
296
|
+
close() {
|
|
297
|
+
return new Promise((resolve, reject) => {
|
|
298
|
+
if (this.server) {
|
|
299
|
+
this.server.close((err) => {
|
|
300
|
+
if (err) {
|
|
301
|
+
reject(err);
|
|
302
|
+
} else {
|
|
303
|
+
resolve();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
} else {
|
|
307
|
+
resolve();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* 处理请求
|
|
313
|
+
*
|
|
314
|
+
* @param req - Node.js 请求对象
|
|
315
|
+
* @param res - Node.js 响应对象
|
|
316
|
+
*/
|
|
317
|
+
async handleRequest(req, res) {
|
|
318
|
+
const url = req.url || "/";
|
|
319
|
+
const path = url.split("?")[0] || "/";
|
|
320
|
+
const method = req.method || "GET";
|
|
321
|
+
const request = {
|
|
322
|
+
method,
|
|
323
|
+
url,
|
|
324
|
+
path,
|
|
325
|
+
headers: req.headers,
|
|
326
|
+
query: commonQuery.parseQueryStringWithArrays(url),
|
|
327
|
+
params: {},
|
|
328
|
+
ip: req.socket.remoteAddress
|
|
329
|
+
};
|
|
330
|
+
const response = {
|
|
331
|
+
status: 200,
|
|
332
|
+
headers: {}
|
|
333
|
+
};
|
|
334
|
+
const ctx = {
|
|
335
|
+
request,
|
|
336
|
+
response
|
|
337
|
+
};
|
|
338
|
+
const match = this.router.match(method, path);
|
|
339
|
+
if (match) {
|
|
340
|
+
ctx.request.params = {};
|
|
341
|
+
for (const [key, value] of Object.entries(match.params)) {
|
|
342
|
+
if (Array.isArray(value)) {
|
|
343
|
+
ctx.request.params[key] = value[0] || "";
|
|
344
|
+
} else {
|
|
345
|
+
ctx.request.params[key] = value;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const handler = async () => {
|
|
349
|
+
await match.handler(ctx);
|
|
350
|
+
};
|
|
351
|
+
let index = this.middlewares.length;
|
|
352
|
+
const next = async () => {
|
|
353
|
+
index--;
|
|
354
|
+
if (index >= 0 && this.middlewares[index]) {
|
|
355
|
+
await this.middlewares[index](ctx, next);
|
|
356
|
+
} else {
|
|
357
|
+
await handler();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
await next();
|
|
361
|
+
} else {
|
|
362
|
+
if (ctx.response) {
|
|
363
|
+
ctx.response.status = 404;
|
|
364
|
+
ctx.response.body = { error: "\u672A\u627E\u5230" };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (ctx.response) {
|
|
368
|
+
this.sendResponse(res, ctx.response);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* 发送响应
|
|
373
|
+
*
|
|
374
|
+
* @param res - Node.js 响应对象
|
|
375
|
+
* @param response - 响应对象
|
|
376
|
+
*/
|
|
377
|
+
sendResponse(res, response) {
|
|
378
|
+
res.statusCode = response.status;
|
|
379
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
380
|
+
if (Array.isArray(value)) {
|
|
381
|
+
for (const v of value) {
|
|
382
|
+
res.setHeader(key, v);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
res.setHeader(key, value);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (response.body !== void 0) {
|
|
389
|
+
const body = typeof response.body === "string" ? response.body : JSON.stringify(response.body);
|
|
390
|
+
if (!response.headers["content-type"]) {
|
|
391
|
+
res.setHeader(
|
|
392
|
+
"content-type",
|
|
393
|
+
typeof response.body === "string" ? "text/plain" : "application/json"
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
res.end(body);
|
|
397
|
+
} else {
|
|
398
|
+
res.end();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function createServer() {
|
|
403
|
+
return new Server();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
exports.Router = Router;
|
|
407
|
+
exports.Server = Server;
|
|
408
|
+
exports.createRouter = createRouter;
|
|
409
|
+
exports.createServer = createServer;
|
|
410
|
+
//# sourceMappingURL=index.cjs.map
|
|
411
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/router.ts","../src/server.ts"],"names":["createNodeServer","parseQueryStringWithArrays"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * HTTP 路由实现\n */\nimport type { Handler } from './types';\nimport type { HttpMethod } from '@lytjs/shared-types';\n\n// ===== Token Types =====\n\ninterface TokenStatic {\n type: 'static';\n value: string;\n}\n\ninterface TokenParam {\n type: 'param';\n name: string;\n repeatable: boolean;\n optional: boolean;\n}\n\ninterface TokenWildcard {\n type: 'wildcard';\n value: string;\n}\n\ntype PathToken = TokenStatic | TokenParam | TokenWildcard;\n\n// ===== Path Tokenizer =====\n\nconst PARAM_RE = /^:(\\w+)(\\??)?(\\.\\.\\.)?$/;\nconst WILDCARD_RE = /^\\*$/;\n\n/**\n * Tokenize a path segment string into tokens\n */\nfunction tokenizePath(path: string): PathToken[] {\n const segments = path.split('/');\n const tokens: PathToken[] = [];\n\n for (const segment of segments) {\n if (!segment) continue;\n\n const paramMatch = segment.match(PARAM_RE);\n if (paramMatch) {\n const [, name, optional, repeatable] = paramMatch;\n if (name) {\n tokens.push({\n type: 'param',\n name,\n repeatable: repeatable === '...',\n optional: optional === '?',\n });\n }\n continue;\n }\n\n if (WILDCARD_RE.test(segment)) {\n tokens.push({ type: 'wildcard', value: '*' });\n continue;\n }\n\n tokens.push({ type: 'static', value: segment });\n }\n\n return tokens;\n}\n\n// ===== Path Scoring =====\n\n/**\n * Score a route record for ranking (higher = more specific)\n */\nfunction scoreRoute(tokens: PathToken[]): number {\n let score = 0;\n for (const token of tokens) {\n switch (token.type) {\n case 'static':\n score += 3;\n break;\n case 'param':\n score += token.optional ? 1 : 2;\n break;\n case 'wildcard':\n score += 0;\n break;\n }\n }\n return score;\n}\n\n// ===== Path Matching =====\n\ninterface PathMatchResult {\n matched: boolean;\n params: Record<string, string | string[]>;\n path: string;\n score: number;\n}\n\n/**\n * Match a pathname against a tokenized route\n */\nfunction matchPath(\n pathname: string,\n tokens: PathToken[],\n strict: boolean = false,\n): PathMatchResult {\n const pathSegments = pathname.split('/').filter(Boolean);\n const params: Record<string, string | string[]> = {};\n let matched = true;\n let i = 0;\n\n for (const token of tokens) {\n if (i >= pathSegments.length) {\n if (token.type === 'param' && token.optional) continue;\n if (token.type === 'wildcard') continue;\n matched = false;\n break;\n }\n\n const segment = pathSegments[i];\n\n switch (token.type) {\n case 'static':\n if (segment !== token.value) {\n matched = false;\n }\n i++;\n break;\n\n case 'param':\n if (token.repeatable) {\n params[token.name] = pathSegments.slice(i);\n i = pathSegments.length;\n } else if (segment !== undefined) {\n params[token.name] = segment;\n i++;\n }\n break;\n\n case 'wildcard':\n params[token.value] = pathSegments.slice(i).join('/');\n i = pathSegments.length;\n break;\n }\n\n if (!matched) break;\n }\n\n // Check if all path segments were consumed\n if (matched && i < pathSegments.length) {\n matched = false;\n }\n\n // In non-strict mode, trailing slash is ok\n if (!strict && matched && pathSegments.length === 0 && tokens.length === 0) {\n matched = true;\n }\n\n return {\n matched,\n params,\n path: '/' + pathSegments.slice(0, i).join('/'),\n score: scoreRoute(tokens),\n };\n}\n\n/**\n * 路由类\n */\nexport class Router {\n /** 路由列表 */\n private routes: Array<{\n method: HttpMethod;\n path: string;\n tokens: ReturnType<typeof tokenizePath>;\n handler: Handler;\n }> = [];\n\n /**\n * 添加路由\n *\n * @param method - HTTP 方法\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n on(method: HttpMethod, path: string, handler: Handler): this {\n const tokens = tokenizePath(path);\n this.routes.push({ method, path, tokens, handler });\n return this;\n }\n\n /**\n * 添加 GET 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n get(path: string, handler: Handler): this {\n return this.on('GET', path, handler);\n }\n\n /**\n * 添加 POST 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n post(path: string, handler: Handler): this {\n return this.on('POST', path, handler);\n }\n\n /**\n * 添加 PUT 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n put(path: string, handler: Handler): this {\n return this.on('PUT', path, handler);\n }\n\n /**\n * 添加 PATCH 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n patch(path: string, handler: Handler): this {\n return this.on('PATCH', path, handler);\n }\n\n /**\n * 添加 DELETE 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n delete(path: string, handler: Handler): this {\n return this.on('DELETE', path, handler);\n }\n\n /**\n * 匹配路由\n *\n * @param method - HTTP 方法\n * @param path - 路径\n * @returns 匹配结果或 null\n */\n match(\n method: HttpMethod,\n path: string,\n ): { handler: Handler; params: Record<string, string | string[]> } | null {\n const candidates = this.routes.filter((r) => r.method === method);\n\n let bestMatch: { handler: Handler; params: Record<string, string | string[]> } | null = null;\n let bestScore = -1;\n\n for (const route of candidates) {\n const result = matchPath(path, route.tokens);\n if (result.matched && result.score > bestScore) {\n bestScore = result.score;\n bestMatch = { handler: route.handler, params: result.params };\n }\n }\n\n return bestMatch;\n }\n}\n\n/**\n * 创建路由\n *\n * @returns 路由实例\n */\nexport function createRouter(): Router {\n return new Router();\n}\n","/**\n * HTTP 服务器实现\n */\nimport type { Server as NodeServer, IncomingMessage, ServerResponse } from 'node:http';\nimport { createServer as createNodeServer } from 'node:http';\nimport type { Handler } from './types';\nimport type {\n HttpMethod,\n HttpContext as Context,\n HttpRequest as Request,\n HttpResponse as Response,\n} from '@lytjs/shared-types';\nimport { Router } from './router';\nimport { parseQueryStringWithArrays } from '@lytjs/common-query';\n\n/**\n * HTTP 服务器类\n */\nexport class Server {\n /** 路由实例 */\n private router: Router;\n /** 中间件列表 */\n private middlewares: ((ctx: Context, next: () => Promise<void>) => Promise<void>)[] = [];\n /** Node.js 服务器实例 */\n private server?: NodeServer;\n\n /**\n * 构造函数\n */\n constructor() {\n this.router = new Router();\n }\n\n /**\n * 添加中间件\n *\n * @param middleware - 中间件函数\n * @returns 服务器实例\n */\n use(middleware: (ctx: Context, next: () => Promise<void>) => Promise<void>): this {\n this.middlewares.push(middleware);\n return this;\n }\n\n /**\n * 添加路由\n *\n * @param method - HTTP 方法\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n on(method: HttpMethod, path: string, handler: Handler): this {\n this.router.on(method, path, handler);\n return this;\n }\n\n /**\n * 添加 GET 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n get(path: string, handler: Handler): this {\n return this.on('GET', path, handler);\n }\n\n /**\n * 添加 POST 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n post(path: string, handler: Handler): this {\n return this.on('POST', path, handler);\n }\n\n /**\n * 添加 PUT 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n put(path: string, handler: Handler): this {\n return this.on('PUT', path, handler);\n }\n\n /**\n * 添加 PATCH 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n patch(path: string, handler: Handler): this {\n return this.on('PATCH', path, handler);\n }\n\n /**\n * 添加 DELETE 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n delete(path: string, handler: Handler): this {\n return this.on('DELETE', path, handler);\n }\n\n /**\n * 启动服务器监听\n *\n * @param port - 端口\n * @param hostname - 主机名\n * @returns Promise\n */\n listen(port: number, hostname?: string): Promise<void> {\n return new Promise((resolve) => {\n this.server = createNodeServer(this.handleRequest.bind(this));\n this.server.listen(port, hostname, () => {\n resolve();\n });\n });\n }\n\n /**\n * 关闭服务器\n *\n * @returns Promise\n */\n close(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.server) {\n this.server.close((err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n } else {\n resolve();\n }\n });\n }\n\n /**\n * 处理请求\n *\n * @param req - Node.js 请求对象\n * @param res - Node.js 响应对象\n */\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = req.url || '/';\n const path = url.split('?')[0] || '/';\n const method = (req.method || 'GET') as HttpMethod;\n\n const request: Request = {\n method,\n url,\n path,\n headers: req.headers,\n query: parseQueryStringWithArrays(url),\n params: {},\n ip: req.socket.remoteAddress,\n };\n\n const response: Response = {\n status: 200,\n headers: {},\n };\n\n const ctx: Context = {\n request,\n response,\n };\n\n const match = this.router.match(method, path);\n\n if (match) {\n // 转换类型,确保兼容性\n ctx.request.params = {};\n for (const [key, value] of Object.entries(match.params)) {\n if (Array.isArray(value)) {\n // 如果是数组,只取第一个元素\n ctx.request.params[key] = value[0] || '';\n } else {\n ctx.request.params[key] = value;\n }\n }\n\n const handler = async () => {\n await match.handler(ctx);\n };\n\n let index = this.middlewares.length;\n const next = async () => {\n index--;\n if (index >= 0 && this.middlewares[index]) {\n await this.middlewares[index](ctx, next);\n } else {\n await handler();\n }\n };\n\n await next();\n } else {\n if (ctx.response) {\n ctx.response.status = 404;\n ctx.response.body = { error: '未找到' };\n }\n }\n\n if (ctx.response) {\n this.sendResponse(res, ctx.response);\n }\n }\n\n /**\n * 发送响应\n *\n * @param res - Node.js 响应对象\n * @param response - 响应对象\n */\n private sendResponse(res: ServerResponse, response: Response): void {\n res.statusCode = response.status;\n\n for (const [key, value] of Object.entries(response.headers)) {\n if (Array.isArray(value)) {\n for (const v of value) {\n res.setHeader(key, v);\n }\n } else {\n res.setHeader(key, value);\n }\n }\n\n if (response.body !== undefined) {\n const body =\n typeof response.body === 'string' ? response.body : JSON.stringify(response.body);\n\n if (!response.headers['content-type']) {\n res.setHeader(\n 'content-type',\n typeof response.body === 'string' ? 'text/plain' : 'application/json',\n );\n }\n\n res.end(body);\n } else {\n res.end();\n }\n }\n}\n\n/**\n * 创建 HTTP 服务器\n *\n * @returns 服务器实例\n */\nexport function createServer(): Server {\n return new Server();\n}\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { createServer as createServer$1 } from 'http';
|
|
2
|
+
import { parseQueryStringWithArrays } from '@lytjs/common-query';
|
|
3
|
+
|
|
4
|
+
// src/server.ts
|
|
5
|
+
|
|
6
|
+
// src/router.ts
|
|
7
|
+
var PARAM_RE = /^:(\w+)(\??)?(\.\.\.)?$/;
|
|
8
|
+
var WILDCARD_RE = /^\*$/;
|
|
9
|
+
function tokenizePath(path) {
|
|
10
|
+
const segments = path.split("/");
|
|
11
|
+
const tokens = [];
|
|
12
|
+
for (const segment of segments) {
|
|
13
|
+
if (!segment) continue;
|
|
14
|
+
const paramMatch = segment.match(PARAM_RE);
|
|
15
|
+
if (paramMatch) {
|
|
16
|
+
const [, name, optional, repeatable] = paramMatch;
|
|
17
|
+
if (name) {
|
|
18
|
+
tokens.push({
|
|
19
|
+
type: "param",
|
|
20
|
+
name,
|
|
21
|
+
repeatable: repeatable === "...",
|
|
22
|
+
optional: optional === "?"
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (WILDCARD_RE.test(segment)) {
|
|
28
|
+
tokens.push({ type: "wildcard", value: "*" });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
tokens.push({ type: "static", value: segment });
|
|
32
|
+
}
|
|
33
|
+
return tokens;
|
|
34
|
+
}
|
|
35
|
+
function scoreRoute(tokens) {
|
|
36
|
+
let score = 0;
|
|
37
|
+
for (const token of tokens) {
|
|
38
|
+
switch (token.type) {
|
|
39
|
+
case "static":
|
|
40
|
+
score += 3;
|
|
41
|
+
break;
|
|
42
|
+
case "param":
|
|
43
|
+
score += token.optional ? 1 : 2;
|
|
44
|
+
break;
|
|
45
|
+
case "wildcard":
|
|
46
|
+
score += 0;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return score;
|
|
51
|
+
}
|
|
52
|
+
function matchPath(pathname, tokens, strict = false) {
|
|
53
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
54
|
+
const params = {};
|
|
55
|
+
let matched = true;
|
|
56
|
+
let i = 0;
|
|
57
|
+
for (const token of tokens) {
|
|
58
|
+
if (i >= pathSegments.length) {
|
|
59
|
+
if (token.type === "param" && token.optional) continue;
|
|
60
|
+
if (token.type === "wildcard") continue;
|
|
61
|
+
matched = false;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
const segment = pathSegments[i];
|
|
65
|
+
switch (token.type) {
|
|
66
|
+
case "static":
|
|
67
|
+
if (segment !== token.value) {
|
|
68
|
+
matched = false;
|
|
69
|
+
}
|
|
70
|
+
i++;
|
|
71
|
+
break;
|
|
72
|
+
case "param":
|
|
73
|
+
if (token.repeatable) {
|
|
74
|
+
params[token.name] = pathSegments.slice(i);
|
|
75
|
+
i = pathSegments.length;
|
|
76
|
+
} else if (segment !== void 0) {
|
|
77
|
+
params[token.name] = segment;
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case "wildcard":
|
|
82
|
+
params[token.value] = pathSegments.slice(i).join("/");
|
|
83
|
+
i = pathSegments.length;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (!matched) break;
|
|
87
|
+
}
|
|
88
|
+
if (matched && i < pathSegments.length) {
|
|
89
|
+
matched = false;
|
|
90
|
+
}
|
|
91
|
+
if (!strict && matched && pathSegments.length === 0 && tokens.length === 0) {
|
|
92
|
+
matched = true;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
matched,
|
|
96
|
+
params,
|
|
97
|
+
path: "/" + pathSegments.slice(0, i).join("/"),
|
|
98
|
+
score: scoreRoute(tokens)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
var Router = class {
|
|
102
|
+
constructor() {
|
|
103
|
+
/** 路由列表 */
|
|
104
|
+
this.routes = [];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 添加路由
|
|
108
|
+
*
|
|
109
|
+
* @param method - HTTP 方法
|
|
110
|
+
* @param path - 路径
|
|
111
|
+
* @param handler - 处理器
|
|
112
|
+
* @returns 路由实例
|
|
113
|
+
*/
|
|
114
|
+
on(method, path, handler) {
|
|
115
|
+
const tokens = tokenizePath(path);
|
|
116
|
+
this.routes.push({ method, path, tokens, handler });
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 添加 GET 路由
|
|
121
|
+
*
|
|
122
|
+
* @param path - 路径
|
|
123
|
+
* @param handler - 处理器
|
|
124
|
+
* @returns 路由实例
|
|
125
|
+
*/
|
|
126
|
+
get(path, handler) {
|
|
127
|
+
return this.on("GET", path, handler);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 添加 POST 路由
|
|
131
|
+
*
|
|
132
|
+
* @param path - 路径
|
|
133
|
+
* @param handler - 处理器
|
|
134
|
+
* @returns 路由实例
|
|
135
|
+
*/
|
|
136
|
+
post(path, handler) {
|
|
137
|
+
return this.on("POST", path, handler);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 添加 PUT 路由
|
|
141
|
+
*
|
|
142
|
+
* @param path - 路径
|
|
143
|
+
* @param handler - 处理器
|
|
144
|
+
* @returns 路由实例
|
|
145
|
+
*/
|
|
146
|
+
put(path, handler) {
|
|
147
|
+
return this.on("PUT", path, handler);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 添加 PATCH 路由
|
|
151
|
+
*
|
|
152
|
+
* @param path - 路径
|
|
153
|
+
* @param handler - 处理器
|
|
154
|
+
* @returns 路由实例
|
|
155
|
+
*/
|
|
156
|
+
patch(path, handler) {
|
|
157
|
+
return this.on("PATCH", path, handler);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 添加 DELETE 路由
|
|
161
|
+
*
|
|
162
|
+
* @param path - 路径
|
|
163
|
+
* @param handler - 处理器
|
|
164
|
+
* @returns 路由实例
|
|
165
|
+
*/
|
|
166
|
+
delete(path, handler) {
|
|
167
|
+
return this.on("DELETE", path, handler);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 匹配路由
|
|
171
|
+
*
|
|
172
|
+
* @param method - HTTP 方法
|
|
173
|
+
* @param path - 路径
|
|
174
|
+
* @returns 匹配结果或 null
|
|
175
|
+
*/
|
|
176
|
+
match(method, path) {
|
|
177
|
+
const candidates = this.routes.filter((r) => r.method === method);
|
|
178
|
+
let bestMatch = null;
|
|
179
|
+
let bestScore = -1;
|
|
180
|
+
for (const route of candidates) {
|
|
181
|
+
const result = matchPath(path, route.tokens);
|
|
182
|
+
if (result.matched && result.score > bestScore) {
|
|
183
|
+
bestScore = result.score;
|
|
184
|
+
bestMatch = { handler: route.handler, params: result.params };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return bestMatch;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
function createRouter() {
|
|
191
|
+
return new Router();
|
|
192
|
+
}
|
|
193
|
+
var Server = class {
|
|
194
|
+
/**
|
|
195
|
+
* 构造函数
|
|
196
|
+
*/
|
|
197
|
+
constructor() {
|
|
198
|
+
/** 中间件列表 */
|
|
199
|
+
this.middlewares = [];
|
|
200
|
+
this.router = new Router();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 添加中间件
|
|
204
|
+
*
|
|
205
|
+
* @param middleware - 中间件函数
|
|
206
|
+
* @returns 服务器实例
|
|
207
|
+
*/
|
|
208
|
+
use(middleware) {
|
|
209
|
+
this.middlewares.push(middleware);
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 添加路由
|
|
214
|
+
*
|
|
215
|
+
* @param method - HTTP 方法
|
|
216
|
+
* @param path - 路径
|
|
217
|
+
* @param handler - 处理器
|
|
218
|
+
* @returns 服务器实例
|
|
219
|
+
*/
|
|
220
|
+
on(method, path, handler) {
|
|
221
|
+
this.router.on(method, path, handler);
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* 添加 GET 路由
|
|
226
|
+
*
|
|
227
|
+
* @param path - 路径
|
|
228
|
+
* @param handler - 处理器
|
|
229
|
+
* @returns 服务器实例
|
|
230
|
+
*/
|
|
231
|
+
get(path, handler) {
|
|
232
|
+
return this.on("GET", path, handler);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 添加 POST 路由
|
|
236
|
+
*
|
|
237
|
+
* @param path - 路径
|
|
238
|
+
* @param handler - 处理器
|
|
239
|
+
* @returns 服务器实例
|
|
240
|
+
*/
|
|
241
|
+
post(path, handler) {
|
|
242
|
+
return this.on("POST", path, handler);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 添加 PUT 路由
|
|
246
|
+
*
|
|
247
|
+
* @param path - 路径
|
|
248
|
+
* @param handler - 处理器
|
|
249
|
+
* @returns 服务器实例
|
|
250
|
+
*/
|
|
251
|
+
put(path, handler) {
|
|
252
|
+
return this.on("PUT", path, handler);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 添加 PATCH 路由
|
|
256
|
+
*
|
|
257
|
+
* @param path - 路径
|
|
258
|
+
* @param handler - 处理器
|
|
259
|
+
* @returns 服务器实例
|
|
260
|
+
*/
|
|
261
|
+
patch(path, handler) {
|
|
262
|
+
return this.on("PATCH", path, handler);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 添加 DELETE 路由
|
|
266
|
+
*
|
|
267
|
+
* @param path - 路径
|
|
268
|
+
* @param handler - 处理器
|
|
269
|
+
* @returns 服务器实例
|
|
270
|
+
*/
|
|
271
|
+
delete(path, handler) {
|
|
272
|
+
return this.on("DELETE", path, handler);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 启动服务器监听
|
|
276
|
+
*
|
|
277
|
+
* @param port - 端口
|
|
278
|
+
* @param hostname - 主机名
|
|
279
|
+
* @returns Promise
|
|
280
|
+
*/
|
|
281
|
+
listen(port, hostname) {
|
|
282
|
+
return new Promise((resolve) => {
|
|
283
|
+
this.server = createServer$1(this.handleRequest.bind(this));
|
|
284
|
+
this.server.listen(port, hostname, () => {
|
|
285
|
+
resolve();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 关闭服务器
|
|
291
|
+
*
|
|
292
|
+
* @returns Promise
|
|
293
|
+
*/
|
|
294
|
+
close() {
|
|
295
|
+
return new Promise((resolve, reject) => {
|
|
296
|
+
if (this.server) {
|
|
297
|
+
this.server.close((err) => {
|
|
298
|
+
if (err) {
|
|
299
|
+
reject(err);
|
|
300
|
+
} else {
|
|
301
|
+
resolve();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
} else {
|
|
305
|
+
resolve();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 处理请求
|
|
311
|
+
*
|
|
312
|
+
* @param req - Node.js 请求对象
|
|
313
|
+
* @param res - Node.js 响应对象
|
|
314
|
+
*/
|
|
315
|
+
async handleRequest(req, res) {
|
|
316
|
+
const url = req.url || "/";
|
|
317
|
+
const path = url.split("?")[0] || "/";
|
|
318
|
+
const method = req.method || "GET";
|
|
319
|
+
const request = {
|
|
320
|
+
method,
|
|
321
|
+
url,
|
|
322
|
+
path,
|
|
323
|
+
headers: req.headers,
|
|
324
|
+
query: parseQueryStringWithArrays(url),
|
|
325
|
+
params: {},
|
|
326
|
+
ip: req.socket.remoteAddress
|
|
327
|
+
};
|
|
328
|
+
const response = {
|
|
329
|
+
status: 200,
|
|
330
|
+
headers: {}
|
|
331
|
+
};
|
|
332
|
+
const ctx = {
|
|
333
|
+
request,
|
|
334
|
+
response
|
|
335
|
+
};
|
|
336
|
+
const match = this.router.match(method, path);
|
|
337
|
+
if (match) {
|
|
338
|
+
ctx.request.params = {};
|
|
339
|
+
for (const [key, value] of Object.entries(match.params)) {
|
|
340
|
+
if (Array.isArray(value)) {
|
|
341
|
+
ctx.request.params[key] = value[0] || "";
|
|
342
|
+
} else {
|
|
343
|
+
ctx.request.params[key] = value;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const handler = async () => {
|
|
347
|
+
await match.handler(ctx);
|
|
348
|
+
};
|
|
349
|
+
let index = this.middlewares.length;
|
|
350
|
+
const next = async () => {
|
|
351
|
+
index--;
|
|
352
|
+
if (index >= 0 && this.middlewares[index]) {
|
|
353
|
+
await this.middlewares[index](ctx, next);
|
|
354
|
+
} else {
|
|
355
|
+
await handler();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
await next();
|
|
359
|
+
} else {
|
|
360
|
+
if (ctx.response) {
|
|
361
|
+
ctx.response.status = 404;
|
|
362
|
+
ctx.response.body = { error: "\u672A\u627E\u5230" };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (ctx.response) {
|
|
366
|
+
this.sendResponse(res, ctx.response);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* 发送响应
|
|
371
|
+
*
|
|
372
|
+
* @param res - Node.js 响应对象
|
|
373
|
+
* @param response - 响应对象
|
|
374
|
+
*/
|
|
375
|
+
sendResponse(res, response) {
|
|
376
|
+
res.statusCode = response.status;
|
|
377
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
378
|
+
if (Array.isArray(value)) {
|
|
379
|
+
for (const v of value) {
|
|
380
|
+
res.setHeader(key, v);
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
res.setHeader(key, value);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (response.body !== void 0) {
|
|
387
|
+
const body = typeof response.body === "string" ? response.body : JSON.stringify(response.body);
|
|
388
|
+
if (!response.headers["content-type"]) {
|
|
389
|
+
res.setHeader(
|
|
390
|
+
"content-type",
|
|
391
|
+
typeof response.body === "string" ? "text/plain" : "application/json"
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
res.end(body);
|
|
395
|
+
} else {
|
|
396
|
+
res.end();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
function createServer() {
|
|
401
|
+
return new Server();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export { Router, Server, createRouter, createServer };
|
|
405
|
+
//# sourceMappingURL=index.mjs.map
|
|
406
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/router.ts","../src/server.ts"],"names":["createNodeServer"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * HTTP 路由实现\n */\nimport type { Handler } from './types';\nimport type { HttpMethod } from '@lytjs/shared-types';\n\n// ===== Token Types =====\n\ninterface TokenStatic {\n type: 'static';\n value: string;\n}\n\ninterface TokenParam {\n type: 'param';\n name: string;\n repeatable: boolean;\n optional: boolean;\n}\n\ninterface TokenWildcard {\n type: 'wildcard';\n value: string;\n}\n\ntype PathToken = TokenStatic | TokenParam | TokenWildcard;\n\n// ===== Path Tokenizer =====\n\nconst PARAM_RE = /^:(\\w+)(\\??)?(\\.\\.\\.)?$/;\nconst WILDCARD_RE = /^\\*$/;\n\n/**\n * Tokenize a path segment string into tokens\n */\nfunction tokenizePath(path: string): PathToken[] {\n const segments = path.split('/');\n const tokens: PathToken[] = [];\n\n for (const segment of segments) {\n if (!segment) continue;\n\n const paramMatch = segment.match(PARAM_RE);\n if (paramMatch) {\n const [, name, optional, repeatable] = paramMatch;\n if (name) {\n tokens.push({\n type: 'param',\n name,\n repeatable: repeatable === '...',\n optional: optional === '?',\n });\n }\n continue;\n }\n\n if (WILDCARD_RE.test(segment)) {\n tokens.push({ type: 'wildcard', value: '*' });\n continue;\n }\n\n tokens.push({ type: 'static', value: segment });\n }\n\n return tokens;\n}\n\n// ===== Path Scoring =====\n\n/**\n * Score a route record for ranking (higher = more specific)\n */\nfunction scoreRoute(tokens: PathToken[]): number {\n let score = 0;\n for (const token of tokens) {\n switch (token.type) {\n case 'static':\n score += 3;\n break;\n case 'param':\n score += token.optional ? 1 : 2;\n break;\n case 'wildcard':\n score += 0;\n break;\n }\n }\n return score;\n}\n\n// ===== Path Matching =====\n\ninterface PathMatchResult {\n matched: boolean;\n params: Record<string, string | string[]>;\n path: string;\n score: number;\n}\n\n/**\n * Match a pathname against a tokenized route\n */\nfunction matchPath(\n pathname: string,\n tokens: PathToken[],\n strict: boolean = false,\n): PathMatchResult {\n const pathSegments = pathname.split('/').filter(Boolean);\n const params: Record<string, string | string[]> = {};\n let matched = true;\n let i = 0;\n\n for (const token of tokens) {\n if (i >= pathSegments.length) {\n if (token.type === 'param' && token.optional) continue;\n if (token.type === 'wildcard') continue;\n matched = false;\n break;\n }\n\n const segment = pathSegments[i];\n\n switch (token.type) {\n case 'static':\n if (segment !== token.value) {\n matched = false;\n }\n i++;\n break;\n\n case 'param':\n if (token.repeatable) {\n params[token.name] = pathSegments.slice(i);\n i = pathSegments.length;\n } else if (segment !== undefined) {\n params[token.name] = segment;\n i++;\n }\n break;\n\n case 'wildcard':\n params[token.value] = pathSegments.slice(i).join('/');\n i = pathSegments.length;\n break;\n }\n\n if (!matched) break;\n }\n\n // Check if all path segments were consumed\n if (matched && i < pathSegments.length) {\n matched = false;\n }\n\n // In non-strict mode, trailing slash is ok\n if (!strict && matched && pathSegments.length === 0 && tokens.length === 0) {\n matched = true;\n }\n\n return {\n matched,\n params,\n path: '/' + pathSegments.slice(0, i).join('/'),\n score: scoreRoute(tokens),\n };\n}\n\n/**\n * 路由类\n */\nexport class Router {\n /** 路由列表 */\n private routes: Array<{\n method: HttpMethod;\n path: string;\n tokens: ReturnType<typeof tokenizePath>;\n handler: Handler;\n }> = [];\n\n /**\n * 添加路由\n *\n * @param method - HTTP 方法\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n on(method: HttpMethod, path: string, handler: Handler): this {\n const tokens = tokenizePath(path);\n this.routes.push({ method, path, tokens, handler });\n return this;\n }\n\n /**\n * 添加 GET 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n get(path: string, handler: Handler): this {\n return this.on('GET', path, handler);\n }\n\n /**\n * 添加 POST 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n post(path: string, handler: Handler): this {\n return this.on('POST', path, handler);\n }\n\n /**\n * 添加 PUT 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n put(path: string, handler: Handler): this {\n return this.on('PUT', path, handler);\n }\n\n /**\n * 添加 PATCH 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n patch(path: string, handler: Handler): this {\n return this.on('PATCH', path, handler);\n }\n\n /**\n * 添加 DELETE 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 路由实例\n */\n delete(path: string, handler: Handler): this {\n return this.on('DELETE', path, handler);\n }\n\n /**\n * 匹配路由\n *\n * @param method - HTTP 方法\n * @param path - 路径\n * @returns 匹配结果或 null\n */\n match(\n method: HttpMethod,\n path: string,\n ): { handler: Handler; params: Record<string, string | string[]> } | null {\n const candidates = this.routes.filter((r) => r.method === method);\n\n let bestMatch: { handler: Handler; params: Record<string, string | string[]> } | null = null;\n let bestScore = -1;\n\n for (const route of candidates) {\n const result = matchPath(path, route.tokens);\n if (result.matched && result.score > bestScore) {\n bestScore = result.score;\n bestMatch = { handler: route.handler, params: result.params };\n }\n }\n\n return bestMatch;\n }\n}\n\n/**\n * 创建路由\n *\n * @returns 路由实例\n */\nexport function createRouter(): Router {\n return new Router();\n}\n","/**\n * HTTP 服务器实现\n */\nimport type { Server as NodeServer, IncomingMessage, ServerResponse } from 'node:http';\nimport { createServer as createNodeServer } from 'node:http';\nimport type { Handler } from './types';\nimport type {\n HttpMethod,\n HttpContext as Context,\n HttpRequest as Request,\n HttpResponse as Response,\n} from '@lytjs/shared-types';\nimport { Router } from './router';\nimport { parseQueryStringWithArrays } from '@lytjs/common-query';\n\n/**\n * HTTP 服务器类\n */\nexport class Server {\n /** 路由实例 */\n private router: Router;\n /** 中间件列表 */\n private middlewares: ((ctx: Context, next: () => Promise<void>) => Promise<void>)[] = [];\n /** Node.js 服务器实例 */\n private server?: NodeServer;\n\n /**\n * 构造函数\n */\n constructor() {\n this.router = new Router();\n }\n\n /**\n * 添加中间件\n *\n * @param middleware - 中间件函数\n * @returns 服务器实例\n */\n use(middleware: (ctx: Context, next: () => Promise<void>) => Promise<void>): this {\n this.middlewares.push(middleware);\n return this;\n }\n\n /**\n * 添加路由\n *\n * @param method - HTTP 方法\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n on(method: HttpMethod, path: string, handler: Handler): this {\n this.router.on(method, path, handler);\n return this;\n }\n\n /**\n * 添加 GET 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n get(path: string, handler: Handler): this {\n return this.on('GET', path, handler);\n }\n\n /**\n * 添加 POST 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n post(path: string, handler: Handler): this {\n return this.on('POST', path, handler);\n }\n\n /**\n * 添加 PUT 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n put(path: string, handler: Handler): this {\n return this.on('PUT', path, handler);\n }\n\n /**\n * 添加 PATCH 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n patch(path: string, handler: Handler): this {\n return this.on('PATCH', path, handler);\n }\n\n /**\n * 添加 DELETE 路由\n *\n * @param path - 路径\n * @param handler - 处理器\n * @returns 服务器实例\n */\n delete(path: string, handler: Handler): this {\n return this.on('DELETE', path, handler);\n }\n\n /**\n * 启动服务器监听\n *\n * @param port - 端口\n * @param hostname - 主机名\n * @returns Promise\n */\n listen(port: number, hostname?: string): Promise<void> {\n return new Promise((resolve) => {\n this.server = createNodeServer(this.handleRequest.bind(this));\n this.server.listen(port, hostname, () => {\n resolve();\n });\n });\n }\n\n /**\n * 关闭服务器\n *\n * @returns Promise\n */\n close(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (this.server) {\n this.server.close((err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n } else {\n resolve();\n }\n });\n }\n\n /**\n * 处理请求\n *\n * @param req - Node.js 请求对象\n * @param res - Node.js 响应对象\n */\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = req.url || '/';\n const path = url.split('?')[0] || '/';\n const method = (req.method || 'GET') as HttpMethod;\n\n const request: Request = {\n method,\n url,\n path,\n headers: req.headers,\n query: parseQueryStringWithArrays(url),\n params: {},\n ip: req.socket.remoteAddress,\n };\n\n const response: Response = {\n status: 200,\n headers: {},\n };\n\n const ctx: Context = {\n request,\n response,\n };\n\n const match = this.router.match(method, path);\n\n if (match) {\n // 转换类型,确保兼容性\n ctx.request.params = {};\n for (const [key, value] of Object.entries(match.params)) {\n if (Array.isArray(value)) {\n // 如果是数组,只取第一个元素\n ctx.request.params[key] = value[0] || '';\n } else {\n ctx.request.params[key] = value;\n }\n }\n\n const handler = async () => {\n await match.handler(ctx);\n };\n\n let index = this.middlewares.length;\n const next = async () => {\n index--;\n if (index >= 0 && this.middlewares[index]) {\n await this.middlewares[index](ctx, next);\n } else {\n await handler();\n }\n };\n\n await next();\n } else {\n if (ctx.response) {\n ctx.response.status = 404;\n ctx.response.body = { error: '未找到' };\n }\n }\n\n if (ctx.response) {\n this.sendResponse(res, ctx.response);\n }\n }\n\n /**\n * 发送响应\n *\n * @param res - Node.js 响应对象\n * @param response - 响应对象\n */\n private sendResponse(res: ServerResponse, response: Response): void {\n res.statusCode = response.status;\n\n for (const [key, value] of Object.entries(response.headers)) {\n if (Array.isArray(value)) {\n for (const v of value) {\n res.setHeader(key, v);\n }\n } else {\n res.setHeader(key, value);\n }\n }\n\n if (response.body !== undefined) {\n const body =\n typeof response.body === 'string' ? response.body : JSON.stringify(response.body);\n\n if (!response.headers['content-type']) {\n res.setHeader(\n 'content-type',\n typeof response.body === 'string' ? 'text/plain' : 'application/json',\n );\n }\n\n res.end(body);\n } else {\n res.end();\n }\n }\n}\n\n/**\n * 创建 HTTP 服务器\n *\n * @returns 服务器实例\n */\nexport function createServer(): Server {\n return new Server();\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lytjs/http-server",
|
|
3
|
+
"version": "6.6.0",
|
|
4
|
+
"description": "LytJS HTTP 服务器",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"test:coverage": "vitest run --coverage",
|
|
29
|
+
"type-check": "tsc --noEmit",
|
|
30
|
+
"lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
31
|
+
"clean": "rm -rf dist node_modules .turbo"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@lytjs/common-is": "workspace:*",
|
|
35
|
+
"@lytjs/common-query": "workspace:*",
|
|
36
|
+
"@lytjs/middleware": "workspace:*",
|
|
37
|
+
"@lytjs/shared-types": "workspace:*"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"tsup": "^8.3.6",
|
|
41
|
+
"typescript": "^5.7.3",
|
|
42
|
+
"vitest": "^3.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"registry": "https://registry.npmjs.org/"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://gitee.com/lytjs/lytjs.git",
|
|
52
|
+
"directory": "packages/ecosystem/packages/web-framework/packages/http-server"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"lytjs",
|
|
56
|
+
"http",
|
|
57
|
+
"server",
|
|
58
|
+
"router"
|
|
59
|
+
],
|
|
60
|
+
"author": "LytJS Team",
|
|
61
|
+
"license": "MIT"
|
|
62
|
+
}
|