wok-server 0.5.0 → 0.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.en.md +61 -0
- package/README.md +44 -29
- package/dist/cache/cache.js +98 -98
- package/dist/cache/config.js +19 -19
- package/dist/cache/index.js +27 -27
- package/dist/cache/purge-task.js +46 -46
- package/dist/cache/stat.js +47 -47
- package/dist/config/convert.js +36 -36
- package/dist/config/exception.js +14 -14
- package/dist/config/index.js +81 -81
- package/dist/http-client/index.js +136 -136
- package/dist/i18n/ar.js +17 -17
- package/dist/i18n/de.js +17 -17
- package/dist/i18n/en-us.js +17 -17
- package/dist/i18n/es.js +17 -17
- package/dist/i18n/fr.js +17 -17
- package/dist/i18n/i18n.js +231 -231
- package/dist/i18n/index.js +52 -52
- package/dist/i18n/ja.js +17 -17
- package/dist/i18n/ko.js +17 -17
- package/dist/i18n/msg.js +2 -2
- package/dist/i18n/pt.js +17 -17
- package/dist/i18n/ru.js +17 -17
- package/dist/i18n/tag.js +18 -18
- package/dist/i18n/zh-HK.js +17 -17
- package/dist/i18n/zh-TW.js +17 -17
- package/dist/i18n/zh-cn.js +17 -17
- package/dist/index.js +14 -14
- package/dist/lock/index.js +114 -114
- package/dist/log/config.js +35 -35
- package/dist/log/date.js +21 -21
- package/dist/log/file.js +198 -198
- package/dist/log/index.js +135 -135
- package/dist/log/level.js +33 -33
- package/dist/log/log.js +56 -56
- package/dist/log/store.js +19 -19
- package/dist/mongodb/collection.js +2 -2
- package/dist/mongodb/config.js +34 -34
- package/dist/mongodb/doc.js +2 -2
- package/dist/mongodb/exception.js +14 -14
- package/dist/mongodb/index.js +58 -58
- package/dist/mongodb/manager/base.js +563 -563
- package/dist/mongodb/manager/index.js +63 -63
- package/dist/mongodb/manager/tx-strict.js +84 -84
- package/dist/mongodb/manager/tx.js +30 -30
- package/dist/mongodb/migration.js +52 -52
- package/dist/mvc/access-log.js +33 -33
- package/dist/mvc/config.js +27 -27
- package/dist/mvc/exchange.js +113 -113
- package/dist/mvc/handler/index.js +7 -6
- package/dist/mvc/handler/json.js +65 -65
- package/dist/mvc/handler/restful.js +35 -35
- package/dist/mvc/handler/sse.js +65 -0
- package/dist/mvc/handler/upload.js +31 -31
- package/dist/mvc/index.js +50 -50
- package/dist/mvc/interceptor.js +2 -2
- package/dist/mvc/query.js +43 -43
- package/dist/mvc/render/file.js +132 -132
- package/dist/mvc/render/html/html.js +90 -90
- package/dist/mvc/render/html/index.js +18 -18
- package/dist/mvc/render/html/style.js +2 -2
- package/dist/mvc/render/index.js +7 -7
- package/dist/mvc/render/json.js +26 -26
- package/dist/mvc/render/text.js +16 -16
- package/dist/mvc/router.js +2 -2
- package/dist/mvc/server.js +272 -272
- package/dist/mvc/static/header.js +67 -67
- package/dist/mvc/static/index.js +6 -6
- package/dist/mvc/static/mime-type.js +84 -84
- package/dist/mvc/static/server-cache-config.js +66 -66
- package/dist/mvc/static/server-cache.js +133 -133
- package/dist/mvc/static/static-handler.js +372 -372
- package/dist/mysql/config.js +51 -51
- package/dist/mysql/exception.js +14 -14
- package/dist/mysql/index.js +87 -87
- package/dist/mysql/manager/base.js +239 -239
- package/dist/mysql/manager/index.js +107 -107
- package/dist/mysql/manager/ops/count.js +20 -20
- package/dist/mysql/manager/ops/criteria.js +356 -356
- package/dist/mysql/manager/ops/delete.js +65 -65
- package/dist/mysql/manager/ops/exist.js +26 -26
- package/dist/mysql/manager/ops/find.js +169 -169
- package/dist/mysql/manager/ops/index.js +14 -14
- package/dist/mysql/manager/ops/insert.js +106 -106
- package/dist/mysql/manager/ops/modify.js +10 -10
- package/dist/mysql/manager/ops/paginate.js +23 -23
- package/dist/mysql/manager/ops/query.js +9 -9
- package/dist/mysql/manager/ops/update.js +216 -216
- package/dist/mysql/manager/ops/utils.js +24 -24
- package/dist/mysql/manager/tx-strict.js +103 -103
- package/dist/mysql/manager/tx.js +30 -30
- package/dist/mysql/manager/utils.js +56 -56
- package/dist/mysql/migration.js +136 -136
- package/dist/mysql/table-info.js +8 -8
- package/dist/task/daily.js +59 -59
- package/dist/task/fixed-delay.js +38 -38
- package/dist/task/fixed-rate.js +42 -42
- package/dist/task/index.js +9 -9
- package/dist/task/task.js +56 -56
- package/dist/validation/exception.js +36 -36
- package/dist/validation/index.js +40 -40
- package/dist/validation/validator/array.js +34 -34
- package/dist/validation/validator/enum.js +28 -28
- package/dist/validation/validator/index.js +14 -14
- package/dist/validation/validator/length.js +40 -40
- package/dist/validation/validator/max-length.js +35 -35
- package/dist/validation/validator/max.js +29 -29
- package/dist/validation/validator/min-length.js +33 -33
- package/dist/validation/validator/min.js +29 -29
- package/dist/validation/validator/not-blank.js +33 -33
- package/dist/validation/validator/not-null.js +21 -21
- package/dist/validation/validator/plain-obj.js +32 -32
- package/dist/validation/validator/regexp.js +34 -34
- package/documentation/en/cache.md +56 -0
- package/documentation/en/config.md +96 -0
- package/documentation/en/engineering.md +256 -0
- package/documentation/en/http-client.md +32 -0
- package/documentation/en/i18n.md +143 -0
- package/documentation/en/index.md +24 -0
- package/documentation/en/lock.md +51 -0
- package/documentation/en/log.md +109 -0
- package/documentation/en/mongodb.md +256 -0
- package/documentation/en/mvc.md +688 -0
- package/documentation/en/mysql.md +552 -0
- package/documentation/en/task.md +45 -0
- package/documentation/en/test.md +56 -0
- package/documentation/en/validate.md +130 -0
- package/documentation/zh-cn/mvc.md +66 -24
- package/package.json +3 -1
- package/skills/wok-server-api-rules/SKILL.md +350 -0
- package/skills/wok-server-cache/SKILL.md +216 -0
- package/skills/wok-server-config/SKILL.md +200 -0
- package/skills/wok-server-getting-started/SKILL.md +123 -0
- package/skills/wok-server-getting-started/references/engineering.md +169 -0
- package/skills/wok-server-http-client/SKILL.md +164 -0
- package/skills/wok-server-i18n/SKILL.md +214 -0
- package/skills/wok-server-lock/SKILL.md +144 -0
- package/skills/wok-server-log/SKILL.md +218 -0
- package/skills/wok-server-mongodb/SKILL.md +235 -0
- package/skills/wok-server-mvc/SKILL.md +251 -0
- package/skills/wok-server-mvc/references/respond-html.md +157 -0
- package/skills/wok-server-mvc/references/sse.md +121 -0
- package/skills/wok-server-mvc/references/static-files.md +47 -0
- package/skills/wok-server-mvc/references/upload.md +62 -0
- package/skills/wok-server-mvc/references/websocket.md +30 -0
- package/skills/wok-server-mysql/SKILL.md +315 -0
- package/skills/wok-server-mysql/references/multi-datasource.md +76 -0
- package/skills/wok-server-mysql/references/version-control.md +22 -0
- package/skills/wok-server-task/SKILL.md +158 -0
- package/skills/wok-server-validate/SKILL.md +167 -0
- package/src/cache/cache.ts +118 -0
- package/src/cache/config.ts +53 -0
- package/src/cache/index.ts +27 -0
- package/src/cache/purge-task.ts +53 -0
- package/src/cache/stat.ts +47 -0
- package/src/config/convert.ts +27 -0
- package/src/config/exception.ts +8 -0
- package/src/config/index.ts +92 -0
- package/src/http-client/index.ts +202 -0
- package/src/i18n/ar.ts +16 -0
- package/src/i18n/de.ts +16 -0
- package/src/i18n/en-us.ts +16 -0
- package/src/i18n/es.ts +16 -0
- package/src/i18n/fr.ts +16 -0
- package/src/i18n/i18n.ts +230 -0
- package/src/i18n/index.ts +50 -0
- package/src/i18n/ja.ts +16 -0
- package/src/i18n/ko.ts +16 -0
- package/src/i18n/msg.ts +50 -0
- package/src/i18n/pt.ts +16 -0
- package/src/i18n/ru.ts +16 -0
- package/src/i18n/tag.ts +18 -0
- package/src/i18n/zh-HK.ts +16 -0
- package/src/i18n/zh-TW.ts +16 -0
- package/src/i18n/zh-cn.ts +16 -0
- package/src/index.ts +11 -0
- package/src/lock/index.ts +164 -0
- package/src/log/config.ts +71 -0
- package/src/log/date.ts +19 -0
- package/src/log/file.ts +215 -0
- package/src/log/index.ts +136 -0
- package/src/log/level.ts +29 -0
- package/src/log/log.ts +77 -0
- package/src/log/store.ts +31 -0
- package/src/mongodb/collection.ts +25 -0
- package/src/mongodb/config.ts +69 -0
- package/src/mongodb/doc.ts +12 -0
- package/src/mongodb/exception.ts +8 -0
- package/src/mongodb/index.ts +71 -0
- package/src/mongodb/manager/base.ts +674 -0
- package/src/mongodb/manager/index.ts +80 -0
- package/src/mongodb/manager/tx-strict.ts +153 -0
- package/src/mongodb/manager/tx.ts +34 -0
- package/src/mongodb/migration.ts +66 -0
- package/src/mvc/access-log.ts +33 -0
- package/src/mvc/config.ts +70 -0
- package/src/mvc/exchange.ts +126 -0
- package/src/mvc/handler/index.ts +4 -0
- package/src/mvc/handler/json.ts +96 -0
- package/src/mvc/handler/restful.ts +39 -0
- package/src/mvc/handler/sse.ts +90 -0
- package/src/mvc/handler/upload.ts +54 -0
- package/src/mvc/index.ts +48 -0
- package/src/mvc/interceptor.ts +12 -0
- package/src/mvc/query.ts +36 -0
- package/src/mvc/render/file.ts +148 -0
- package/src/mvc/render/html/html.ts +187 -0
- package/src/mvc/render/html/index.ts +16 -0
- package/src/mvc/render/html/style.ts +1201 -0
- package/src/mvc/render/index.ts +4 -0
- package/src/mvc/render/json.ts +24 -0
- package/src/mvc/render/text.ts +14 -0
- package/src/mvc/router.ts +13 -0
- package/src/mvc/server.ts +315 -0
- package/src/mvc/static/header.ts +86 -0
- package/src/mvc/static/index.ts +3 -0
- package/src/mvc/static/mime-type.ts +81 -0
- package/src/mvc/static/server-cache-config.ts +92 -0
- package/src/mvc/static/server-cache.ts +171 -0
- package/src/mvc/static/static-handler.ts +445 -0
- package/src/mysql/config.ts +130 -0
- package/src/mysql/exception.ts +8 -0
- package/src/mysql/index.ts +88 -0
- package/src/mysql/manager/base.ts +285 -0
- package/src/mysql/manager/index.ts +112 -0
- package/src/mysql/manager/ops/count.ts +30 -0
- package/src/mysql/manager/ops/criteria.ts +412 -0
- package/src/mysql/manager/ops/delete.ts +96 -0
- package/src/mysql/manager/ops/exist.ts +41 -0
- package/src/mysql/manager/ops/find.ts +226 -0
- package/src/mysql/manager/ops/index.ts +11 -0
- package/src/mysql/manager/ops/insert.ts +120 -0
- package/src/mysql/manager/ops/modify.ts +14 -0
- package/src/mysql/manager/ops/paginate.ts +60 -0
- package/src/mysql/manager/ops/query.ts +13 -0
- package/src/mysql/manager/ops/update.ts +294 -0
- package/src/mysql/manager/ops/utils.ts +20 -0
- package/src/mysql/manager/tx-strict.ts +138 -0
- package/src/mysql/manager/tx.ts +31 -0
- package/src/mysql/manager/utils.ts +75 -0
- package/src/mysql/migration.ts +149 -0
- package/src/mysql/table-info.ts +41 -0
- package/src/task/daily.ts +70 -0
- package/src/task/fixed-delay.ts +45 -0
- package/src/task/fixed-rate.ts +49 -0
- package/src/task/index.ts +4 -0
- package/src/task/task.ts +70 -0
- package/src/validation/exception.ts +27 -0
- package/src/validation/index.ts +61 -0
- package/src/validation/validator/array.ts +32 -0
- package/src/validation/validator/enum.ts +25 -0
- package/src/validation/validator/index.ts +11 -0
- package/src/validation/validator/length.ts +42 -0
- package/src/validation/validator/max-length.ts +33 -0
- package/src/validation/validator/max.ts +26 -0
- package/src/validation/validator/min-length.ts +31 -0
- package/src/validation/validator/min.ts +26 -0
- package/src/validation/validator/not-blank.ts +31 -0
- package/src/validation/validator/not-null.ts +19 -0
- package/src/validation/validator/plain-obj.ts +30 -0
- package/src/validation/validator/regexp.ts +32 -0
- package/types/cache/cache.d.ts +52 -52
- package/types/cache/config.d.ts +32 -32
- package/types/cache/index.d.ts +2 -2
- package/types/cache/purge-task.d.ts +11 -11
- package/types/cache/stat.d.ts +26 -26
- package/types/config/convert.d.ts +6 -6
- package/types/config/exception.d.ts +7 -7
- package/types/config/index.d.ts +25 -25
- package/types/http-client/index.d.ts +71 -71
- package/types/i18n/ar.d.ts +2 -2
- package/types/i18n/de.d.ts +2 -2
- package/types/i18n/en-us.d.ts +2 -2
- package/types/i18n/es.d.ts +2 -2
- package/types/i18n/fr.d.ts +2 -2
- package/types/i18n/i18n.d.ts +102 -102
- package/types/i18n/index.d.ts +9 -9
- package/types/i18n/ja.d.ts +2 -2
- package/types/i18n/ko.d.ts +2 -2
- package/types/i18n/msg.d.ts +50 -50
- package/types/i18n/pt.d.ts +2 -2
- package/types/i18n/ru.d.ts +2 -2
- package/types/i18n/tag.d.ts +11 -11
- package/types/i18n/zh-HK.d.ts +2 -2
- package/types/i18n/zh-TW.d.ts +2 -2
- package/types/i18n/zh-cn.d.ts +2 -2
- package/types/index.d.ts +11 -11
- package/types/lock/index.d.ts +64 -64
- package/types/log/config.d.ts +35 -35
- package/types/log/date.d.ts +2 -2
- package/types/log/file.d.ts +13 -13
- package/types/log/index.d.ts +53 -53
- package/types/log/level.d.ts +14 -14
- package/types/log/log.d.ts +40 -40
- package/types/log/store.d.ts +19 -19
- package/types/mongodb/collection.d.ts +25 -25
- package/types/mongodb/config.d.ts +45 -45
- package/types/mongodb/doc.d.ts +11 -11
- package/types/mongodb/exception.d.ts +7 -7
- package/types/mongodb/index.d.ts +29 -29
- package/types/mongodb/manager/base.d.ts +188 -188
- package/types/mongodb/manager/index.d.ts +38 -38
- package/types/mongodb/manager/tx-strict.d.ts +41 -41
- package/types/mongodb/manager/tx.d.ts +21 -21
- package/types/mongodb/migration.d.ts +12 -12
- package/types/mvc/access-log.d.ts +7 -7
- package/types/mvc/config.d.ts +42 -42
- package/types/mvc/exchange.d.ts +72 -72
- package/types/mvc/handler/index.d.ts +4 -3
- package/types/mvc/handler/json.d.ts +44 -44
- package/types/mvc/handler/restful.d.ts +11 -11
- package/types/mvc/handler/sse.d.ts +34 -0
- package/types/mvc/handler/upload.d.ts +36 -36
- package/types/mvc/index.d.ts +22 -22
- package/types/mvc/interceptor.d.ts +11 -11
- package/types/mvc/query.d.ts +13 -13
- package/types/mvc/render/file.d.ts +10 -10
- package/types/mvc/render/html/html.d.ts +98 -98
- package/types/mvc/render/html/index.d.ts +11 -11
- package/types/mvc/render/html/style.d.ts +1201 -1201
- package/types/mvc/render/index.d.ts +4 -4
- package/types/mvc/render/json.d.ts +17 -17
- package/types/mvc/render/text.d.ts +10 -10
- package/types/mvc/router.d.ts +11 -11
- package/types/mvc/server.d.ts +90 -90
- package/types/mvc/static/header.d.ts +27 -27
- package/types/mvc/static/index.d.ts +3 -3
- package/types/mvc/static/mime-type.d.ts +2 -2
- package/types/mvc/static/server-cache-config.d.ts +30 -30
- package/types/mvc/static/server-cache.d.ts +76 -76
- package/types/mvc/static/static-handler.d.ts +77 -77
- package/types/mysql/config.d.ts +90 -90
- package/types/mysql/exception.d.ts +7 -7
- package/types/mysql/index.d.ts +16 -16
- package/types/mysql/manager/base.d.ts +165 -165
- package/types/mysql/manager/index.d.ts +36 -36
- package/types/mysql/manager/ops/count.d.ts +13 -13
- package/types/mysql/manager/ops/criteria.d.ts +134 -134
- package/types/mysql/manager/ops/delete.d.ts +46 -46
- package/types/mysql/manager/ops/exist.d.ts +6 -6
- package/types/mysql/manager/ops/find.d.ts +86 -86
- package/types/mysql/manager/ops/index.d.ts +10 -10
- package/types/mysql/manager/ops/insert.d.ts +18 -18
- package/types/mysql/manager/ops/modify.d.ts +3 -3
- package/types/mysql/manager/ops/paginate.d.ts +36 -36
- package/types/mysql/manager/ops/query.d.ts +3 -3
- package/types/mysql/manager/ops/update.d.ts +76 -76
- package/types/mysql/manager/ops/utils.d.ts +5 -5
- package/types/mysql/manager/tx-strict.d.ts +36 -36
- package/types/mysql/manager/tx.d.ts +15 -15
- package/types/mysql/manager/utils.d.ts +17 -17
- package/types/mysql/migration.d.ts +8 -8
- package/types/mysql/table-info.d.ts +36 -36
- package/types/task/daily.d.ts +16 -16
- package/types/task/fixed-delay.d.ts +9 -9
- package/types/task/fixed-rate.d.ts +9 -9
- package/types/task/index.d.ts +4 -4
- package/types/task/task.d.ts +34 -34
- package/types/validation/exception.d.ts +38 -38
- package/types/validation/index.d.ts +32 -32
- package/types/validation/validator/array.d.ts +5 -5
- package/types/validation/validator/enum.d.ts +8 -8
- package/types/validation/validator/index.d.ts +11 -11
- package/types/validation/validator/length.d.ts +10 -10
- package/types/validation/validator/max-length.d.ts +8 -8
- package/types/validation/validator/max.d.ts +7 -7
- package/types/validation/validator/min-length.d.ts +6 -6
- package/types/validation/validator/min.d.ts +7 -7
- package/types/validation/validator/not-blank.d.ts +7 -7
- package/types/validation/validator/not-null.d.ts +6 -6
- package/types/validation/validator/plain-obj.d.ts +7 -7
- package/types/validation/validator/regexp.d.ts +8 -8
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
# MVC
|
|
2
|
+
|
|
3
|
+
The MVC component is built on Node.js's built-in http module, making it easier to build HTTP servers and handle requests.
|
|
4
|
+
|
|
5
|
+
### Environment Variables
|
|
6
|
+
|
|
7
|
+
| Environment Variable | Description |
|
|
8
|
+
| :--------------------------- | :-------------------------------------------------------------------------- |
|
|
9
|
+
| SERVER_PORT | Port number |
|
|
10
|
+
| SERVER_TIMEOUT | Timeout in milliseconds, default 30000 |
|
|
11
|
+
| SERVER_ACCESS_LOG | Whether to enable access log, disabled by default. Set to true to enable |
|
|
12
|
+
| SERVER_CORS_ALLOW_ORIGIN | Allowed CORS origin, default * |
|
|
13
|
+
| SERVER_CORS_ALLOW_HEADERS | Allowed CORS headers, default * |
|
|
14
|
+
| SERVER_CORS_ALLOW_METHODS | Allowed CORS methods, default * |
|
|
15
|
+
|
|
16
|
+
### Getting Started
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// Start server
|
|
20
|
+
await startWebServer({
|
|
21
|
+
// Routes. In actual development with many pages, each route handler can be written in a separate file
|
|
22
|
+
routers: {
|
|
23
|
+
'/': async exchange => exchange.respondText('Hello world!')
|
|
24
|
+
},
|
|
25
|
+
// Interceptors for authorization validation, exception handling, etc.
|
|
26
|
+
interceptors: [
|
|
27
|
+
async (exchange, next) => {
|
|
28
|
+
// Example: HTTP Basic Authentication
|
|
29
|
+
if (!validateAuth(exchange.request.headers.authorization)) {
|
|
30
|
+
// Respond with authentication required
|
|
31
|
+
exchange.respond({
|
|
32
|
+
statusCode: 401,
|
|
33
|
+
headers: { 'www-Authenticate': 'Basic realm="family"' }
|
|
34
|
+
})
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
// Call next to continue
|
|
38
|
+
await next()
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Starting the server mainly requires two parameters: routes and interceptors. Routes handle specific requests, and interceptors execute before routes to intercept requests. These are detailed below.
|
|
45
|
+
|
|
46
|
+
The server can be stopped when needed.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// Stop server
|
|
50
|
+
await stopWebServer()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Routes
|
|
54
|
+
|
|
55
|
+
Route configuration is key-value pairs where the key is the path and the value is an async handler function that receives an exchange parameter of type ServerExchange, providing methods to read request information and respond with common formats.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
await startWebServer({
|
|
59
|
+
// Routes
|
|
60
|
+
routers: {
|
|
61
|
+
'/': async exchange => {
|
|
62
|
+
// Get request information via exchange
|
|
63
|
+
const url = exchange.request.url
|
|
64
|
+
const method = exchange.request.method
|
|
65
|
+
const referer = exchange.request.headers.referer
|
|
66
|
+
// Read body
|
|
67
|
+
const body = await exchange.bodyJson()
|
|
68
|
+
// Respond
|
|
69
|
+
exchange.respondJson({ ok: true })
|
|
70
|
+
},
|
|
71
|
+
'/users': async exchange => {
|
|
72
|
+
const list = await listUser()
|
|
73
|
+
exchange.respondJson(list)
|
|
74
|
+
},
|
|
75
|
+
'*': async exchange => {
|
|
76
|
+
exchange.respondText(`404 Not Found`, 404)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`*` is a special path for custom 404 pages, handling all requests that fail to match any path. If no 404 page is set, a JSON error message is returned by default.
|
|
83
|
+
|
|
84
|
+
**Currently, route paths only support exact addresses, not fuzzy paths or variable binding**, such as `/users/:id` where user id is part of the path. This is not supported due to performance considerations, but may be considered in future versions.
|
|
85
|
+
|
|
86
|
+
### Interceptors
|
|
87
|
+
|
|
88
|
+
Interceptor configuration is a list where each interceptor is an async function receiving two parameters: exchange and next. exchange is of type ServerExchange, same as in routes. next is a parameterless async function that calls the next interceptor or route.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// Start server
|
|
92
|
+
await startWebServer({
|
|
93
|
+
// Routes
|
|
94
|
+
routers: {
|
|
95
|
+
// Route configuration omitted
|
|
96
|
+
},
|
|
97
|
+
// Interceptors for authorization validation, exception handling, etc.
|
|
98
|
+
interceptors: [
|
|
99
|
+
async (exchange, next) => {
|
|
100
|
+
// Get native request and response objects
|
|
101
|
+
const { request, response } = exchange
|
|
102
|
+
const url = request.url
|
|
103
|
+
const userAgent = request.headers['user-agent']
|
|
104
|
+
const ip = request.socket.remoteAddr
|
|
105
|
+
try {
|
|
106
|
+
// Call next to continue
|
|
107
|
+
await next()
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// Example: Handle validation exceptions uniformly in interceptor
|
|
110
|
+
if (e instanceof ValidationException) {
|
|
111
|
+
// Use utility method on exchange for quick response
|
|
112
|
+
exchange.respondErrMsg(e.errMsg, 400)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
// Throw unhandled exceptions directly
|
|
116
|
+
// Framework responds with 500 status code and logs error for troubleshooting
|
|
117
|
+
throw e
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Exception Handling
|
|
125
|
+
|
|
126
|
+
For business processing failures, the recommended approach is to throw exceptions to abort processing, then handle them uniformly by interceptors and respond. However, this is not mandatory. The framework does not provide any preset types or functions; you need to extend them according to actual needs. Here is an example:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
/**
|
|
130
|
+
* Custom business exception.
|
|
131
|
+
*/
|
|
132
|
+
export class BusinessException {
|
|
133
|
+
constructor(
|
|
134
|
+
/**
|
|
135
|
+
* Message.
|
|
136
|
+
*/
|
|
137
|
+
readonly message: string,
|
|
138
|
+
/**
|
|
139
|
+
* Custom status code, default 400
|
|
140
|
+
*/
|
|
141
|
+
readonly status?: number
|
|
142
|
+
) {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Global exception interceptor that handles specific exceptions and provides appropriate response messages.
|
|
147
|
+
* @param exchange
|
|
148
|
+
* @param next
|
|
149
|
+
*/
|
|
150
|
+
export async function globalErrorInterceptor(
|
|
151
|
+
exchange: ServerExchange,
|
|
152
|
+
next: () => Promise<void>
|
|
153
|
+
): Promise<void> {
|
|
154
|
+
try {
|
|
155
|
+
await next()
|
|
156
|
+
} catch (e) {
|
|
157
|
+
// Handle custom business exceptions
|
|
158
|
+
if (e instanceof BusinessException) {
|
|
159
|
+
const status = typeof e.status === 'number' ? e.status : 400
|
|
160
|
+
const message = e.message || ''
|
|
161
|
+
exchange.respondErrMsg(message, status)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
// Handle validation exceptions
|
|
165
|
+
if (e instanceof ValidationException) {
|
|
166
|
+
exchange.respondErrMsg(e.errMsg, 400)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
// Other exceptions are thrown directly, framework responds with 500 status code by default
|
|
170
|
+
throw e
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
In actual development, multiple business exceptions can be set according to needs, responding with different JSON formats. The respondErrMsg method responds with a preset JSON format. Custom formats can be responded via respondJson method.
|
|
176
|
+
|
|
177
|
+
Set the error handling interceptor as the first interceptor, and use custom exceptions during request processing.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// Start server
|
|
181
|
+
await startWebServer({
|
|
182
|
+
interceptors: [
|
|
183
|
+
// Set error handling interceptor first
|
|
184
|
+
globalErrorInterceptor,
|
|
185
|
+
// Other interceptors follow
|
|
186
|
+
authInterceptor
|
|
187
|
+
],
|
|
188
|
+
// Routes
|
|
189
|
+
routers: {
|
|
190
|
+
'/order/cancel': createJsonHandler<{ id: string }>({
|
|
191
|
+
validation: { id: [notBlank()] },
|
|
192
|
+
async handle(body, exchange) {
|
|
193
|
+
const order = await findOrderById(body.id)
|
|
194
|
+
if (!order) {
|
|
195
|
+
// Use custom business exception to interrupt processing
|
|
196
|
+
throw new BusinessException('Order not found')
|
|
197
|
+
}
|
|
198
|
+
// Continue processing...
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
// Route configuration omitted
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Handle Different Methods
|
|
207
|
+
|
|
208
|
+
The component provides a restful function to dispatch by request method when configuring routes.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
// Start server
|
|
212
|
+
await startWebServer({
|
|
213
|
+
// Routes
|
|
214
|
+
routers: {
|
|
215
|
+
'/users': restful({
|
|
216
|
+
get: async exchange => {
|
|
217
|
+
// Handle GET request
|
|
218
|
+
},
|
|
219
|
+
post: async exchange => {
|
|
220
|
+
// Handle POST request
|
|
221
|
+
},
|
|
222
|
+
delete: async exchange => {
|
|
223
|
+
// Handle DELETE request
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Note: Routes do not support RESTful dynamic paths, as explained earlier.
|
|
231
|
+
|
|
232
|
+
### JSON Request Handling
|
|
233
|
+
|
|
234
|
+
The framework provides createJsonHandler function to easily create a RouterHandler for handling JSON requests. Both request and response are JSON format, and can be empty. Use type {} for empty request and void for empty response.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// JSON request body definition
|
|
238
|
+
interface Form {
|
|
239
|
+
name: string
|
|
240
|
+
age: number
|
|
241
|
+
}
|
|
242
|
+
// JSON response definition
|
|
243
|
+
interface Resp {
|
|
244
|
+
id: string
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const userCreateHandler = createJsonHandler<Form, Resp>({
|
|
248
|
+
/**
|
|
249
|
+
* Request body mapping object validation rules, optional.
|
|
250
|
+
*/
|
|
251
|
+
validation: {
|
|
252
|
+
name: [notBlank('Name cannot be empty'), length({ min: 2, max: 16, message: 'Name must be 2-16 characters' })],
|
|
253
|
+
age: [notNull('Age required'), min(2), max(16)]
|
|
254
|
+
},
|
|
255
|
+
async handle(body, exchange) {
|
|
256
|
+
// Get authorization
|
|
257
|
+
const { authorization } = exchange.headers
|
|
258
|
+
const currentUser = await findUserByAuth(authorization)
|
|
259
|
+
console.log(`Name: ${body.name}`)
|
|
260
|
+
console.log(`Age: ${body.age}`)
|
|
261
|
+
const newUser = await createUser(body)
|
|
262
|
+
return { id: newUser.id }
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Validation automatically switches the validator's language based on the `accept-language` header. For validations without custom error messages, the switched language's default message is used.
|
|
268
|
+
|
|
269
|
+
Starting from version 0.3.0, createJsonHandler supports caching response results through the cache option. When the cache can be reused, it avoids executing the entire handle method flow, improving performance.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
export const userGetHandler = createJsonHandler<Form, User>({
|
|
273
|
+
// Set cache, same parameters as handle method
|
|
274
|
+
// Only caches valid responses, no caching if no response body
|
|
275
|
+
async cache(body, exchange) {
|
|
276
|
+
// Build cache key using id parameter
|
|
277
|
+
const key = `get-user-${body.id}`
|
|
278
|
+
// Return cache key and optional expiration time
|
|
279
|
+
return { key, expiresInSeconds: 60 }
|
|
280
|
+
},
|
|
281
|
+
async handle(body, exchange) {
|
|
282
|
+
// Handle flow omitted...
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Since caching is based on the cache component, you can also clear the cache through the cache component.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
export const userUpdateHandler = createJsonHandler<Form>({
|
|
291
|
+
async handle(body, exchange) {
|
|
292
|
+
// Handle flow omitted...
|
|
293
|
+
// Clear user detail cache
|
|
294
|
+
getCache().remove(`get-user-${body.id}`)
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
However, do not use the cache component to retrieve cached content, as the cached content is stored as Buffer for performance reasons, avoiding re-serialization when using the cache. It is not the object returned by the handle method.
|
|
300
|
+
|
|
301
|
+
### Binary Upload
|
|
302
|
+
|
|
303
|
+
Binary upload means sending files as binary data in the request body. The request body contains only the file content without additional information. The Content-Type is application/octet-stream.
|
|
304
|
+
|
|
305
|
+
The advantage is simplicity and better performance, as the server receives the file directly without parsing like multipart/form-data. However, since the request body only contains the file, other business parameters must be passed through query strings.
|
|
306
|
+
|
|
307
|
+
The framework provides createUploadHandler function to create route handlers for binary format requests, responding with JSON format. Here is an example:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
interface Resp {
|
|
311
|
+
/**
|
|
312
|
+
* New avatar URL
|
|
313
|
+
*/
|
|
314
|
+
url: string
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Example: Upload avatar
|
|
319
|
+
*/
|
|
320
|
+
export const uploadAvatar = createUploadHandler<Resp>({
|
|
321
|
+
async handle(body, exchange) {
|
|
322
|
+
// Get userId parameter from Query String
|
|
323
|
+
const userId = exchange.query.getStr('userId') as string
|
|
324
|
+
// Validate parameters
|
|
325
|
+
validate(
|
|
326
|
+
{ userId },
|
|
327
|
+
{
|
|
328
|
+
userId: [notBlank(), maxLength(64)]
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
// Check file size
|
|
332
|
+
if (body.byteLength > 2 * 1024 * 1024) {
|
|
333
|
+
// BusinessException is a custom business exception, handled by interceptor
|
|
334
|
+
throw new BusinessException('File size cannot exceed 2MB')
|
|
335
|
+
}
|
|
336
|
+
const user = await getMysqlManager().findById(tableUser, userId)
|
|
337
|
+
if (!user) {
|
|
338
|
+
throw new BusinessException('User not found')
|
|
339
|
+
}
|
|
340
|
+
// Simulate uploading to object storage, oss is object storage service SDK
|
|
341
|
+
const key = `users/${userId}/avatar`
|
|
342
|
+
await oss.putObject(key, body)
|
|
343
|
+
await getMysqlManager().partialUpdate(tableUser, { id: userId, avatar_key: key })
|
|
344
|
+
return { url: oss.getUrl(key) }
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
For higher flexibility, such as not returning JSON format, define a regular route handler and use the bodyBuffer method on exchange to get the request body.
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
export const uploadAvatar: RouterHandler = async exchange => {
|
|
353
|
+
// Read file
|
|
354
|
+
const file = await exchange.bodyBuffer()
|
|
355
|
+
// Get parameters from Query String
|
|
356
|
+
const query = exchange.parseQueryString()
|
|
357
|
+
const userId = query.getStr('userId')
|
|
358
|
+
|
|
359
|
+
// Parameter validation and file storage omitted...
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### multipart/form-data File Upload
|
|
364
|
+
|
|
365
|
+
The component does not currently handle multipart/form-data requests. Third-party libraries can be used to parse file upload requests.
|
|
366
|
+
|
|
367
|
+
Here is an example using formidable:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
// Start server
|
|
371
|
+
import formidable from 'formidable'
|
|
372
|
+
|
|
373
|
+
await startWebServer({
|
|
374
|
+
// Routes
|
|
375
|
+
routers: {
|
|
376
|
+
'/cover': async exchange => {
|
|
377
|
+
const form = formidable({})
|
|
378
|
+
// Parse request
|
|
379
|
+
const [fields, files] = await form.parse(exchange.request)
|
|
380
|
+
// TODO continue business processing
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
If other libraries are used to read the request content, the bodyXxx methods in exchange cannot be used, otherwise an exception is thrown. Similarly, after calling bodyXxx methods, other libraries cannot read the content, otherwise incomplete content will be read or exceptions will occur. However, bodyXxx methods can be called repeatedly without errors. Calling bodyText then bodyJson is allowed.
|
|
387
|
+
|
|
388
|
+
### Respond HTML
|
|
389
|
+
|
|
390
|
+
The ServerExchange type provides a respondHtml method for rendering HTML, easily organizing tag hierarchies and dynamically building structures.
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
// Start server
|
|
394
|
+
await startWebServer({
|
|
395
|
+
// Routes
|
|
396
|
+
routers: {
|
|
397
|
+
'/profile': async exchange => {
|
|
398
|
+
// Get user info
|
|
399
|
+
const user = await getUser(exchange.headers.authorization)
|
|
400
|
+
// HTML structure logic may be long, complex business can be extracted into separate functions
|
|
401
|
+
exchange.respondHtml({
|
|
402
|
+
lang: 'zh',
|
|
403
|
+
head: [
|
|
404
|
+
// Add tags to head, here add a title
|
|
405
|
+
// Strings in children represent TextNode
|
|
406
|
+
{ tag: 'title', children: ['Profile'] },
|
|
407
|
+
{ tag: 'script', attrs: { type: 'module', src: 'main.js' } }
|
|
408
|
+
],
|
|
409
|
+
body: {
|
|
410
|
+
// Attributes with type inference for HTML global attributes like style, id, class
|
|
411
|
+
attrs: {
|
|
412
|
+
// Style has type inference
|
|
413
|
+
style: { 'background-color': 'white' }
|
|
414
|
+
},
|
|
415
|
+
// children can also accept a function whose parameter is a function to add child elements
|
|
416
|
+
// This allows dynamic rendering with loops and conditionals
|
|
417
|
+
children: add => {
|
|
418
|
+
add({ tag: 'h1', children: ['Profile'] })
|
|
419
|
+
// Render different elements based on user presence
|
|
420
|
+
if (user) {
|
|
421
|
+
add({ tag: 'p', children: [`Username: ${user.account}`] })
|
|
422
|
+
} else {
|
|
423
|
+
add({
|
|
424
|
+
tag: 'p',
|
|
425
|
+
children: [
|
|
426
|
+
'Please login to view',
|
|
427
|
+
{ tag: 'a', attrs: { href: '/login' }, children: ['Click to login'] }
|
|
428
|
+
]
|
|
429
|
+
})
|
|
430
|
+
}
|
|
431
|
+
// Common combinations can be encapsulated into functions returning HtmlTag type
|
|
432
|
+
// footer is such an example
|
|
433
|
+
add(footer())
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
})
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
})
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
footer function example:
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
function footer(): HtmlTag {
|
|
446
|
+
return {
|
|
447
|
+
tag: 'div',
|
|
448
|
+
attrs: { class: 'footer' },
|
|
449
|
+
children: [
|
|
450
|
+
{ tag: 'a', attrs: { href: '/about' }, children: ['About Us'] },
|
|
451
|
+
{ tag: 'a', attrs: { href: '/call' }, children: ['Contact Us'] },
|
|
452
|
+
{ tag: 'a', attrs: { href: '/privacy' }, children: ['Privacy Policy'] }
|
|
453
|
+
]
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
If you don't like the framework's built-in HTML rendering, you can use third-party template rendering components.
|
|
459
|
+
|
|
460
|
+
Here is an example using handlebars:
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
import { compile } from 'handlebars'
|
|
464
|
+
|
|
465
|
+
await startWebServer({
|
|
466
|
+
// Routes
|
|
467
|
+
routers: {
|
|
468
|
+
'/html': async exchange => {
|
|
469
|
+
const source =
|
|
470
|
+
'<!DOCTYPE html>' +
|
|
471
|
+
'<html>' +
|
|
472
|
+
'<head>' +
|
|
473
|
+
'<title>Handlebars Example</title>' +
|
|
474
|
+
'</head>' +
|
|
475
|
+
'<body>' +
|
|
476
|
+
'<p>Hello, my name is {{name}}. I am from {{hometown}}. I have ' +
|
|
477
|
+
'{{kids.length}} kids:</p>' +
|
|
478
|
+
'<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>' +
|
|
479
|
+
'</body>' +
|
|
480
|
+
'</html>'
|
|
481
|
+
// Compile template
|
|
482
|
+
const template = compile(source)
|
|
483
|
+
// Data
|
|
484
|
+
const data = {
|
|
485
|
+
name: 'Alan',
|
|
486
|
+
hometown: 'Somewhere, TX',
|
|
487
|
+
kids: [
|
|
488
|
+
{ name: 'Jimmy', age: '12' },
|
|
489
|
+
{ name: 'Sally', age: '4' }
|
|
490
|
+
]
|
|
491
|
+
}
|
|
492
|
+
// Build content
|
|
493
|
+
const result = template(data)
|
|
494
|
+
// Render HTML string
|
|
495
|
+
exchange.respondHtml(result)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
Here is an example using Vue 3.x SSR to render HTML:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
import { createSSRApp } from 'vue'
|
|
505
|
+
import { renderToString } from 'vue/server-renderer'
|
|
506
|
+
|
|
507
|
+
await startWebServer({
|
|
508
|
+
// Routes
|
|
509
|
+
routers: {
|
|
510
|
+
'/html': async exchange => {
|
|
511
|
+
const app = createSSRApp({
|
|
512
|
+
data: () => ({ count: 1 }),
|
|
513
|
+
template: `<button>{{ count }}</button>`
|
|
514
|
+
})
|
|
515
|
+
const html = await renderToString(app)
|
|
516
|
+
// Render page with generated HTML
|
|
517
|
+
exchange.respondHtml(`<!DOCTYPE html>
|
|
518
|
+
<html>
|
|
519
|
+
<head>
|
|
520
|
+
<title>Vue SSR Example</title>
|
|
521
|
+
</head>
|
|
522
|
+
<body>
|
|
523
|
+
<div id="app">${html}</div>
|
|
524
|
+
</body>
|
|
525
|
+
</html>`)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
})
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Static Files
|
|
532
|
+
|
|
533
|
+
Static file directory mapping can be set via the static parameter, mapping a directory to a request path.
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
await startWebServer({
|
|
537
|
+
static: {
|
|
538
|
+
'/a': { dir: 'D:\\Download', cacheAge: 300 },
|
|
539
|
+
'/a/b': { dir: 'E:\\Dowload', cacheAge: 150 },
|
|
540
|
+
'/b': { dir: 'static', cacheAge: 0 }
|
|
541
|
+
},
|
|
542
|
+
routers: {}
|
|
543
|
+
})
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
The dir parameter is the mapped directory path, which can be absolute or relative. Relative paths are resolved from the current working directory. The cacheAge parameter is the cache time. If set to a value greater than 0, a Cache-Control header is generated accordingly.
|
|
547
|
+
|
|
548
|
+
Request paths only support prefix matching, not wildcards. For example, /a/demo.html matches /a path and responds with the demo.html file from the configured directory. Path configurations have priority. Accessing /a/b/music.mp3 matches the /a/b configuration, not /a, because /a/b is more specific and has higher priority. If the file is not found in the /a/b directory, the /a configuration is not tried.
|
|
549
|
+
|
|
550
|
+
Static files also support automatic index page mapping. For example, accessing /a/b/c matches the /a/b configuration, then looks for file c in the configured directory. If not found, it tries to find index.html in directory c.
|
|
551
|
+
|
|
552
|
+
#### Static File Server Cache
|
|
553
|
+
|
|
554
|
+
Starting from version 0.3.0, server-side caching of static files is supported. To enable static file caching, configure the following environment variables:
|
|
555
|
+
|
|
556
|
+
| Environment Variable | Description |
|
|
557
|
+
| :--------------------------------- | :-------------------------------------------------------------------------- |
|
|
558
|
+
| SERVER_STATIC_CACHE_ENABLE | Whether to enable server cache, default false |
|
|
559
|
+
| SERVER_STATIC_CACHE_MAX_AGE | Server cache time in seconds, default 600 |
|
|
560
|
+
| SERVER_STATIC_CACHE_MAX_FILE_SIZE | Maximum cacheable file size, supports semantic format like 10m and 100k, default 10m |
|
|
561
|
+
| SERVER_STATIC_CACHE_MAX_SIZE | Maximum cache size, triggers cleanup when exceeded. Same semantic format support, default 100m |
|
|
562
|
+
|
|
563
|
+
Version 0.3.2 adds the removeServerStaticCache function to actively delete specified server-side static cache.
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
import { removeServerStaticCache } from 'wok-server'
|
|
567
|
+
|
|
568
|
+
removeServerStaticCache('/assets/index.js')
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Request Logs
|
|
572
|
+
|
|
573
|
+
Set the environment variable SERVER_ACCESS_LOG to true to enable request logging. When enabled, request and response information is output to the log after each response, disabled by default.
|
|
574
|
+
|
|
575
|
+
```
|
|
576
|
+
[2023/10/25 17:09:47.872][INFO][access-log]{"method":"GET","url":"/html?tab=%E6%9C%8D%E8%A3%85","ip":"::ffff:127.0.0.1","start":"2023/10/25 17:09:47.871","rt":1,"status":200}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
The component only provides simple JSON format output and does not support format configuration. For request statistics, consider custom interceptors to store request information in message queues or databases for calculation, or analyze logs to extract request information.
|
|
580
|
+
|
|
581
|
+
### WebSocket
|
|
582
|
+
|
|
583
|
+
The component does not provide WebSocket functionality directly, but the startWebServer function provides a preHandler parameter for pre-processing the server, allowing additional operations before server startup to integrate other libraries that support the native http module.
|
|
584
|
+
|
|
585
|
+
WebSocket support can be implemented by integrating socket.io.
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
import { Server } from 'socket.io'
|
|
589
|
+
await startWebServer({
|
|
590
|
+
routers: {
|
|
591
|
+
// Route configuration omitted...
|
|
592
|
+
},
|
|
593
|
+
// Pre-handler to connect socket.io
|
|
594
|
+
preHandler: async server => {
|
|
595
|
+
const io = new Server(server)
|
|
596
|
+
// Adapt /chat path. If /chat also exists in routes, there's no conflict
|
|
597
|
+
// HTTP requests from clients are handled by routes
|
|
598
|
+
// Of course, this is not recommended
|
|
599
|
+
io.of('/chat').on('connection', socket => {
|
|
600
|
+
// Manage sessions and handle business in callback after connection
|
|
601
|
+
socket.on('message', data => {
|
|
602
|
+
/* Handle custom events */
|
|
603
|
+
})
|
|
604
|
+
socket.on('disconnect', () => {
|
|
605
|
+
/* Handle disconnection */
|
|
606
|
+
})
|
|
607
|
+
})
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### Server-Sent Events
|
|
613
|
+
|
|
614
|
+
Starting from version 0.4.0, the MVC component includes `createSseHandler`, which encapsulates SSE protocol details and greatly simplifies server push implementation.
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
import { createSseHandler } from 'wok-server'
|
|
618
|
+
|
|
619
|
+
await startWebServer({
|
|
620
|
+
routers: {
|
|
621
|
+
'/sse': createSseHandler({
|
|
622
|
+
async handle(ctx) {
|
|
623
|
+
let counter = 0
|
|
624
|
+
for (let i = 0; i < 10; i++) {
|
|
625
|
+
await new Promise<void>(resolve => setTimeout(resolve, 1000))
|
|
626
|
+
counter++
|
|
627
|
+
ctx.send({ message: 'Real-time update', count: counter })
|
|
628
|
+
if (counter >= 10) {
|
|
629
|
+
break
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
ctx.close()
|
|
633
|
+
}
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
})
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
The `handle` function receives an `SseContext` object with the following capabilities:
|
|
640
|
+
|
|
641
|
+
| Method/Property | Description |
|
|
642
|
+
| :--------------------------------------- | :----------------------------------------------------------- |
|
|
643
|
+
| `ctx.send(data, event?, id?)` | Sends an SSE event. `data` is JSON serialized. `event` specifies the event name (use `addEventListener` on frontend), `id` sets the event ID (for `Last-Event-ID` on reconnection) |
|
|
644
|
+
| `ctx.close()` | Explicitly ends the SSE connection. The connection also closes automatically when `handle` returns or throws |
|
|
645
|
+
| `ctx.request` | The raw `IncomingMessage`, for reading request headers etc. |
|
|
646
|
+
| `ctx.response` | The raw `ServerResponse`, for advanced scenarios |
|
|
647
|
+
|
|
648
|
+
#### Named Events
|
|
649
|
+
|
|
650
|
+
Send named events via the `event` parameter, allowing the frontend to handle different event types separately:
|
|
651
|
+
|
|
652
|
+
```ts
|
|
653
|
+
createSseHandler({
|
|
654
|
+
async handle(ctx) {
|
|
655
|
+
ctx.send({ title: 'New message' }, 'notification')
|
|
656
|
+
ctx.send({ progress: 50 }, 'progress')
|
|
657
|
+
}
|
|
658
|
+
})
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
Frontend:
|
|
662
|
+
|
|
663
|
+
```ts
|
|
664
|
+
const es = new EventSource('/sse')
|
|
665
|
+
es.addEventListener('notification', e => {
|
|
666
|
+
const data = JSON.parse(e.data)
|
|
667
|
+
console.log('Notification:', data.title)
|
|
668
|
+
})
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### Reconnection
|
|
672
|
+
|
|
673
|
+
Set event IDs via the `id` parameter. On reconnection, the browser automatically sends the `Last-Event-ID` header:
|
|
674
|
+
|
|
675
|
+
```ts
|
|
676
|
+
createSseHandler({
|
|
677
|
+
async handle(ctx) {
|
|
678
|
+
const lastId = ctx.request.headers['last-event-id']
|
|
679
|
+
// Determine where to resume based on lastId
|
|
680
|
+
}
|
|
681
|
+
})
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
#### Connection Lifecycle
|
|
685
|
+
|
|
686
|
+
- SSE headers are sent and the connection is established when `handle` begins execution
|
|
687
|
+
- `ctx.send()` becomes a no-op when the client disconnects (automatically ignored)
|
|
688
|
+
- The connection closes automatically when `handle` returns or throws
|