wok-server 0.1.2 → 0.1.5
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 +1 -1
- package/dist/mvc/handler/json.js +6 -1
- package/dist/mvc/index.js +10 -8
- package/dist/mvc/render/file.js +48 -11
- package/package.json +1 -1
- package/types/mvc/handler/json.d.ts +1 -1
package/README.md
CHANGED
package/dist/mvc/handler/json.js
CHANGED
|
@@ -20,7 +20,12 @@ function createJsonHandler(opts) {
|
|
|
20
20
|
if (opts.validation) {
|
|
21
21
|
// 切换语言
|
|
22
22
|
(0, i18n_1.getI18n)().switchByRequest(exchange.request.headers);
|
|
23
|
-
|
|
23
|
+
if (typeof opts.validation === 'function') {
|
|
24
|
+
(0, validation_1.validate)(body, opts.validation());
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
(0, validation_1.validate)(body, opts.validation);
|
|
28
|
+
}
|
|
24
29
|
}
|
|
25
30
|
const res = await opts.handle(body, exchange);
|
|
26
31
|
if (!res) {
|
package/dist/mvc/index.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.stopWebServer = exports.startWebServer = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
|
+
const promises_1 = require("fs/promises");
|
|
6
7
|
const http_1 = require("http");
|
|
7
8
|
const os_1 = require("os");
|
|
8
9
|
const path_1 = require("path");
|
|
@@ -73,7 +74,7 @@ async function handleRouter(exchange, routers, staticSettings) {
|
|
|
73
74
|
const router = routers[path];
|
|
74
75
|
if (!router) {
|
|
75
76
|
// 路由找不不到,尝试静态文件
|
|
76
|
-
if (staticSettings.length) {
|
|
77
|
+
if ((exchange.request.method || '').toLowerCase() === 'get' && staticSettings.length) {
|
|
77
78
|
await handleStatic(exchange, routers, path, staticSettings);
|
|
78
79
|
}
|
|
79
80
|
else {
|
|
@@ -123,15 +124,15 @@ async function handleStatic(exchange, routers, path, staticSettings) {
|
|
|
123
124
|
respond404(exchange, routers, path);
|
|
124
125
|
return;
|
|
125
126
|
}
|
|
126
|
-
const
|
|
127
|
+
const statRes = await (0, promises_1.stat)(fullPath);
|
|
127
128
|
// 目录,寻找 index.html
|
|
128
|
-
if (
|
|
129
|
+
if (statRes.isDirectory()) {
|
|
129
130
|
const indexPath = (0, path_1.resolve)(fullPath, 'index.html');
|
|
130
131
|
if (!(0, fs_1.existsSync)(indexPath)) {
|
|
131
132
|
respond404(exchange, routers, path);
|
|
132
133
|
return;
|
|
133
134
|
}
|
|
134
|
-
const indexStat = (0,
|
|
135
|
+
const indexStat = await (0, promises_1.stat)(indexPath);
|
|
135
136
|
if (!indexStat.isFile()) {
|
|
136
137
|
respond404(exchange, routers, path);
|
|
137
138
|
return;
|
|
@@ -144,7 +145,7 @@ async function handleStatic(exchange, routers, path, staticSettings) {
|
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
// 文件直接渲染
|
|
147
|
-
if (
|
|
148
|
+
if (statRes.isFile()) {
|
|
148
149
|
// Cache-Control
|
|
149
150
|
if (matchedSetting.cacheAge >= 0) {
|
|
150
151
|
exchange.response.setHeader('Cache-Control', matchedSetting.cacheAge === 0 ? 'no-store' : `max-age=${matchedSetting.cacheAge}`);
|
|
@@ -210,8 +211,8 @@ async function startWebServer(opts) {
|
|
|
210
211
|
if (!(0, fs_1.existsSync)(dir)) {
|
|
211
212
|
throw new Error(`Static file configuration error,path ${dir} does not exist,config dir:${setting.dir}`);
|
|
212
213
|
}
|
|
213
|
-
const
|
|
214
|
-
if (!
|
|
214
|
+
const statRes = await (0, promises_1.stat)(dir);
|
|
215
|
+
if (!statRes.isDirectory()) {
|
|
215
216
|
throw new Error(`Static file configuration error,path ${dir} is not a directory,config dir:${setting.dir}`);
|
|
216
217
|
}
|
|
217
218
|
let finalPath = path.startsWith('/') ? path : '/' + path;
|
|
@@ -243,6 +244,7 @@ async function startWebServer(opts) {
|
|
|
243
244
|
interceptors.push(...opts.interceptors);
|
|
244
245
|
}
|
|
245
246
|
SERVER = (0, http_1.createServer)((req, res) => {
|
|
247
|
+
res.setHeader('Server', 'Wok Server');
|
|
246
248
|
res.on('error', error => {
|
|
247
249
|
// 如果响应流发生错误,只能把信息记录下来
|
|
248
250
|
(0, log_1.getLogger)().error(`Response Error:${req.url}`, error);
|
|
@@ -266,7 +268,7 @@ async function startWebServer(opts) {
|
|
|
266
268
|
await new Promise((resolve, reject) => {
|
|
267
269
|
server.on('error', e => {
|
|
268
270
|
if (e.code === 'EADDRINUSE') {
|
|
269
|
-
reject(
|
|
271
|
+
reject(`Port ${config_1.config.port} is already in use.`);
|
|
270
272
|
}
|
|
271
273
|
else {
|
|
272
274
|
reject(e);
|
package/dist/mvc/render/file.js
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderFile = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
5
6
|
const json_1 = require("./json");
|
|
6
7
|
const path_1 = require("path");
|
|
8
|
+
const zlib_1 = require("zlib");
|
|
7
9
|
/**
|
|
8
10
|
* 常用的 content-type 对照表
|
|
9
11
|
*/
|
|
@@ -96,7 +98,7 @@ function decideContentType(fileName) {
|
|
|
96
98
|
*/
|
|
97
99
|
async function renderFile(request, response, filePath, download = false) {
|
|
98
100
|
if (!(0, fs_1.existsSync)(filePath)) {
|
|
99
|
-
(0, json_1.renderError)(response, '
|
|
101
|
+
(0, json_1.renderError)(response, 'Cannot find file.', 404);
|
|
100
102
|
return;
|
|
101
103
|
}
|
|
102
104
|
const fileName = (0, path_1.basename)(filePath);
|
|
@@ -118,11 +120,31 @@ async function renderFile(request, response, filePath, download = false) {
|
|
|
118
120
|
else if (contentType) {
|
|
119
121
|
response.setHeader('Content-Type', contentType);
|
|
120
122
|
}
|
|
123
|
+
const statRes = await (0, promises_1.stat)(filePath);
|
|
124
|
+
// 支持 If-Modified-Since
|
|
125
|
+
// 由于只是简单的文件映射,没有 etag,不能支持 If-None-Match
|
|
126
|
+
// 缓存校验只能支持时间的比对,修改时间是文件系统本来就有的
|
|
127
|
+
if (request.headers['if-modified-since']) {
|
|
128
|
+
const modifiedSince = new Date(request.headers['if-modified-since']);
|
|
129
|
+
// 判定日期是否有效
|
|
130
|
+
if (modifiedSince instanceof Date && !isNaN(modifiedSince.getTime())) {
|
|
131
|
+
// 比较更改日期,只精确到秒, UTC 格式只精确到秒,但是 mtime 是包含毫秒的
|
|
132
|
+
const { mtime } = statRes;
|
|
133
|
+
mtime.setMilliseconds(0);
|
|
134
|
+
if (modifiedSince >= mtime) {
|
|
135
|
+
response.statusCode = 304;
|
|
136
|
+
response.setHeader('Last-Modified', statRes.mtime.toUTCString());
|
|
137
|
+
response.end();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
121
142
|
// 支持 Range
|
|
122
143
|
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Range
|
|
123
144
|
const rangeHeader = request.headers['range'];
|
|
124
145
|
if (!rangeHeader) {
|
|
125
|
-
|
|
146
|
+
response.setHeader('Last-Modified', statRes.mtime.toUTCString());
|
|
147
|
+
return streamFile(filePath, request, response);
|
|
126
148
|
}
|
|
127
149
|
// 解析,range 示例:bytes=200-1000, 2000-6576, 19000-
|
|
128
150
|
// 多段的情况,暂时不做支持,非常麻烦,段数多还可能会有效率问题
|
|
@@ -130,38 +152,37 @@ async function renderFile(request, response, filePath, download = false) {
|
|
|
130
152
|
const ranges = rangeHeader.split(',');
|
|
131
153
|
let range = ranges.length ? ranges[0] : undefined;
|
|
132
154
|
if (!range) {
|
|
133
|
-
return streamFile(filePath, response);
|
|
155
|
+
return streamFile(filePath, request, response);
|
|
134
156
|
}
|
|
135
157
|
range = range.trim();
|
|
136
158
|
if (!range.startsWith('bytes=')) {
|
|
137
|
-
return streamFile(filePath, response);
|
|
159
|
+
return streamFile(filePath, request, response);
|
|
138
160
|
}
|
|
139
161
|
range = range.substring(6);
|
|
140
162
|
const strs = range.split('-');
|
|
141
163
|
let start = strs[0] ? parseInt(strs[0], 10) : NaN;
|
|
142
164
|
let end = strs[1] ? parseInt(strs[1], 10) : NaN;
|
|
143
165
|
// 解析文件
|
|
144
|
-
const stat = (0, fs_1.statSync)(filePath);
|
|
145
166
|
if (isNaN(start) || start < 0) {
|
|
146
167
|
// 范围不合法,返回 416
|
|
147
168
|
(0, json_1.renderError)(response, `Range not satisfiable,start is ${start}`, 416);
|
|
148
169
|
return;
|
|
149
170
|
}
|
|
150
171
|
if (isNaN(end)) {
|
|
151
|
-
end =
|
|
172
|
+
end = statRes.size - 1;
|
|
152
173
|
}
|
|
153
|
-
else if (end >
|
|
174
|
+
else if (end > statRes.size - 1) {
|
|
154
175
|
// 范围不合法,返回 416
|
|
155
|
-
(0, json_1.renderError)(response, `Range not satisfiable,end must not be greater than ${
|
|
176
|
+
(0, json_1.renderError)(response, `Range not satisfiable,end must not be greater than ${statRes.size - 1}`, 416);
|
|
156
177
|
return;
|
|
157
178
|
}
|
|
158
179
|
// 注:Range 和 Content-Range 还有 createReadStream 中的字节范围,都是前后全包含的
|
|
159
180
|
// Content-Range: bytes 42-1233/1234
|
|
160
|
-
response.setHeader('Content-Range', `bytes ${start}-${end}/${
|
|
161
|
-
return streamFile(filePath, response, { start, end });
|
|
181
|
+
response.setHeader('Content-Range', `bytes ${start}-${end}/${statRes.size}`);
|
|
182
|
+
return streamFile(filePath, request, response, { start, end });
|
|
162
183
|
}
|
|
163
184
|
exports.renderFile = renderFile;
|
|
164
|
-
function streamFile(filePath, response, opts) {
|
|
185
|
+
function streamFile(filePath, request, response, opts) {
|
|
165
186
|
return new Promise((res, rej) => {
|
|
166
187
|
if (opts && typeof opts.start === 'number') {
|
|
167
188
|
// 部分返回 206
|
|
@@ -171,6 +192,22 @@ function streamFile(filePath, response, opts) {
|
|
|
171
192
|
// 全部返回 200
|
|
172
193
|
response.statusCode = 200;
|
|
173
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
|
+
}
|
|
174
211
|
(0, fs_1.createReadStream)(filePath, opts).pipe(response);
|
|
175
212
|
response.once('finish', res).once('error', rej);
|
|
176
213
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wok-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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": {
|
|
@@ -12,7 +12,7 @@ export declare function createJsonHandler<REQ, RES = void>(opts: {
|
|
|
12
12
|
/**
|
|
13
13
|
* 校验信息,可选,用于检查请求信息.对于一些特殊情况,无法使用校验器的,可以在 handle 中继续处理.
|
|
14
14
|
*/
|
|
15
|
-
validation?: ValidationOpts<REQ
|
|
15
|
+
validation?: ValidationOpts<REQ> | (() => ValidationOpts<REQ>);
|
|
16
16
|
/**
|
|
17
17
|
* 处理请求.
|
|
18
18
|
* @param body 正文内容
|