wok-server 0.1.4 → 0.1.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.
@@ -27,7 +27,7 @@ function createJsonHandler(opts) {
27
27
  (0, validation_1.validate)(body, opts.validation);
28
28
  }
29
29
  }
30
- const res = await opts.handle(body, exchange);
30
+ const res = await opts.handle(body, { request: exchange.request });
31
31
  if (!res) {
32
32
  exchange.respond({ statusCode: 200 });
33
33
  return;
@@ -17,10 +17,8 @@ function createUploadHandler(opts) {
17
17
  return;
18
18
  }
19
19
  const body = await exchange.bodyBuffer();
20
- const { url, headers } = exchange.request;
21
20
  const res = await opts.handle(body, {
22
- url: url || '',
23
- headers,
21
+ request: exchange.request,
24
22
  query: exchange.parseQueryString()
25
23
  });
26
24
  if (!res) {
@@ -5,6 +5,7 @@ const fs_1 = require("fs");
5
5
  const promises_1 = require("fs/promises");
6
6
  const json_1 = require("./json");
7
7
  const path_1 = require("path");
8
+ const zlib_1 = require("zlib");
8
9
  /**
9
10
  * 常用的 content-type 对照表
10
11
  */
@@ -143,7 +144,7 @@ async function renderFile(request, response, filePath, download = false) {
143
144
  const rangeHeader = request.headers['range'];
144
145
  if (!rangeHeader) {
145
146
  response.setHeader('Last-Modified', statRes.mtime.toUTCString());
146
- return streamFile(filePath, response);
147
+ return streamFile(filePath, request, response);
147
148
  }
148
149
  // 解析,range 示例:bytes=200-1000, 2000-6576, 19000-
149
150
  // 多段的情况,暂时不做支持,非常麻烦,段数多还可能会有效率问题
@@ -151,11 +152,11 @@ async function renderFile(request, response, filePath, download = false) {
151
152
  const ranges = rangeHeader.split(',');
152
153
  let range = ranges.length ? ranges[0] : undefined;
153
154
  if (!range) {
154
- return streamFile(filePath, response);
155
+ return streamFile(filePath, request, response);
155
156
  }
156
157
  range = range.trim();
157
158
  if (!range.startsWith('bytes=')) {
158
- return streamFile(filePath, response);
159
+ return streamFile(filePath, request, response);
159
160
  }
160
161
  range = range.substring(6);
161
162
  const strs = range.split('-');
@@ -178,10 +179,10 @@ async function renderFile(request, response, filePath, download = false) {
178
179
  // 注:Range 和 Content-Range 还有 createReadStream 中的字节范围,都是前后全包含的
179
180
  // Content-Range: bytes 42-1233/1234
180
181
  response.setHeader('Content-Range', `bytes ${start}-${end}/${statRes.size}`);
181
- return streamFile(filePath, response, { start, end });
182
+ return streamFile(filePath, request, response, { start, end });
182
183
  }
183
184
  exports.renderFile = renderFile;
184
- function streamFile(filePath, response, opts) {
185
+ function streamFile(filePath, request, response, opts) {
185
186
  return new Promise((res, rej) => {
186
187
  if (opts && typeof opts.start === 'number') {
187
188
  // 部分返回 206
@@ -191,6 +192,22 @@ function streamFile(filePath, response, opts) {
191
192
  // 全部返回 200
192
193
  response.statusCode = 200;
193
194
  }
195
+ // 支持 gzip
196
+ const acceptEncoding = request.headers['accept-encoding'];
197
+ if (acceptEncoding) {
198
+ // Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
199
+ const acceptEncodings = acceptEncoding
200
+ .trim()
201
+ .split(',')
202
+ .map(item => item.trim())
203
+ .map(item => item.split(';')[0]);
204
+ if (acceptEncodings.includes('gzip') || acceptEncodings.includes('*')) {
205
+ response.setHeader('Content-Encoding', 'gzip');
206
+ (0, fs_1.createReadStream)(filePath, opts).pipe((0, zlib_1.createGzip)()).pipe(response);
207
+ response.once('finish', res).once('error', rej);
208
+ return;
209
+ }
210
+ }
194
211
  (0, fs_1.createReadStream)(filePath, opts).pipe(response);
195
212
  response.once('finish', res).once('error', rej);
196
213
  });
@@ -33,6 +33,7 @@
33
33
  │ │ ├──create-tag.ts 创建标签接口(/tag/create)
34
34
  │ │ ├──delete-tag.ts 删除标签接口(/tag/delete)
35
35
  │ │ └──index.ts 包入口文件,导出需要导出的模块
36
+ │ ├──exception.ts 全局异常
36
37
  │ └──main.ts 入口文件,启动服务,配置路由
37
38
  ├──.env 开发环境变量配置
38
39
  ├──package.json
@@ -42,12 +43,64 @@
42
43
 
43
44
  ## 文件说明
44
45
 
46
+ exception.ts 这个文件中包含了自定义的业务异常,以及异常的处理拦截器。
47
+
48
+ ```ts
49
+ /**
50
+ * 自定义业务异常.
51
+ */
52
+ export class BusinessException {
53
+ constructor(
54
+ /**
55
+ * 提示信息.
56
+ */
57
+ readonly message: string,
58
+ /**
59
+ * 自定义状态码,默认 400
60
+ */
61
+ readonly status?: number
62
+ ) {}
63
+ }
64
+
65
+ /**
66
+ * 全局异常拦截器,对一些特定的异常做处理,给予合适的响应信息.
67
+ * @param exchange
68
+ * @param next
69
+ */
70
+ export async function globalErrorInterceptor(
71
+ exchange: ServerExchange,
72
+ next: () => Promise<void>
73
+ ): Promise<void> {
74
+ try {
75
+ await next()
76
+ } catch (e) {
77
+ // 处理自定义业务异常
78
+ if (e instanceof BusinessException) {
79
+ const status = typeof e.status === 'number' ? e.status : 400
80
+ const message = e.message || ''
81
+ exchange.respondErrMsg(message, status)
82
+ return
83
+ }
84
+ // 校验异常
85
+ if (e instanceof ValidationException) {
86
+ exchange.respondErrMsg(`${e.propertyPath}:${e.errMsg}`, 400)
87
+ return
88
+ }
89
+ // 其它异常直接抛出,框架默认会响应 500 状态码
90
+ throw e
91
+ }
92
+ }
93
+ ```
94
+
95
+ 使用自定义业务异常,在业务无法进行的情况下抛出异常中止请求处理,事务的处理过程中遇到异常也会进行回滚,这是推荐的做法。
96
+
45
97
  main.ts 示例:
46
98
 
47
99
  ```ts
48
100
  import { createAuth, authInterceptor, ccuTask } from './auth'
49
101
  import { createUser, updateUser, deleteUser } from './user'
50
102
  import { createTag, deleteTag } from './tag'
103
+ import { globalErrorInterceptor } from './exception'
51
104
 
52
105
  async function main() {
53
106
  // 改写 date 原型,将日期序列化为数字
@@ -77,7 +130,7 @@ async function main() {
77
130
  '/tag/delete': deleteTag
78
131
  },
79
132
  // 拦截器
80
- interceptors: [authInterceptor]
133
+ interceptors: [globalErrorInterceptor, authInterceptor]
81
134
  })
82
135
  // 周期性任务
83
136
  // 每60秒统计一次在线人数
@@ -194,9 +247,9 @@ export const createUser = createJsonHandler<Form, Resp>({
194
247
  async handle(body, exchange) {
195
248
  const manager = getMysqlManager()
196
249
  if (await manager.existsBy(tableUser, { code: body.code })) {
197
- // 错误信息的响应也可以自定义异常,然后通过拦截器来统一处理,这里仅仅是简单的示例
198
- exchage.respondErrMsg('编号已经存在')
199
- return
250
+ // 抛出异常,中止处理
251
+ // 异常最终会由拦截器来处理并响应
252
+ throw new BusinessException('编号已经存在')
200
253
  }
201
254
  const newUser = await manager.insert(tableUser, body)
202
255
  return { id: newUser.id }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wok-server",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "packageManager": "pnpm@8.9.0",
5
5
  "description": "一个基于 NodeJs 和 TypeScript 的后端框架,轻量级、克制、简洁。A lightweight, restrained, and concise backend framework based on Node.js and TypeScript.",
6
6
  "scripts": {
@@ -1,6 +1,13 @@
1
+ /// <reference types="node" />
2
+ import { IncomingMessage } from 'http';
1
3
  import { ValidationOpts } from '../../validation';
2
- import { ServerExchange } from '../exchange';
3
4
  import { RouterHandler } from '../router';
5
+ export interface JsonHandlerExchange {
6
+ /**
7
+ * 请求信息
8
+ */
9
+ request: IncomingMessage;
10
+ }
4
11
  /**
5
12
  * 创建 json 处理器..
6
13
  * @param <REQ> 表示请求的 json 数据格式类型
@@ -19,5 +26,5 @@ export declare function createJsonHandler<REQ, RES = void>(opts: {
19
26
  * @param exchange 请求传输对象,用于获取请求的基本信息
20
27
  * @returns
21
28
  */
22
- handle: (body: REQ, exchange: ServerExchange) => Promise<RES>;
29
+ handle: (body: REQ, exchange: JsonHandlerExchange) => Promise<RES>;
23
30
  }): RouterHandler;
@@ -1,20 +1,16 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- import { IncomingHttpHeaders } from 'http';
3
+ import { IncomingMessage } from 'http';
4
4
  import { QueryString } from '../query';
5
5
  import { RouterHandler } from '../router';
6
6
  /**
7
7
  * 上传请求传输对象.
8
8
  */
9
- export interface UploadRouterExchange {
9
+ export interface UploadHandlerExchange {
10
10
  /**
11
- * 请求地址
11
+ * 请求信息
12
12
  */
13
- url: string;
14
- /**
15
- * 请求头
16
- */
17
- headers: IncomingHttpHeaders;
13
+ request: IncomingMessage;
18
14
  /**
19
15
  * querystring 解析结果
20
16
  */
@@ -36,5 +32,5 @@ export declare function createUploadHandler<RES = void>(opts: {
36
32
  * @param exchange 请求传输对象,用于获取请求的基本信息
37
33
  * @returns
38
34
  */
39
- handle: (body: Buffer, exchange: UploadRouterExchange) => Promise<RES>;
35
+ handle: (body: Buffer, exchange: UploadHandlerExchange) => Promise<RES>;
40
36
  }): RouterHandler;