@hile/http 1.0.6
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 +234 -0
- package/dist/controller.d.ts +17 -0
- package/dist/controller.js +37 -0
- package/dist/find-my-way.d.ts +11 -0
- package/dist/find-my-way.js +47 -0
- package/dist/http.d.ts +109 -0
- package/dist/http.js +161 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/loader.d.ts +28 -0
- package/dist/loader.js +82 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# @hile/http
|
|
2
|
+
|
|
3
|
+
基于 Koa + find-my-way 的 HTTP 服务框架,支持中间件、路由注册和文件系统自动路由加载。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @hile/http
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 快速开始
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Http } from '@hile/http'
|
|
15
|
+
|
|
16
|
+
const http = new Http({ port: 3000 })
|
|
17
|
+
|
|
18
|
+
http.get('/hello', async (ctx) => {
|
|
19
|
+
ctx.body = 'Hello, World!'
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const close = await http.listen(() => {
|
|
23
|
+
console.log('Server running on http://localhost:3000')
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 核心概念
|
|
28
|
+
|
|
29
|
+
### 创建服务
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const http = new Http({
|
|
33
|
+
port: 3000,
|
|
34
|
+
keys: ['secret1', 'secret2'], // 可选,不传则自动生成
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 中间件
|
|
39
|
+
|
|
40
|
+
通过 `use()` 注册全局中间件,支持链式调用。中间件必须在 `listen()` 之前注册。
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
http
|
|
44
|
+
.use(async (ctx, next) => {
|
|
45
|
+
const start = Date.now()
|
|
46
|
+
await next()
|
|
47
|
+
ctx.set('X-Response-Time', `${Date.now() - start}ms`)
|
|
48
|
+
})
|
|
49
|
+
.use(async (ctx, next) => {
|
|
50
|
+
console.log(`${ctx.method} ${ctx.url}`)
|
|
51
|
+
await next()
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 路由
|
|
56
|
+
|
|
57
|
+
使用快捷方法注册路由,支持路径参数和多个中间件:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
http.get('/users', async (ctx) => {
|
|
61
|
+
ctx.body = [{ id: 1, name: 'Alice' }]
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
http.get('/users/:id', async (ctx) => {
|
|
65
|
+
ctx.body = { id: ctx.params.id }
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
http.post('/users', authMiddleware, async (ctx) => {
|
|
69
|
+
ctx.body = { created: true }
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
所有路由方法返回注销函数,调用后路由不再匹配:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const off = http.get('/temporary', async (ctx) => {
|
|
77
|
+
ctx.body = 'gone soon'
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
off() // 移除该路由
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
支持的快捷方法:`get`、`post`、`put`、`delete`、`trace`,或使用 `route()` 指定任意 HTTP 方法:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
http.route('PATCH', '/users/:id', async (ctx) => {
|
|
87
|
+
ctx.body = { patched: true }
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 启动与关闭
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const close = await http.listen((server) => {
|
|
95
|
+
console.log(`Listening on port ${http.port}`)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// 关闭服务
|
|
99
|
+
close()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 文件系统路由
|
|
103
|
+
|
|
104
|
+
### 定义控制器
|
|
105
|
+
|
|
106
|
+
使用 `defineController` 定义路由控制器。返回值自动赋给 `ctx.body`。
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// controllers/users/index.controller.ts
|
|
110
|
+
import { defineController } from '@hile/http'
|
|
111
|
+
|
|
112
|
+
export default defineController('GET', async (ctx) => {
|
|
113
|
+
return { users: [] }
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
带中间件:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { defineController } from '@hile/http'
|
|
121
|
+
|
|
122
|
+
const auth = async (ctx, next) => {
|
|
123
|
+
if (!ctx.headers.authorization) ctx.throw(401)
|
|
124
|
+
await next()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default defineController('POST', [auth], async (ctx) => {
|
|
128
|
+
return { created: true }
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
同一文件导出多个控制器:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// controllers/users/[id].controller.ts
|
|
136
|
+
import { defineController } from '@hile/http'
|
|
137
|
+
|
|
138
|
+
const getUser = defineController('GET', async (ctx) => {
|
|
139
|
+
return { id: ctx.params.id }
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const updateUser = defineController('PUT', [auth], async (ctx) => {
|
|
143
|
+
return { updated: true }
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
export default [getUser, updateUser]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 加载路由
|
|
150
|
+
|
|
151
|
+
使用 `load()` 自动扫描目录并注册路由:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
await http.load('./src/controllers', {
|
|
155
|
+
suffix: 'controller', // 匹配 *.controller.{ts,js}
|
|
156
|
+
prefix: '/api', // 路由前缀
|
|
157
|
+
defaultSuffix: '/index', // index 文件映射到父路径
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 路径映射规则
|
|
162
|
+
|
|
163
|
+
| 文件路径 | 路由路径 |
|
|
164
|
+
|---------|---------|
|
|
165
|
+
| `index.controller.ts` | `/api` |
|
|
166
|
+
| `users/index.controller.ts` | `/api/users` |
|
|
167
|
+
| `users/list.controller.ts` | `/api/users/list` |
|
|
168
|
+
| `users/[id].controller.ts` | `/api/users/:id` |
|
|
169
|
+
| `[category]/[id].controller.ts` | `/api/:category/:id` |
|
|
170
|
+
|
|
171
|
+
路径中的 `[param]` 会自动转换为 `:param` 路由参数。
|
|
172
|
+
|
|
173
|
+
## 与 hile 集成
|
|
174
|
+
|
|
175
|
+
配合 `hile` 服务容器管理 HTTP 服务的生命周期:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { defineService } from 'hile'
|
|
179
|
+
import { Http } from '@hile/http'
|
|
180
|
+
|
|
181
|
+
export const httpService = defineService(async (shutdown) => {
|
|
182
|
+
const http = new Http({ port: 3000 })
|
|
183
|
+
|
|
184
|
+
await http.load('./src/controllers', {
|
|
185
|
+
suffix: 'controller',
|
|
186
|
+
prefix: '/api',
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const close = await http.listen()
|
|
190
|
+
shutdown(close)
|
|
191
|
+
|
|
192
|
+
return http
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## API
|
|
197
|
+
|
|
198
|
+
### Http
|
|
199
|
+
|
|
200
|
+
| 方法 | 说明 |
|
|
201
|
+
|------|------|
|
|
202
|
+
| `new Http(props)` | 创建实例,`port` 必填 |
|
|
203
|
+
| `port` | 获取端口号 |
|
|
204
|
+
| `use(middleware)` | 注册全局中间件,返回 `this` |
|
|
205
|
+
| `listen(onListen?)` | 启动服务,返回关闭函数 |
|
|
206
|
+
| `get(url, ...mw)` | 注册 GET 路由,返回注销函数 |
|
|
207
|
+
| `post(url, ...mw)` | 注册 POST 路由,返回注销函数 |
|
|
208
|
+
| `put(url, ...mw)` | 注册 PUT 路由,返回注销函数 |
|
|
209
|
+
| `delete(url, ...mw)` | 注册 DELETE 路由,返回注销函数 |
|
|
210
|
+
| `trace(url, ...mw)` | 注册 TRACE 路由,返回注销函数 |
|
|
211
|
+
| `route(method, url, ...mw)` | 注册任意方法路由,返回注销函数 |
|
|
212
|
+
| `load(dir, options?)` | 加载文件系统路由,返回注销函数 |
|
|
213
|
+
|
|
214
|
+
### defineController
|
|
215
|
+
|
|
216
|
+
| 调用形式 | 说明 |
|
|
217
|
+
|---------|------|
|
|
218
|
+
| `defineController(method, fn)` | 无中间件 |
|
|
219
|
+
| `defineController(method, [mw...], fn)` | 带中间件 |
|
|
220
|
+
|
|
221
|
+
- 控制器函数接收 `ctx`,返回值非 `undefined` 时自动设为 `ctx.body`
|
|
222
|
+
- 控制器文件必须 `export default` 导出
|
|
223
|
+
|
|
224
|
+
### load 选项
|
|
225
|
+
|
|
226
|
+
| 选项 | 默认值 | 说明 |
|
|
227
|
+
|------|-------|------|
|
|
228
|
+
| `suffix` | `'controller'` | 文件后缀标记 |
|
|
229
|
+
| `prefix` | — | 路由前缀 |
|
|
230
|
+
| `defaultSuffix` | `'/index'` | 映射到父路径的文件名 |
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HTTPMethod } from 'find-my-way';
|
|
2
|
+
import { Context, Middleware } from 'koa';
|
|
3
|
+
export type ControllerFunction<R> = (ctx: Context) => R | Promise<R>;
|
|
4
|
+
export interface ControllerRegisterProps<R> {
|
|
5
|
+
id: number;
|
|
6
|
+
method: HTTPMethod;
|
|
7
|
+
middlewares: Middleware[];
|
|
8
|
+
data: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 定义路由控制器
|
|
12
|
+
* @param method 请求方法
|
|
13
|
+
* @param middlewares 中间件,可以是中间件数组或控制器函数
|
|
14
|
+
* @param fn 控制器函数
|
|
15
|
+
* @returns 路由控制器注册信息
|
|
16
|
+
*/
|
|
17
|
+
export declare function defineController<R>(method: HTTPMethod, middlewares: Middleware[] | ControllerFunction<R>, fn?: ControllerFunction<R>): ControllerRegisterProps<R>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
let _id = 1;
|
|
2
|
+
/**
|
|
3
|
+
* 定义路由控制器
|
|
4
|
+
* @param method 请求方法
|
|
5
|
+
* @param middlewares 中间件,可以是中间件数组或控制器函数
|
|
6
|
+
* @param fn 控制器函数
|
|
7
|
+
* @returns 路由控制器注册信息
|
|
8
|
+
*/
|
|
9
|
+
export function defineController(method, middlewares, fn) {
|
|
10
|
+
let _middlewares = [];
|
|
11
|
+
let _fn = undefined;
|
|
12
|
+
if (typeof middlewares === 'function' && !fn) {
|
|
13
|
+
_fn = middlewares;
|
|
14
|
+
_middlewares = [];
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
_middlewares = middlewares;
|
|
18
|
+
_fn = fn;
|
|
19
|
+
}
|
|
20
|
+
if (!Array.isArray(_middlewares))
|
|
21
|
+
throw new Error('Middlewares must be an array');
|
|
22
|
+
if (!_fn)
|
|
23
|
+
throw new Error('Controller function is required');
|
|
24
|
+
const id = _id++;
|
|
25
|
+
_middlewares.push(async (ctx) => {
|
|
26
|
+
const result = await _fn(ctx);
|
|
27
|
+
if (result !== undefined) {
|
|
28
|
+
ctx.body = result;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
method,
|
|
34
|
+
middlewares: _middlewares,
|
|
35
|
+
data: {},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Config, HTTPVersion, HTTPMethod } from 'find-my-way';
|
|
2
|
+
import { Middleware } from 'koa';
|
|
3
|
+
export { HTTPVersion, };
|
|
4
|
+
export interface Instance {
|
|
5
|
+
on(method: HTTPMethod | HTTPMethod[], path: string, ...middlewares: Middleware[]): Instance;
|
|
6
|
+
off(method: HTTPMethod | HTTPMethod[], path: string): Instance;
|
|
7
|
+
prettyPrint(): string;
|
|
8
|
+
routes(): Middleware;
|
|
9
|
+
}
|
|
10
|
+
declare const _default: <V extends HTTPVersion = HTTPVersion.V1>(options: Config<V>) => Instance;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import compose from 'koa-compose';
|
|
2
|
+
import FindMyWay, { HTTPVersion } from 'find-my-way';
|
|
3
|
+
import { METHODS } from 'node:http';
|
|
4
|
+
export { HTTPVersion, };
|
|
5
|
+
export default (options) => {
|
|
6
|
+
const fmw = FindMyWay(options);
|
|
7
|
+
const r = {};
|
|
8
|
+
function on(method, path, ...middlewares) {
|
|
9
|
+
// optional store argument
|
|
10
|
+
let store;
|
|
11
|
+
if (middlewares.length > 1 && typeof middlewares[middlewares.length - 1] === 'object') {
|
|
12
|
+
store = middlewares.pop();
|
|
13
|
+
}
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
fmw.on(method, path, compose(middlewares), store);
|
|
16
|
+
return r;
|
|
17
|
+
}
|
|
18
|
+
r.on = on;
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
r.all = (path, ...middlewares) => on(METHODS, path, ...middlewares);
|
|
21
|
+
METHODS.forEach((m) => {
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
r[m.toLowerCase()] = (path, ...middlewares) => on(m, path, ...middlewares);
|
|
24
|
+
});
|
|
25
|
+
[
|
|
26
|
+
'off',
|
|
27
|
+
'reset',
|
|
28
|
+
'prettyPrint',
|
|
29
|
+
'find',
|
|
30
|
+
].forEach((m) => {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
r[m] = fmw[m].bind(fmw);
|
|
33
|
+
});
|
|
34
|
+
r.routes = () => (ctx, next) => {
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
const handle = fmw.find(ctx.method, ctx.path);
|
|
37
|
+
if (!handle) {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
return fmw.defaultRoute && fmw.defaultRoute(ctx, next);
|
|
40
|
+
}
|
|
41
|
+
ctx.params = handle.params;
|
|
42
|
+
ctx.store = handle.store;
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
return handle.handler(ctx, next);
|
|
45
|
+
};
|
|
46
|
+
return r;
|
|
47
|
+
};
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Middleware } from 'koa';
|
|
2
|
+
import { Config, HTTPMethod, HTTPVersion } from 'find-my-way';
|
|
3
|
+
import { Server } from 'node:http';
|
|
4
|
+
import { LoaderFromOptions } from './loader';
|
|
5
|
+
export type HttpProps = {
|
|
6
|
+
port: number;
|
|
7
|
+
keys?: string[];
|
|
8
|
+
} & Omit<Config<HTTPVersion.V1>, 'defaultRoute'>;
|
|
9
|
+
/**
|
|
10
|
+
* Http 服务类
|
|
11
|
+
* @description - 提供 HTTP 服务的基本功能
|
|
12
|
+
* @example
|
|
13
|
+
* const http = new Http({
|
|
14
|
+
* port: 3000,
|
|
15
|
+
* ignoreDuplicateSlashes: true,
|
|
16
|
+
* ignoreTrailingSlash: true,
|
|
17
|
+
* maxParamLength: +Infinity,
|
|
18
|
+
* allowUnsafeRegex: true,
|
|
19
|
+
* caseSensitive: true,
|
|
20
|
+
* });
|
|
21
|
+
* http.use(async (ctx, next) => {
|
|
22
|
+
* await next();
|
|
23
|
+
* });
|
|
24
|
+
* http.use(async (ctx, next) => {
|
|
25
|
+
* await next();
|
|
26
|
+
* });
|
|
27
|
+
* http.use(async (ctx, next) => {
|
|
28
|
+
* await next();
|
|
29
|
+
* });
|
|
30
|
+
* await http.listen((server) => {
|
|
31
|
+
* console.log('Server is running on port 3000');
|
|
32
|
+
* });
|
|
33
|
+
* console.log('Server is running on port 3000');
|
|
34
|
+
*/
|
|
35
|
+
export declare class Http {
|
|
36
|
+
private readonly props;
|
|
37
|
+
private readonly koa;
|
|
38
|
+
private readonly loader;
|
|
39
|
+
private readonly router;
|
|
40
|
+
private server?;
|
|
41
|
+
constructor(props: HttpProps);
|
|
42
|
+
/**
|
|
43
|
+
* 获取服务端口
|
|
44
|
+
* @returns - 服务端口
|
|
45
|
+
*/
|
|
46
|
+
get port(): number;
|
|
47
|
+
/**
|
|
48
|
+
* 启动服务前注册中间件
|
|
49
|
+
* @param middleware - 中间件函数
|
|
50
|
+
* @returns - 当前实例
|
|
51
|
+
*/
|
|
52
|
+
use(middleware: Middleware): this;
|
|
53
|
+
/**
|
|
54
|
+
* 启动服务
|
|
55
|
+
* @param onListen - 启动服务后回调函数,可选,回调函数返回值为关闭服务回调函数
|
|
56
|
+
* @returns - 关闭服务回调函数
|
|
57
|
+
*/
|
|
58
|
+
listen(onListen?: (server: Server) => void | Promise<void>): Promise<() => void>;
|
|
59
|
+
/**
|
|
60
|
+
* 注册 GET 请求路由
|
|
61
|
+
* @param url - 请求路径
|
|
62
|
+
* @param middlewares - 中间件函数
|
|
63
|
+
* @returns - 注销路由回调函数
|
|
64
|
+
*/
|
|
65
|
+
get(url: string, ...middlewares: Middleware[]): () => void;
|
|
66
|
+
/**
|
|
67
|
+
* 注册 POST 请求路由
|
|
68
|
+
* @param url - 请求路径
|
|
69
|
+
* @param middlewares - 中间件函数
|
|
70
|
+
* @returns - 注销路由回调函数
|
|
71
|
+
*/
|
|
72
|
+
post(url: string, ...middlewares: Middleware[]): () => void;
|
|
73
|
+
/**
|
|
74
|
+
* 注册 PUT 请求路由
|
|
75
|
+
* @param url - 请求路径
|
|
76
|
+
* @param middlewares - 中间件函数
|
|
77
|
+
* @returns - 注销路由回调函数
|
|
78
|
+
*/
|
|
79
|
+
put(url: string, ...middlewares: Middleware[]): () => void;
|
|
80
|
+
/**
|
|
81
|
+
* 注册 DELETE 请求路由
|
|
82
|
+
* @param url - 请求路径
|
|
83
|
+
* @param middlewares - 中间件函数
|
|
84
|
+
* @returns - 注销路由回调函数
|
|
85
|
+
*/
|
|
86
|
+
delete(url: string, ...middlewares: Middleware[]): () => void;
|
|
87
|
+
/**
|
|
88
|
+
* 注册 TRACE 请求路由
|
|
89
|
+
* @param url - 请求路径
|
|
90
|
+
* @param middlewares - 中间件函数
|
|
91
|
+
* @returns - 注销路由回调函数
|
|
92
|
+
*/
|
|
93
|
+
trace(url: string, ...middlewares: Middleware[]): () => void;
|
|
94
|
+
/**
|
|
95
|
+
* 注册路由
|
|
96
|
+
* @param method 请求方法
|
|
97
|
+
* @param url 请求路径
|
|
98
|
+
* @param middlewares 中间件
|
|
99
|
+
* @returns 注销路由回调函数
|
|
100
|
+
*/
|
|
101
|
+
route(method: HTTPMethod, url: string, ...middlewares: Middleware[]): () => void;
|
|
102
|
+
/**
|
|
103
|
+
* 绑定文件夹下的所有路由
|
|
104
|
+
* @param directory 文件夹路径
|
|
105
|
+
* @param options 选项
|
|
106
|
+
* @returns 注销回调
|
|
107
|
+
*/
|
|
108
|
+
load(directory: string, options?: LoaderFromOptions): Promise<() => void>;
|
|
109
|
+
}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import Koa from 'koa';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import FindMyWay from './find-my-way';
|
|
4
|
+
import { createServer } from 'node:http';
|
|
5
|
+
import { Loader } from './loader';
|
|
6
|
+
/**
|
|
7
|
+
* Http 服务类
|
|
8
|
+
* @description - 提供 HTTP 服务的基本功能
|
|
9
|
+
* @example
|
|
10
|
+
* const http = new Http({
|
|
11
|
+
* port: 3000,
|
|
12
|
+
* ignoreDuplicateSlashes: true,
|
|
13
|
+
* ignoreTrailingSlash: true,
|
|
14
|
+
* maxParamLength: +Infinity,
|
|
15
|
+
* allowUnsafeRegex: true,
|
|
16
|
+
* caseSensitive: true,
|
|
17
|
+
* });
|
|
18
|
+
* http.use(async (ctx, next) => {
|
|
19
|
+
* await next();
|
|
20
|
+
* });
|
|
21
|
+
* http.use(async (ctx, next) => {
|
|
22
|
+
* await next();
|
|
23
|
+
* });
|
|
24
|
+
* http.use(async (ctx, next) => {
|
|
25
|
+
* await next();
|
|
26
|
+
* });
|
|
27
|
+
* await http.listen((server) => {
|
|
28
|
+
* console.log('Server is running on port 3000');
|
|
29
|
+
* });
|
|
30
|
+
* console.log('Server is running on port 3000');
|
|
31
|
+
*/
|
|
32
|
+
export class Http {
|
|
33
|
+
props;
|
|
34
|
+
koa = new Koa();
|
|
35
|
+
loader = new Loader(this);
|
|
36
|
+
router;
|
|
37
|
+
server;
|
|
38
|
+
constructor(props) {
|
|
39
|
+
this.props = props;
|
|
40
|
+
if (!this.props.keys) {
|
|
41
|
+
this.props.keys = [randomBytes(32).toString(), randomBytes(64).toString()];
|
|
42
|
+
}
|
|
43
|
+
this.koa.keys = this.props.keys;
|
|
44
|
+
this.router = FindMyWay({
|
|
45
|
+
ignoreDuplicateSlashes: this.props.ignoreDuplicateSlashes ?? true,
|
|
46
|
+
ignoreTrailingSlash: this.props.ignoreTrailingSlash ?? true,
|
|
47
|
+
maxParamLength: this.props.maxParamLength ?? +Infinity,
|
|
48
|
+
allowUnsafeRegex: this.props.allowUnsafeRegex ?? true,
|
|
49
|
+
caseSensitive: this.props.caseSensitive ?? true,
|
|
50
|
+
// @ts-ignore
|
|
51
|
+
defaultRoute: async (_, next) => await next(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 获取服务端口
|
|
56
|
+
* @returns - 服务端口
|
|
57
|
+
*/
|
|
58
|
+
get port() {
|
|
59
|
+
return this.props.port;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 启动服务前注册中间件
|
|
63
|
+
* @param middleware - 中间件函数
|
|
64
|
+
* @returns - 当前实例
|
|
65
|
+
*/
|
|
66
|
+
use(middleware) {
|
|
67
|
+
this.koa.use(middleware);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 启动服务
|
|
72
|
+
* @param onListen - 启动服务后回调函数,可选,回调函数返回值为关闭服务回调函数
|
|
73
|
+
* @returns - 关闭服务回调函数
|
|
74
|
+
*/
|
|
75
|
+
async listen(onListen) {
|
|
76
|
+
this.koa.use(this.router.routes());
|
|
77
|
+
this.server = createServer(this.koa.callback());
|
|
78
|
+
if (onListen)
|
|
79
|
+
await onListen(this.server);
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
this.server.listen(this.props.port, (err) => {
|
|
82
|
+
if (err)
|
|
83
|
+
return reject(err);
|
|
84
|
+
resolve();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
this.server.close();
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 注册 GET 请求路由
|
|
93
|
+
* @param url - 请求路径
|
|
94
|
+
* @param middlewares - 中间件函数
|
|
95
|
+
* @returns - 注销路由回调函数
|
|
96
|
+
*/
|
|
97
|
+
get(url, ...middlewares) {
|
|
98
|
+
return this.route('GET', url, ...middlewares);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 注册 POST 请求路由
|
|
102
|
+
* @param url - 请求路径
|
|
103
|
+
* @param middlewares - 中间件函数
|
|
104
|
+
* @returns - 注销路由回调函数
|
|
105
|
+
*/
|
|
106
|
+
post(url, ...middlewares) {
|
|
107
|
+
return this.route('POST', url, ...middlewares);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 注册 PUT 请求路由
|
|
111
|
+
* @param url - 请求路径
|
|
112
|
+
* @param middlewares - 中间件函数
|
|
113
|
+
* @returns - 注销路由回调函数
|
|
114
|
+
*/
|
|
115
|
+
put(url, ...middlewares) {
|
|
116
|
+
return this.route('PUT', url, ...middlewares);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 注册 DELETE 请求路由
|
|
120
|
+
* @param url - 请求路径
|
|
121
|
+
* @param middlewares - 中间件函数
|
|
122
|
+
* @returns - 注销路由回调函数
|
|
123
|
+
*/
|
|
124
|
+
delete(url, ...middlewares) {
|
|
125
|
+
return this.route('DELETE', url, ...middlewares);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 注册 TRACE 请求路由
|
|
129
|
+
* @param url - 请求路径
|
|
130
|
+
* @param middlewares - 中间件函数
|
|
131
|
+
* @returns - 注销路由回调函数
|
|
132
|
+
*/
|
|
133
|
+
trace(url, ...middlewares) {
|
|
134
|
+
return this.route('TRACE', url, ...middlewares);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 注册路由
|
|
138
|
+
* @param method 请求方法
|
|
139
|
+
* @param url 请求路径
|
|
140
|
+
* @param middlewares 中间件
|
|
141
|
+
* @returns 注销路由回调函数
|
|
142
|
+
*/
|
|
143
|
+
route(method, url, ...middlewares) {
|
|
144
|
+
this.router.on(method, url, ...middlewares);
|
|
145
|
+
return () => {
|
|
146
|
+
this.router.off(method, url);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 绑定文件夹下的所有路由
|
|
151
|
+
* @param directory 文件夹路径
|
|
152
|
+
* @param options 选项
|
|
153
|
+
* @returns 注销回调
|
|
154
|
+
*/
|
|
155
|
+
load(directory, options = {
|
|
156
|
+
defaultSuffix: '/index',
|
|
157
|
+
suffix: 'controller',
|
|
158
|
+
}) {
|
|
159
|
+
return this.loader.from(directory, options);
|
|
160
|
+
}
|
|
161
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ControllerRegisterProps } from './controller';
|
|
2
|
+
import { Http } from './http';
|
|
3
|
+
export interface LoaderCompileOptions {
|
|
4
|
+
defaultSuffix?: string;
|
|
5
|
+
prefix?: string;
|
|
6
|
+
}
|
|
7
|
+
export type LoaderFromOptions = {
|
|
8
|
+
suffix?: string;
|
|
9
|
+
} & LoaderCompileOptions;
|
|
10
|
+
export declare class Loader {
|
|
11
|
+
private readonly http;
|
|
12
|
+
constructor(http: Http);
|
|
13
|
+
/**
|
|
14
|
+
* 单个路由绑定编译
|
|
15
|
+
* @param path 路径
|
|
16
|
+
* @param controllers 控制器数组或单个控制器
|
|
17
|
+
* @param options 选项
|
|
18
|
+
* @returns 注销回调
|
|
19
|
+
*/
|
|
20
|
+
compile<R>(path: string, controllers: ControllerRegisterProps<R> | ControllerRegisterProps<R>[], options?: LoaderCompileOptions): () => void;
|
|
21
|
+
/**
|
|
22
|
+
* 文件夹批量路由绑定编译
|
|
23
|
+
* @param directory 文件夹路径
|
|
24
|
+
* @param options 选项
|
|
25
|
+
* @returns 注销回调
|
|
26
|
+
*/
|
|
27
|
+
from(directory: string, options?: LoaderFromOptions): Promise<() => void>;
|
|
28
|
+
}
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export class Loader {
|
|
4
|
+
http;
|
|
5
|
+
constructor(http) {
|
|
6
|
+
this.http = http;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 单个路由绑定编译
|
|
10
|
+
* @param path 路径
|
|
11
|
+
* @param controllers 控制器数组或单个控制器
|
|
12
|
+
* @param options 选项
|
|
13
|
+
* @returns 注销回调
|
|
14
|
+
*/
|
|
15
|
+
compile(path, controllers, options = {
|
|
16
|
+
defaultSuffix: '/index',
|
|
17
|
+
}) {
|
|
18
|
+
const callbacks = [];
|
|
19
|
+
// 格式化路径,去除默认后缀
|
|
20
|
+
const defaultSuffix = options.defaultSuffix || '/index';
|
|
21
|
+
let url = path.startsWith('/') ? path : '/' + path;
|
|
22
|
+
if (url.endsWith(defaultSuffix)) {
|
|
23
|
+
url = url.substring(0, url.length - defaultSuffix.length);
|
|
24
|
+
}
|
|
25
|
+
if (!url)
|
|
26
|
+
url = '/';
|
|
27
|
+
// 如果导出单个路由,则转化为数组,批量执行
|
|
28
|
+
if (!Array.isArray(controllers)) {
|
|
29
|
+
controllers = [controllers];
|
|
30
|
+
}
|
|
31
|
+
// 添加前缀
|
|
32
|
+
const _url = options.prefix ? options.prefix + url : url;
|
|
33
|
+
// 将路径中的参数转换为路由参数
|
|
34
|
+
const router_url = _url.replace(/\[([^\]]+)\]/g, ':$1');
|
|
35
|
+
// 批量注册路由
|
|
36
|
+
for (let i = 0; i < controllers.length; i++) {
|
|
37
|
+
const controller = controllers[i];
|
|
38
|
+
const { method, middlewares } = controller;
|
|
39
|
+
controller.data.url = router_url;
|
|
40
|
+
callbacks.push(this.http.route(method, router_url, ...middlewares));
|
|
41
|
+
}
|
|
42
|
+
// 销毁路由绑定的回调
|
|
43
|
+
return () => {
|
|
44
|
+
let j = callbacks.length;
|
|
45
|
+
while (j--)
|
|
46
|
+
callbacks[j]();
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 文件夹批量路由绑定编译
|
|
51
|
+
* @param directory 文件夹路径
|
|
52
|
+
* @param options 选项
|
|
53
|
+
* @returns 注销回调
|
|
54
|
+
*/
|
|
55
|
+
async from(directory, options = {
|
|
56
|
+
defaultSuffix: '/index',
|
|
57
|
+
suffix: 'controller',
|
|
58
|
+
}) {
|
|
59
|
+
const { suffix = 'controller', ...extras } = options;
|
|
60
|
+
// 获取文件夹下的所有文件
|
|
61
|
+
const files = await glob(`**/*.${suffix}.{ts,js}`, { cwd: directory });
|
|
62
|
+
// 批量注册路由
|
|
63
|
+
const callbacks = await Promise.all(files.map(async (file) => {
|
|
64
|
+
// 获取文件绝对路径
|
|
65
|
+
const path = resolve(directory, file);
|
|
66
|
+
// 获取文件地址
|
|
67
|
+
const url = file.substring(0, file.length - suffix.length - 4);
|
|
68
|
+
// 导入文件
|
|
69
|
+
const controller = await import(path);
|
|
70
|
+
// 获取文件导出的默认函数
|
|
71
|
+
const { default: fn } = controller;
|
|
72
|
+
// 注册路由
|
|
73
|
+
return this.compile(url, fn, extras);
|
|
74
|
+
}));
|
|
75
|
+
// 销毁路由绑定的回调
|
|
76
|
+
return () => {
|
|
77
|
+
let i = callbacks.length;
|
|
78
|
+
while (i--)
|
|
79
|
+
callbacks[i]();
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hile/http",
|
|
3
|
+
"description": "Hile http - HTTP service framework",
|
|
4
|
+
"version": "1.0.6",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -b",
|
|
10
|
+
"dev": "tsc -b --watch",
|
|
11
|
+
"test": "vitest run"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/koa": "^3.0.1",
|
|
22
|
+
"vitest": "^4.0.18"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"find-my-way": "^9.5.0",
|
|
26
|
+
"glob": "^13.0.6",
|
|
27
|
+
"koa": "^3.1.2",
|
|
28
|
+
"koa-compose": "^4.1.0"
|
|
29
|
+
},
|
|
30
|
+
"gitHead": "16396508dcd3a71359fd8371feefb2f621a92a76"
|
|
31
|
+
}
|