topbit 1.0.0 → 2.0.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/LICENSE +128 -0
- package/README.cn.md +1562 -0
- package/README.md +1272 -0
- package/bin/app.js +17 -0
- package/bin/loadinfo.sh +18 -0
- package/bin/new-ctl.js +230 -0
- package/bin/newapp.js +22 -0
- package/cache/allow.js +130 -0
- package/cache/errserv.js +45 -0
- package/cache/minserv.js +167 -0
- package/cache/router.js +84 -0
- package/cache/rsa/localhost-cert.pem +19 -0
- package/cache/rsa/localhost-privkey.pem +28 -0
- package/cache/servsock.js +286 -0
- package/cache/sni.js +66 -0
- package/demo/allow.js +98 -0
- package/demo/group-api.js +161 -0
- package/demo/group-api2.js +109 -0
- package/demo/log.js +118 -0
- package/demo/memlimit.js +31 -0
- package/demo/min.js +7 -0
- package/demo/serv.js +15 -0
- package/images/middleware.jpg +0 -0
- package/images/titbit-middleware.png +0 -0
- package/images/titbit.png +0 -0
- package/package.json +38 -8
- package/src/bodyparser.js +420 -0
- package/src/connfilter.js +125 -0
- package/src/context1.js +166 -0
- package/src/context2.js +179 -0
- package/src/ctxpool.js +38 -0
- package/src/ext.js +318 -0
- package/src/fastParseUrl.js +426 -0
- package/src/headerLimit.js +18 -0
- package/src/http1.js +337 -0
- package/src/http2.js +337 -0
- package/src/httpc.js +251 -0
- package/src/loader/loader.js +999 -0
- package/src/logger.js +32 -0
- package/src/loggermsg.js +349 -0
- package/src/makeId.js +200 -0
- package/src/midcore.js +213 -0
- package/src/middleware1.js +104 -0
- package/src/middleware2.js +121 -0
- package/src/monitor.js +380 -0
- package/src/movefile.js +30 -0
- package/src/optionsCheck.js +54 -0
- package/src/randstring.js +23 -0
- package/src/router.js +682 -0
- package/src/sendmsg.js +27 -0
- package/src/strong.js +72 -0
- package/src/token/token.js +462 -0
- package/src/topbit.js +1291 -0
- package/src/versionCheck.js +31 -0
- package/test/test-bigctx.js +29 -0
- package/test/test-daemon-args.js +7 -0
- package/test/test-find.js +69 -0
- package/test/test-helper.js +81 -0
- package/test/test-route-sort.js +71 -0
- package/test/test-route.js +49 -0
- package/test/test-route2.js +51 -0
- package/test/test-run-args.js +7 -0
- package/test/test-url.js +52 -0
- package/tmp/buff-code +134 -0
- package/tmp/devplan +9 -0
- package/tmp/evt-test.js +34 -0
- package/tmp/fastParseUrl.js +302 -0
- package/tmp/router-rule.js +559 -0
- package/tmp/test-cdps.js +122 -0
- package/tmp/titbit.js +1286 -0
- package/main.js +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,1272 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# titbit
|
|
4
|
+
|
|
5
|
+
**titbit** is a server-side web framework initially designed to simplify development in educational settings, but it has also been used in some production systems. It is not a heavyweight framework, but it is far from overly simplistic.
|
|
6
|
+
|
|
7
|
+
> **About Type Systems and TypeScript Support**
|
|
8
|
+
> If the ECMAScript proposal for a type system is approved, JavaScript will natively support types in the future, eliminating the need to consider TypeScript support.
|
|
9
|
+
> If this proposal is not adopted, TypeScript support will be considered later.
|
|
10
|
+
|
|
11
|
+
> Reference link: <a href="https://github.com/tc39/proposal-type-annotations" target="_blank">JavaScript Type Annotations Proposal</a>
|
|
12
|
+
|
|
13
|
+
> For bugs or questions, please submit an issue or send a private message.
|
|
14
|
+
|
|
15
|
+
> It is extremely fast, both in route lookup and middleware execution.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
A Node.js web development framework that supports both HTTP/1.1 and HTTP/2 protocols, providing a robust middleware mechanism.
|
|
19
|
+
|
|
20
|
+
**Core Features:**
|
|
21
|
+
|
|
22
|
+
* Request context design that abstracts interface differences.
|
|
23
|
+
* Middleware pattern.
|
|
24
|
+
* Route grouping and naming.
|
|
25
|
+
* Middleware execution based on route groups, matching request methods and routes.
|
|
26
|
+
* Support for running as a daemon using the `cluster` module.
|
|
27
|
+
* Display of subprocess load information.
|
|
28
|
+
* Automatic parsing of request body data.
|
|
29
|
+
* Configurable support for HTTP/1.1 or HTTP/2 services, with a compatibility mode allowing simultaneous support for both.
|
|
30
|
+
* Support for enabling HTTPS (required for HTTP/2 services).
|
|
31
|
+
* Request rate limiting.
|
|
32
|
+
* Limiting the maximum number of requests from a single IP within a time period.
|
|
33
|
+
* IP blacklists and whitelists.
|
|
34
|
+
* In cluster mode, automatic restart of subprocesses that exceed maximum memory limits.
|
|
35
|
+
* Optional automatic load balancing: creates new subprocesses based on load and reverts to the initial state when idle.
|
|
36
|
+
* Control over maximum memory usage for subprocesses, with automatic restarts when limits are exceeded or when memory usage surpasses a threshold and there are no active connections.
|
|
37
|
+
* Default configurations for network security to mitigate DDoS attacks and other security issues at the software service layer.
|
|
38
|
+
|
|
39
|
+
## !Note
|
|
40
|
+
|
|
41
|
+
Always use the latest version whenever possible. **titbit performs route lookup before creating the request context object. If no route is found, the request context object is not created.** This avoids unnecessary operations and includes detection for some erroneous or malicious requests, with error status codes 404 and 400. To customize error messages during this process, use the `notFound` and `badRequest` initialization options, which by default return simple text messages. (For routes you define, handle 404 errors internally as needed.)
|
|
42
|
+
|
|
43
|
+
## **v25.x Version Changes**
|
|
44
|
+
|
|
45
|
+
Starting with v25.0.0, the request context and related details have been updated, flattening data attributes. The `res` object in the request context has been removed, and `ctx.res.body` is no longer used to collect response data; instead, `ctx.data` is used directly.
|
|
46
|
+
|
|
47
|
+
Use the `ctx.send()` function to set the final response data. The code remains compatible, so no changes are required, and you can upgrade directly.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
npm i titbit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You can also install it using Yarn:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
yarn add titbit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Compatibility
|
|
62
|
+
|
|
63
|
+
Since v21.8.1, updates have been kept compatible, allowing seamless version upgrades without compatibility concerns. If future technological changes require breaking updates, detailed explanations will be provided. Please review the documentation and Wiki.
|
|
64
|
+
|
|
65
|
+
Before v21.8.1, major version numbers ensured compatibility.
|
|
66
|
+
|
|
67
|
+
<a href="https://gitee.com/daoio/titbit/wikis/%E7%89%88%E6%9C%AC%E6%94%B9%E8%BF%9B%E8%AF%B4%E6%98%8E?sort_id=3220595" target="_blank">· Important Version Improvements</a>
|
|
68
|
+
|
|
69
|
+
## Minimal Example
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
'use strict'
|
|
73
|
+
|
|
74
|
+
const Titbit = require('titbit')
|
|
75
|
+
|
|
76
|
+
const app = new Titbit({
|
|
77
|
+
debug: true
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
app.run(1234)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
When no routes are added, titbit automatically adds a default route:
|
|
84
|
+
|
|
85
|
+
`/*`
|
|
86
|
+
|
|
87
|
+
Visiting this in a browser displays a simple page, intended for initial exploration and documentation access. It has no impact on actual development.
|
|
88
|
+
|
|
89
|
+
## Adding a Route
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
'use strict'
|
|
93
|
+
|
|
94
|
+
const Titbit = require('titbit')
|
|
95
|
+
|
|
96
|
+
const app = new Titbit({
|
|
97
|
+
debug: true
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
app.get('/', async ctx => {
|
|
101
|
+
ctx.send('success')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Listens on 0.0.0.0 by default; parameters are consistent with the native `listen` interface.
|
|
105
|
+
app.run(1234)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`ctx.data` holds the response data, or you can use `ctx.send(data)`.
|
|
109
|
+
> Internally, `ctx.send()` sets the value of `ctx.data`.
|
|
110
|
+
**It’s recommended to use `ctx.send()` to set response data, as versions prior to v25.0.0 used `ctx.res.body` for responses, and `send` ensures compatibility.**
|
|
111
|
+
|
|
112
|
+
## Using ES6 Imports
|
|
113
|
+
|
|
114
|
+
In `.mjs` files, you can use ES6 `import` syntax:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
import Titbit from 'titbit'
|
|
118
|
+
|
|
119
|
+
const app = new Titbit({
|
|
120
|
+
debug: true
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
app.get('/', async ctx => {
|
|
124
|
+
ctx.send('success')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
app.run(1234)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Routes and Request Types
|
|
131
|
+
|
|
132
|
+
HTTP request methods (also called request types) are specified in the HTTP start line. Supported request methods:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
GET POST PUT PATCH DELETE OPTIONS TRACE HEAD
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The most commonly used are the first six. For each request type, the router provides a corresponding lowercase function for mounting routes. For convenience, these methods are also available directly on the `app` instance after initialization. (The framework only supports these methods.)
|
|
139
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
'use strict'
|
|
144
|
+
|
|
145
|
+
const Titbit = require('titbit')
|
|
146
|
+
|
|
147
|
+
const app = new Titbit({
|
|
148
|
+
debug: true
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
app.get('/', async c => {
|
|
152
|
+
c.send('success')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
app.get('/p', async c => {
|
|
156
|
+
c.send(`${c.method} ${c.routepath}`)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
app.post('/', async c => {
|
|
160
|
+
// Returns the submitted data
|
|
161
|
+
c.send(c.body)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
app.put('/p', async c => {
|
|
165
|
+
c.send({
|
|
166
|
+
method: c.method,
|
|
167
|
+
body: c.body,
|
|
168
|
+
query: c.query
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Listens on 0.0.0.0 by default; parameters are consistent with the native `listen` interface.
|
|
173
|
+
app.run(8080)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Retrieving URL Parameters
|
|
177
|
+
|
|
178
|
+
- Query string parameters (e.g., `?a=1&b=2`) are parsed into `c.query`.
|
|
179
|
+
- Form-submitted data is parsed into `c.body`.
|
|
180
|
+
|
|
181
|
+
> Form submissions typically have a `content-type` of `application/x-www-form-urlencoded`.
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
'use strict'
|
|
185
|
+
|
|
186
|
+
const titbit = require('titbit')
|
|
187
|
+
|
|
188
|
+
let app = new titbit({
|
|
189
|
+
debug: true
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
app.get('/q', async c => {
|
|
193
|
+
// Query string parameters from the URL are parsed into `query`.
|
|
194
|
+
// Returns JSON text with `content-type` set to `text/json`.
|
|
195
|
+
c.send(c.query)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
app.post('/p', async c => {
|
|
199
|
+
// Data from POST or PUT requests is stored in `body`. For forms, it is automatically parsed; otherwise, it’s raw text.
|
|
200
|
+
// Middleware can be used to handle various data types.
|
|
201
|
+
c.send(c.body)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
app.run(2019)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Retrieving POST Data
|
|
208
|
+
|
|
209
|
+
POST and PUT requests submit data in the request body, typically from form submissions or asynchronous requests.
|
|
210
|
+
|
|
211
|
+
- Form-submitted data is parsed into `c.body`.
|
|
212
|
+
|
|
213
|
+
> Forms have a `content-type` of `application/x-www-form-urlencoded`.
|
|
214
|
+
> Asynchronous requests often use `content-type: application/json`.
|
|
215
|
+
|
|
216
|
+
For both types, `c.body` is an object.
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
'use strict'
|
|
220
|
+
|
|
221
|
+
const titbit = require('titbit')
|
|
222
|
+
|
|
223
|
+
let app = new titbit({ debug: true })
|
|
224
|
+
|
|
225
|
+
app.post('/p', async c => {
|
|
226
|
+
// Data from POST or PUT requests is stored in `body`. For forms, it is automatically parsed into an object.
|
|
227
|
+
// Middleware can be used to handle various data types.
|
|
228
|
+
c.send(c.body)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
app.run(2019)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## About `content-type`
|
|
235
|
+
|
|
236
|
+
**`application/x-www-form-urlencoded`**
|
|
237
|
+
Basic form data is parsed into `c.body` as a JavaScript object.
|
|
238
|
+
|
|
239
|
+
**`text/*`**
|
|
240
|
+
For `content-type` starting with `text/`, such as `text/json`, the framework does not parse the data. It converts the uploaded data to a UTF-8 encoded string and assigns it to `c.body`. Further processing is left to the developer.
|
|
241
|
+
|
|
242
|
+
**`multipart/form-data;boundary=xxx`**
|
|
243
|
+
For file uploads, the framework parses the data by default, and the parsed file objects are stored in `c.files`, accessible via `c.getFile`.
|
|
244
|
+
|
|
245
|
+
**`application/json`**
|
|
246
|
+
This type is parsed using `JSON.parse`.
|
|
247
|
+
|
|
248
|
+
**Other Types**
|
|
249
|
+
For other `content-type` values, `c.body` points to `c.rawBody`, which is the raw `Buffer` data.
|
|
250
|
+
|
|
251
|
+
The framework provides core support for basic types, leaving other types for developers to handle or extend. To disable default body parsing, set the `parseBody` initialization option to `false`. You can also extend body parsing as needed.
|
|
252
|
+
|
|
253
|
+
The body parsing module is essentially a middleware, designed to facilitate extensions and replacements.
|
|
254
|
+
|
|
255
|
+
## The `send` Function
|
|
256
|
+
|
|
257
|
+
The `send` function is a wrapper for setting `c.data`. It supports an optional second parameter for the status code (default is 0, which uses the module’s default status code, 200 in Node.js HTTP and HTTP/2).
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
app.get('/', async c => {
|
|
261
|
+
c.send('success')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
app.get('/randerr', async c => {
|
|
265
|
+
let n = parseInt(Math.random() * 10)
|
|
266
|
+
if (n >= 5) {
|
|
267
|
+
c.send('success')
|
|
268
|
+
} else {
|
|
269
|
+
// Returns 404 status code
|
|
270
|
+
/*
|
|
271
|
+
Equivalent to:
|
|
272
|
+
c.status(404).data = 'not found'
|
|
273
|
+
*/
|
|
274
|
+
// Chainable calls are supported in v22.4.6 and above.
|
|
275
|
+
c.status(404).send('not found')
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
app.run(1234)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Chainable Calls
|
|
283
|
+
|
|
284
|
+
Starting with v22.4.6, `setHeader`, `status`, and `sendHeader` support chainable calls.
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
app.get('/', async c => {
|
|
288
|
+
c.setHeader('content-type', 'text/plain; charset=utf-8')
|
|
289
|
+
.setHeader('x-server', 'nodejs server')
|
|
290
|
+
.status(200)
|
|
291
|
+
.send(`${Date.now()} Math.random()`)
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Route Parameters
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
app.get('/:name/:id', async c => {
|
|
299
|
+
// Route parameters (denoted by `:`) are parsed into `c.param`.
|
|
300
|
+
let username = c.param.name
|
|
301
|
+
let uid = c.param.id
|
|
302
|
+
c.send(`${username} ${uid}`)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
app.run(8000)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Wildcard Path Parameters
|
|
309
|
+
|
|
310
|
+
The `*` wildcard indicates any path but must appear at the end of the route.
|
|
311
|
+
|
|
312
|
+
```javascript
|
|
313
|
+
app.get('/static/*', async c => {
|
|
314
|
+
// The wildcard path is parsed into `c.param.starPath`.
|
|
315
|
+
let spath = c.param.starPath
|
|
316
|
+
c.send(spath)
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Route Lookup Rules
|
|
321
|
+
|
|
322
|
+
Since v23.5.9, the route lookup process has been optimized, particularly for routes with parameters and wildcards, enforcing stricter order control instead of matching based on the order routes were added.
|
|
323
|
+
|
|
324
|
+
This change does not affect applications developed with earlier versions, ensuring compatibility. The stricter order reduces the likelihood of conflicts.
|
|
325
|
+
|
|
326
|
+
**Route Lookup Strategy:**
|
|
327
|
+
|
|
328
|
+
1. Exact string paths.
|
|
329
|
+
2. Routes with parameters (fewer parameters match first).
|
|
330
|
+
3. Wildcard (`*`) routes, matched from longest to shortest.
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
Example:
|
|
334
|
+
Routes: /x/y/:id /x/y/* /x/* /x/:key/:id
|
|
335
|
+
|
|
336
|
+
/x/y/123 matches /x/y/:id and stops.
|
|
337
|
+
/x/y/123/345 matches /x/y/* and stops.
|
|
338
|
+
/x/q/123 matches /x/:key/:id.
|
|
339
|
+
/x/a.jpg matches /x/*, as no other routes match.
|
|
340
|
+
/x/static/images/a.jpg matches /x/*, as no other routes match.
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Route Grouping
|
|
344
|
+
|
|
345
|
+
You can use `app.middleware` to specify middleware and use the returned `group` method to add grouped routes, or directly use `app.group` to add grouped routes.
|
|
346
|
+
|
|
347
|
+
**`Titbit.prototype.middleware(mids, options=null)`**
|
|
348
|
+
|
|
349
|
+
- `mids`: An array where each element is a middleware function or an array containing a middleware function and its options.
|
|
350
|
+
- `options`: Defaults to `null`. Pass an object to apply options to all middleware, e.g., `{pre: true}`.
|
|
351
|
+
|
|
352
|
+
**`Titbit.prototype.group(group_name, callback, prefix=true)`**
|
|
353
|
+
|
|
354
|
+
- `group_name`: A string representing the group name. If it’s a valid path, it also serves as a route prefix.
|
|
355
|
+
- `callback`: A callback function that receives a parameter for adding middleware or routes using `get`, `post`, etc.
|
|
356
|
+
- `prefix`: A boolean (default `true`) controlling whether `group_name` is used as a route prefix (only if it’s a valid route string).
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
'use strict'
|
|
360
|
+
|
|
361
|
+
const Titbit = require('titbit')
|
|
362
|
+
|
|
363
|
+
const app = new Titbit({
|
|
364
|
+
debug: true
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
// Middleware function
|
|
368
|
+
let mid_timing = async (c, next) => {
|
|
369
|
+
console.time('request')
|
|
370
|
+
await next()
|
|
371
|
+
console.timeEnd('request')
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// The group return value supports `use` and `pre` for adding middleware.
|
|
375
|
+
// `/api` is also added as a route prefix.
|
|
376
|
+
app.group('/api', route => {
|
|
377
|
+
route.get('/test', async c => {
|
|
378
|
+
c.send('api test')
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
route.get('/:name', async c => {
|
|
382
|
+
c.send(c.param)
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
// Add middleware to a specific group
|
|
387
|
+
app.use(
|
|
388
|
+
async (c, next) => {
|
|
389
|
+
console.log(c.method, c.headers)
|
|
390
|
+
await next()
|
|
391
|
+
}, { group: '/sub' }
|
|
392
|
+
).group('/sub', route => {
|
|
393
|
+
route.get('/:id', async c => {
|
|
394
|
+
c.send(c.param.id)
|
|
395
|
+
})
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
// Test group name (not a valid route, so it won’t be a prefix)
|
|
399
|
+
app.group('test', route => {
|
|
400
|
+
route.get('/test', async c => {
|
|
401
|
+
console.log(c.group, c.name)
|
|
402
|
+
c.send('test ok')
|
|
403
|
+
}, 'test')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
app.run(1234)
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
This approach can be complex when specifying multiple middleware. The `middleware` method simplifies this, as shown below.
|
|
410
|
+
|
|
411
|
+
### Assigning Middleware to Groups and Subgroups
|
|
412
|
+
|
|
413
|
+
```javascript
|
|
414
|
+
'use strict'
|
|
415
|
+
|
|
416
|
+
const Titbit = require('titbit')
|
|
417
|
+
const { ToFile } = require('titbit-toolkit')
|
|
418
|
+
|
|
419
|
+
const app = new Titbit({
|
|
420
|
+
debug: true
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Middleware function
|
|
424
|
+
let mid_timing = async (c, next) => {
|
|
425
|
+
console.time('request')
|
|
426
|
+
await next()
|
|
427
|
+
console.timeEnd('request')
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
let sub_mid_test = async (c, next) => {
|
|
431
|
+
console.log('mid test start')
|
|
432
|
+
await next()
|
|
433
|
+
console.log('mid test end')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// The group return value supports `use`, `pre`, and `middleware` for adding middleware.
|
|
437
|
+
// `/api` is added as a route prefix.
|
|
438
|
+
app.middleware([
|
|
439
|
+
// Timing middleware runs before receiving request body data, so `pre` is set to `true`.
|
|
440
|
+
[mid_timing, { pre: true }],
|
|
441
|
+
// ToFile extension runs after receiving request body data, only for POST and PUT.
|
|
442
|
+
[new ToFile(), { method: ['POST', 'PUT'] }]
|
|
443
|
+
]).group('/api', route => {
|
|
444
|
+
route.get('/test', async c => {
|
|
445
|
+
c.send('api test')
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
route.get('/:name', async c => {
|
|
449
|
+
c.send(c.param)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// Subgroup `/sub` enables `sub_mid_test` middleware and inherits parent middleware.
|
|
453
|
+
route.middleware([sub_mid_test]).group('/sub', sub => {
|
|
454
|
+
sub.get('/:key', async c => {
|
|
455
|
+
c.send(c.param)
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
app.run(1234)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Group nesting is supported but should not exceed 9 levels. Nesting beyond 3 levels is often a sign of poor design and should be reconsidered.
|
|
464
|
+
|
|
465
|
+
**This feature is non-intrusive and does not affect existing code or conflict with `titbit-loader`.**
|
|
466
|
+
|
|
467
|
+
**Complex route handlers should be placed in separate modules and loaded using a unified automation function.**
|
|
468
|
+
|
|
469
|
+
Starting with v24.0.9, you can add routes using return values without passing a callback function:
|
|
470
|
+
|
|
471
|
+
```javascript
|
|
472
|
+
'use strict'
|
|
473
|
+
|
|
474
|
+
const Titbit = require('titbit')
|
|
475
|
+
const { ToFile } = require('titbit-toolkit')
|
|
476
|
+
|
|
477
|
+
const app = new Titbit({
|
|
478
|
+
debug: true
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
// Middleware function
|
|
482
|
+
let mid_timing = async (c, next) => {
|
|
483
|
+
console.time('request')
|
|
484
|
+
await next()
|
|
485
|
+
console.timeEnd('request')
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
let sub_mid_test = async (c, next) => {
|
|
489
|
+
console.log('mid test start')
|
|
490
|
+
await next()
|
|
491
|
+
console.log('mid test end')
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let route = app.middleware([
|
|
495
|
+
// Timing middleware runs before receiving request body data, so `pre` is set to `true`.
|
|
496
|
+
[mid_timing, { pre: true }],
|
|
497
|
+
// ToFile extension runs after receiving request body data, only for POST and PUT.
|
|
498
|
+
[new ToFile(), { method: ['POST', 'PUT'] }]
|
|
499
|
+
]).group('/api')
|
|
500
|
+
|
|
501
|
+
route.get('/test', async c => {
|
|
502
|
+
c.send('api test')
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
route.get('/:name', async c => {
|
|
506
|
+
c.send(c.param)
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
// Subgroup `/sub` enables `sub_mid_test` middleware and inherits parent middleware.
|
|
510
|
+
route.middleware([sub_mid_test]).group('/sub', sub => {
|
|
511
|
+
sub.get('/:key', async c => {
|
|
512
|
+
c.send(c.param)
|
|
513
|
+
})
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
app.run(1234)
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## File Uploads
|
|
520
|
+
|
|
521
|
+
File uploads are parsed by default. You can disable this by setting the `parseBody` option during initialization. Parsed file data is stored in `c.files`, with the structure detailed below.
|
|
522
|
+
|
|
523
|
+
```javascript
|
|
524
|
+
'use strict'
|
|
525
|
+
|
|
526
|
+
const titbit = require('titbit')
|
|
527
|
+
|
|
528
|
+
const app = new titbit()
|
|
529
|
+
|
|
530
|
+
app.post('/upload', async c => {
|
|
531
|
+
let f = c.getFile('image')
|
|
532
|
+
|
|
533
|
+
// Helper function to generate a unique filename based on timestamp and file extension.
|
|
534
|
+
let fname = c.ext.makeName(f.filename)
|
|
535
|
+
|
|
536
|
+
try {
|
|
537
|
+
c.send(await c.moveFile(f, fname))
|
|
538
|
+
} catch (err) {
|
|
539
|
+
c.status(500).send(err.message)
|
|
540
|
+
}
|
|
541
|
+
}, 'upload-image') // Names the route `upload-image`, accessible via `c.name`.
|
|
542
|
+
|
|
543
|
+
app.run(1234)
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## `c.files` Data Structure
|
|
547
|
+
|
|
548
|
+
The structure is designed based on the HTTP protocol’s file upload data format. Since HTTP allows multiple files under the same upload name, files are parsed into an array. `c.getFile` returns the first file by default, as most cases involve a single file per upload name.
|
|
549
|
+
|
|
550
|
+
> For front-end developers, the upload name is the `name` attribute in the HTML form: `<input type="file" name="image">`.
|
|
551
|
+
> Do not confuse the upload name with the filename.
|
|
552
|
+
|
|
553
|
+
```javascript
|
|
554
|
+
{
|
|
555
|
+
image: [
|
|
556
|
+
{
|
|
557
|
+
'content-type': CONTENT_TYPE,
|
|
558
|
+
// Available since v23.2.6, alias for content-type
|
|
559
|
+
type: CONTENT_TYPE,
|
|
560
|
+
filename: ORIGIN_FILENAME,
|
|
561
|
+
start: START,
|
|
562
|
+
end: END,
|
|
563
|
+
length: LENGTH,
|
|
564
|
+
rawHeader: HEADER_DATA,
|
|
565
|
+
headers: {...}
|
|
566
|
+
},
|
|
567
|
+
...
|
|
568
|
+
],
|
|
569
|
+
video: [
|
|
570
|
+
{
|
|
571
|
+
'content-type': CONTENT_TYPE,
|
|
572
|
+
// Available since v23.2.6, alias for content-type
|
|
573
|
+
type: CONTENT_TYPE,
|
|
574
|
+
filename: ORIGIN_FILENAME,
|
|
575
|
+
start: START,
|
|
576
|
+
end: END,
|
|
577
|
+
length: LENGTH,
|
|
578
|
+
rawHeader: HEADER_DATA,
|
|
579
|
+
headers: {...}
|
|
580
|
+
},
|
|
581
|
+
...
|
|
582
|
+
]
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`c.getFile(name)` retrieves file information by name, defaulting to index 0. If a negative index is provided, it returns the entire file array; if no files are found, it returns `null`.
|
|
587
|
+
|
|
588
|
+
## Body Size Limit
|
|
589
|
+
|
|
590
|
+
```javascript
|
|
591
|
+
'use strict'
|
|
592
|
+
|
|
593
|
+
const titbit = require('titbit')
|
|
594
|
+
|
|
595
|
+
const app = new titbit({
|
|
596
|
+
// Sets the maximum data size for POST or PUT requests to ~20MB (in bytes).
|
|
597
|
+
maxBody: 20000000
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
app.run(1234)
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## Middleware
|
|
604
|
+
|
|
605
|
+
Middleware is a powerful pattern, with implementations varying slightly across languages but sharing the same essence. Middleware allows developers to organize code effectively and handle complex logic. The framework’s entire operation is based on the middleware pattern.
|
|
606
|
+
|
|
607
|
+
**Middleware Diagram:**
|
|
608
|
+
|
|
609
|
+

|
|
610
|
+
|
|
611
|
+
The framework’s middleware is designed to execute based on route groups and request types, ensuring fast performance. Middleware is executed only when needed, avoiding unnecessary operations. Example:
|
|
612
|
+
|
|
613
|
+
```javascript
|
|
614
|
+
/*
|
|
615
|
+
The second parameter is optional, indicating global middleware.
|
|
616
|
+
Here, it specifies that the middleware only applies to POST requests in the `/api` group.
|
|
617
|
+
This design ensures efficient execution without unnecessary operations.
|
|
618
|
+
*/
|
|
619
|
+
app.add(async (c, next) => {
|
|
620
|
+
console.log('before')
|
|
621
|
+
await next()
|
|
622
|
+
console.log('after')
|
|
623
|
+
}, { method: 'POST', group: '/api' })
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Middleware added with `add` executes in reverse order of addition (standard onion model). To align with developer intuition, the `use` interface adds middleware that executes in the order it was added. Different frameworks handle execution order differently, but sequential execution is more intuitive.
|
|
627
|
+
|
|
628
|
+
**Recommendation: Use `use` to add middleware.**
|
|
629
|
+
|
|
630
|
+
```javascript
|
|
631
|
+
// Executes first
|
|
632
|
+
app.use(async (c, next) => {
|
|
633
|
+
let start_time = Date.now()
|
|
634
|
+
await next()
|
|
635
|
+
let end_time = Date.now()
|
|
636
|
+
console.log(end_time - start_time)
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
// Executes second
|
|
640
|
+
app.use(async (c, next) => {
|
|
641
|
+
console.log(c.method, c.path)
|
|
642
|
+
await next()
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
// `use` supports chaining: app.use(m1).use(m2)
|
|
646
|
+
// Available since v21.5.4, though this is less critical with `titbit-loader`.
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
## titbit Complete Flow Diagram
|
|
650
|
+
|
|
651
|
+

|
|
652
|
+
|
|
653
|
+
> **Note: Internally, body data reception and parsing are also middleware, deliberately ordered and separated into `pre` and `use` interfaces.**
|
|
654
|
+
|
|
655
|
+
## Middleware Parameters
|
|
656
|
+
|
|
657
|
+
The `use` and `pre` interfaces support a second parameter for precise control:
|
|
658
|
+
|
|
659
|
+
- `group`: Specifies the route group for the middleware.
|
|
660
|
+
- `method`: Request method(s) as a string or array (must be uppercase).
|
|
661
|
+
- `name`: Route name, restricting middleware to specific routes.
|
|
662
|
+
|
|
663
|
+
Example:
|
|
664
|
+
|
|
665
|
+
```javascript
|
|
666
|
+
app.get('/xyz', async c => {
|
|
667
|
+
// Route grouped under 'proxy'
|
|
668
|
+
}, { group: 'proxy' })
|
|
669
|
+
|
|
670
|
+
app.use(proxy, {
|
|
671
|
+
method: ['PUT', 'POST', 'GET', 'DELETE', 'OPTIONS'],
|
|
672
|
+
group: 'proxy'
|
|
673
|
+
})
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## `pre` Middleware (Before Body Parsing)
|
|
677
|
+
|
|
678
|
+
The main difference between `pre` and `use` is that `pre` middleware executes before receiving body data, useful for permission filtering. Its parameters are the same as `use`.
|
|
679
|
+
|
|
680
|
+
For a consistent experience, you can use `use` with the `pre` option:
|
|
681
|
+
|
|
682
|
+
```javascript
|
|
683
|
+
let setbodysize = async (c, next) => {
|
|
684
|
+
// Sets max body size to ~10KB.
|
|
685
|
+
c.maxBody = 10000
|
|
686
|
+
await next()
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Equivalent to app.pre(setbodysize)
|
|
690
|
+
app.use(setbodysize, { pre: true })
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
`pre` middleware can handle complex logic and intercept requests without proceeding to the next layer. For example, the `titbit-toolkit` proxy module uses this to implement a high-performance proxy as a middleware.
|
|
694
|
+
|
|
695
|
+
**Dynamic Body Size Limits by Request Type**
|
|
696
|
+
|
|
697
|
+
This can be achieved using `pre` middleware:
|
|
698
|
+
|
|
699
|
+
```javascript
|
|
700
|
+
const app = new titbit({
|
|
701
|
+
// Default max body size ~10MB
|
|
702
|
+
maxBody: 10000000
|
|
703
|
+
})
|
|
704
|
+
|
|
705
|
+
app.pre(async (c, next) => {
|
|
706
|
+
let ctype = c.headers['content-type'] || ''
|
|
707
|
+
|
|
708
|
+
if (ctype.indexOf('text/') === 0) {
|
|
709
|
+
// 50KB
|
|
710
|
+
c.maxBody = 50000
|
|
711
|
+
} else if (ctype.indexOf('application/') === 0) {
|
|
712
|
+
// 100KB
|
|
713
|
+
c.maxBody = 100000
|
|
714
|
+
} else if (ctype.indexOf('multipart/form-data') < 0) {
|
|
715
|
+
// 10KB
|
|
716
|
+
c.maxBody = 10000
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
await next()
|
|
720
|
+
}, { method: ['POST', 'PUT'] })
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
These parameters can make the code complex and hard to maintain, but they are powerful. For automation, use `titbit-loader`, which simplifies routing, model loading, and middleware orchestration: <a target="_blank" href="https://gitee.com/daoio/titbit-loader">titbit-loader</a>.
|
|
724
|
+
|
|
725
|
+
## HTTPS
|
|
726
|
+
|
|
727
|
+
```javascript
|
|
728
|
+
'use strict'
|
|
729
|
+
|
|
730
|
+
const Titbit = require('titbit')
|
|
731
|
+
|
|
732
|
+
// Specify paths to the certificate and key files
|
|
733
|
+
const app = new Titbit({
|
|
734
|
+
cert: './xxx.cert',
|
|
735
|
+
key: './xxx.key'
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
app.run(1234)
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## Supporting HTTP/2 and HTTP/1.1 (Compatibility Mode)
|
|
742
|
+
|
|
743
|
+
Compatibility mode uses the ALPN protocol and requires HTTPS, so certificate and key files must be configured.
|
|
744
|
+
|
|
745
|
+
```javascript
|
|
746
|
+
'use strict'
|
|
747
|
+
|
|
748
|
+
const Titbit = require('titbit')
|
|
749
|
+
|
|
750
|
+
// Specify paths to the certificate and key files
|
|
751
|
+
const app = new Titbit({
|
|
752
|
+
cert: './xxx.cert',
|
|
753
|
+
key: './xxx.key',
|
|
754
|
+
// Enable HTTP/2 and allow HTTP/1.1 compatibility
|
|
755
|
+
http2: true,
|
|
756
|
+
allowHTTP1: true
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
app.run(1234)
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
## Configuration Options
|
|
763
|
+
|
|
764
|
+
Complete configuration options for application initialization, with detailed comments:
|
|
765
|
+
|
|
766
|
+
```javascript
|
|
767
|
+
{
|
|
768
|
+
// Maximum byte size for POST/PUT form submissions and file uploads.
|
|
769
|
+
maxBody: 8000000,
|
|
770
|
+
|
|
771
|
+
// Maximum number of files to parse.
|
|
772
|
+
maxFiles: 12,
|
|
773
|
+
|
|
774
|
+
daemon: false, // Enable daemon mode.
|
|
775
|
+
|
|
776
|
+
// If set to a non-empty string in daemon mode, writes the PID to this file for service management.
|
|
777
|
+
pidFile: '',
|
|
778
|
+
|
|
779
|
+
// Enable global logging to output or save request information.
|
|
780
|
+
globalLog: false,
|
|
781
|
+
|
|
782
|
+
// Log output method: 'stdio' for terminal, 'file' for file.
|
|
783
|
+
logType: 'stdio',
|
|
784
|
+
|
|
785
|
+
// File path for successful request logs (2xx or 3xx status codes).
|
|
786
|
+
logFile: '',
|
|
787
|
+
|
|
788
|
+
// File path for error request logs (4xx or 5xx status codes).
|
|
789
|
+
errorLogFile: '',
|
|
790
|
+
|
|
791
|
+
// Maximum number of log entries per file.
|
|
792
|
+
logMaxLines: 50000,
|
|
793
|
+
|
|
794
|
+
// Maximum number of historical log files.
|
|
795
|
+
logHistory: 50,
|
|
796
|
+
|
|
797
|
+
// Custom log handling function.
|
|
798
|
+
logHandle: null,
|
|
799
|
+
|
|
800
|
+
// Enable HTTPS.
|
|
801
|
+
https: false,
|
|
802
|
+
|
|
803
|
+
http2: false,
|
|
804
|
+
|
|
805
|
+
allowHTTP1: false,
|
|
806
|
+
|
|
807
|
+
// File paths for HTTPS key and certificate. Setting these enables `https: true`.
|
|
808
|
+
key: '',
|
|
809
|
+
cert: '',
|
|
810
|
+
|
|
811
|
+
// Server options passed to `http2.createSecureServer` or `tls.createServer`.
|
|
812
|
+
server: {
|
|
813
|
+
handshakeTimeout: 8192, // TLS handshake timeout
|
|
814
|
+
// sessionTimeout: 350,
|
|
815
|
+
},
|
|
816
|
+
|
|
817
|
+
// Server timeout (milliseconds). Can be overridden per request.
|
|
818
|
+
timeout: 15000,
|
|
819
|
+
|
|
820
|
+
debug: false,
|
|
821
|
+
|
|
822
|
+
// Ignore trailing slashes in paths.
|
|
823
|
+
ignoreSlash: true,
|
|
824
|
+
|
|
825
|
+
// Enable request limiting.
|
|
826
|
+
useLimit: false,
|
|
827
|
+
|
|
828
|
+
// Maximum connections (0 for unlimited).
|
|
829
|
+
maxConn: 1024,
|
|
830
|
+
|
|
831
|
+
// Maximum requests per IP within a time period (0 for unlimited).
|
|
832
|
+
maxIPRequest: 0,
|
|
833
|
+
|
|
834
|
+
// Time period for request limiting (default: 60 second).
|
|
835
|
+
unitTime: 60,
|
|
836
|
+
|
|
837
|
+
// Display load information (requires `daemon` mode).
|
|
838
|
+
loadMonitor: true,
|
|
839
|
+
|
|
840
|
+
// Load information type: 'text', 'json', or '--null'. JSON is for programmatic use.
|
|
841
|
+
loadInfoType: 'text',
|
|
842
|
+
|
|
843
|
+
// File path for load information (if unset, outputs to terminal).
|
|
844
|
+
loadInfoFile: '',
|
|
845
|
+
|
|
846
|
+
// Data for 404 responses.
|
|
847
|
+
notFound: 'Not Found',
|
|
848
|
+
|
|
849
|
+
// Data for 400 responses.
|
|
850
|
+
badRequest: 'Bad Request',
|
|
851
|
+
|
|
852
|
+
// Memory usage percentage factor for subprocesses (-0.42 to 0.36; base is 0.52, default is 80%).
|
|
853
|
+
memFactor: 0.28,
|
|
854
|
+
|
|
855
|
+
// Maximum URL length.
|
|
856
|
+
maxUrlLength: 2048,
|
|
857
|
+
|
|
858
|
+
// Maximum request context cache pool size.
|
|
859
|
+
maxpool: 4096,
|
|
860
|
+
|
|
861
|
+
// Interval (milliseconds) for subprocess resource reporting.
|
|
862
|
+
monitorTimeSlice: 640,
|
|
863
|
+
|
|
864
|
+
// Log real IP addresses in global logs (useful in reverse proxy mode).
|
|
865
|
+
realIP: false,
|
|
866
|
+
|
|
867
|
+
// Maximum number of query string parameters.
|
|
868
|
+
maxQuery: 25,
|
|
869
|
+
|
|
870
|
+
// Enable strong mode to handle `rejectionHandled` and `uncaughtException` events,
|
|
871
|
+
// capturing errors like TypeError, ReferenceError, etc.
|
|
872
|
+
strong: false,
|
|
873
|
+
|
|
874
|
+
// Fast query string parsing (uses only the first value for duplicate keys, not an array).
|
|
875
|
+
fastParseQuery: false,
|
|
876
|
+
|
|
877
|
+
// Automatically decode query parameters using `decodeURIComponent`.
|
|
878
|
+
autoDecodeQuery: true,
|
|
879
|
+
|
|
880
|
+
// Maximum length for a single form item in multipart format.
|
|
881
|
+
maxFormLength: 1000000,
|
|
882
|
+
|
|
883
|
+
// Error handling function for runtime errors (e.g., tlsClientError, server errors).
|
|
884
|
+
// `errname` is a string like `--ERR-CONNECTION--` or `--ERR-CLIENT--`.
|
|
885
|
+
errorHandle: (err, errname) => {
|
|
886
|
+
this.config.debug && console.error(errname, err)
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
// Maximum CPU load percentage (default: 75%). Effective only with `autoWorker`.
|
|
890
|
+
maxLoadRate: 75,
|
|
891
|
+
|
|
892
|
+
// HTTP/2 stream timeout (-1 to match `timeout`).
|
|
893
|
+
streamTimeout: -1,
|
|
894
|
+
|
|
895
|
+
// Total request timeout to counter malicious requests (e.g., DDoS attacks).
|
|
896
|
+
requestTimeout: 100000
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
## Request Context
|
|
901
|
+
|
|
902
|
+
The request context is an object encapsulating various request data, abstracting differences between HTTP/1.1 and HTTP/2 and handling Node.js version incompatibilities. For HTTP/2, the request object is a `stream`, not `IncomingMessage` and `ServerResponse` as in HTTP/1.1.
|
|
903
|
+
|
|
904
|
+
**Request Context Properties and Descriptions**
|
|
905
|
+
|
|
906
|
+
| Property | Description |
|
|
907
|
+
|----------|-------------|
|
|
908
|
+
| version | Protocol version (`'1.1'` or `'2'`). |
|
|
909
|
+
| major | Major protocol version (1, 2, or 3 for HTTP/1.1, HTTP/2, HTTP/3; 3 not yet supported). |
|
|
910
|
+
| maxBody | Maximum request body size (bytes), defaults to `maxBody` from initialization, adjustable in middleware. |
|
|
911
|
+
| method | Request method (e.g., `GET`, `POST`), uppercase string. |
|
|
912
|
+
| host | Hostname from `request.headers.host`. |
|
|
913
|
+
| protocol | Protocol string (`http` or `https`, no colon). |
|
|
914
|
+
| path | Requested path. |
|
|
915
|
+
| routepath | Actual route string executed. |
|
|
916
|
+
| query | URL query parameters. |
|
|
917
|
+
| param | Route parameters. |
|
|
918
|
+
| files | Uploaded file information. |
|
|
919
|
+
| body | Request body data (string, object, or Buffer, depending on `content-type`). |
|
|
920
|
+
| port | Client request port. |
|
|
921
|
+
| ip | Client IP address (socket address; check `x-real-ip` or `x-forwarded-for` for proxies). |
|
|
922
|
+
| headers | Reference to `request.headers`. |
|
|
923
|
+
| isUpload() | Checks if the request is a file upload (`multipart/form-data`). |
|
|
924
|
+
| name | Route name (default: empty string). |
|
|
925
|
+
| group | Route group (default: empty string). |
|
|
926
|
+
| reply | HTTP/1.1: `response`; HTTP/2: `stream`. |
|
|
927
|
+
| request | HTTP/1.1: `IncomingMessage`; HTTP/2: `stream`. |
|
|
928
|
+
| box | Empty object for dynamically passing data to subsequent layers. |
|
|
929
|
+
| service | Dependency injection object, points to `app.service`. |
|
|
930
|
+
| data | Final response data (set directly or via `ctx.send`). Before v24.x, was `ctx.res.body`. |
|
|
931
|
+
| ext | Helper functions (see Wiki). |
|
|
932
|
+
| send(data) | Sets `ctx.data`. |
|
|
933
|
+
| write(data) | Writes data directly to the client. |
|
|
934
|
+
| moveFile(file, target_filepath) | Moves an uploaded file to the specified path. |
|
|
935
|
+
| status() | Sets the status code. |
|
|
936
|
+
| setHeader(k, v) | Sets a response header. |
|
|
937
|
+
| removeHeader(k) | Removes a pending response header. |
|
|
938
|
+
| getFile(name) | Retrieves uploaded file information from `files`. |
|
|
939
|
+
| sendHeader() | Sends headers (HTTP/2 only; no-op for HTTP/1.1). |
|
|
940
|
+
| user | Standard property for user login (default: `null`). |
|
|
941
|
+
| json(data) | Sets response data with `content-type: application/json`. |
|
|
942
|
+
| text(data) | Sets response data with `content-type: text/plain`. |
|
|
943
|
+
| html(data) | Sets response data with `content-type: text/html`. |
|
|
944
|
+
| pipe(filepath) | Streams file data (e.g., `await ctx.setHeader('content-type', 'text/html').pipe('./index.html')`). |
|
|
945
|
+
| pipeJson(filepath) | Streams file data as JSON. |
|
|
946
|
+
| pipeText(filepath) | Streams file data as text. |
|
|
947
|
+
| pipeHtml(filepath) | Streams file data as HTML. |
|
|
948
|
+
|
|
949
|
+
**Note:** The `send` function only sets `ctx.data`. It’s equivalent to direct assignment but helps catch errors faster, as incorrect property assignments create new properties without errors, leading to incorrect responses.
|
|
950
|
+
|
|
951
|
+
## Dependency Injection
|
|
952
|
+
|
|
953
|
+
The request context includes a `service` property pointing to `app.service`. After initializing the `app`, you can attach pre-initialized data or instances to `app.service`.
|
|
954
|
+
|
|
955
|
+
```javascript
|
|
956
|
+
'use strict'
|
|
957
|
+
|
|
958
|
+
const titbit = require('titbit')
|
|
959
|
+
|
|
960
|
+
const app = new titbit({
|
|
961
|
+
debug: true
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
// Overwrites if exists, adds if not.
|
|
965
|
+
app.addService('name', 'first')
|
|
966
|
+
app.addService('data', {
|
|
967
|
+
id: 123,
|
|
968
|
+
ip: '127.0.0.1'
|
|
969
|
+
})
|
|
970
|
+
|
|
971
|
+
app.get('/info', async c => {
|
|
972
|
+
c.send({
|
|
973
|
+
name: c.service.name,
|
|
974
|
+
data: c.service.data
|
|
975
|
+
})
|
|
976
|
+
})
|
|
977
|
+
|
|
978
|
+
app.run(1234)
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
## Extending the Request Context
|
|
982
|
+
|
|
983
|
+
To extend the request context, use `app.httpServ.context`. This is the constructor for the request context.
|
|
984
|
+
|
|
985
|
+
**Example:**
|
|
986
|
+
|
|
987
|
+
```javascript
|
|
988
|
+
'use strict'
|
|
989
|
+
|
|
990
|
+
const titbit = require('titbit')
|
|
991
|
+
|
|
992
|
+
const app = new titbit({
|
|
993
|
+
debug: true
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
// `this` refers to the request context
|
|
997
|
+
app.httpServ.context.prototype.testCtx = function () {
|
|
998
|
+
console.log(this.method, this.path)
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
app.get('/test', async ctx => {
|
|
1002
|
+
ctx.testCtx()
|
|
1003
|
+
})
|
|
1004
|
+
|
|
1005
|
+
app.run(1234)
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
## `app.isMaster` and `app.isWorker`
|
|
1009
|
+
|
|
1010
|
+
Since Node.js v16.x, the `cluster` module recommends `isPrimary` over `isMaster`, though `isMaster` remains available. After initializing the `app`, `app.isMaster` and `app.isWorker` getter properties are provided, mirroring `cluster` properties to:
|
|
1011
|
+
|
|
1012
|
+
- Avoid requiring `const cluster = require('cluster')`.
|
|
1013
|
+
- Shield against future `cluster` incompatibilities.
|
|
1014
|
+
|
|
1015
|
+
## `daemon` and `run`
|
|
1016
|
+
|
|
1017
|
+
The `run` interface accepts `port` and `host` (default: `0.0.0.0`). It also supports a `sockPath` (e.g., `.sock` file), consistent with the HTTP `listen` interface, in which case `host` is ignored.
|
|
1018
|
+
|
|
1019
|
+
The `daemon` interface shares the same first two parameters but supports a third parameter specifying the number of subprocesses. If set to 0, it defaults to the number of CPU cores. It maintains subprocess stability by creating new ones if any terminate unexpectedly.
|
|
1020
|
+
|
|
1021
|
+
**In cluster mode, the maximum number of subprocesses is twice the CPU core count.**
|
|
1022
|
+
|
|
1023
|
+
**Examples:**
|
|
1024
|
+
|
|
1025
|
+
```javascript
|
|
1026
|
+
// Default host: 0.0.0.0, port: 1234
|
|
1027
|
+
app.run(1234)
|
|
1028
|
+
|
|
1029
|
+
// Listen on localhost (local access only)
|
|
1030
|
+
app.run(1234, 'localhost')
|
|
1031
|
+
|
|
1032
|
+
// Use 2 subprocesses, default host: 0.0.0.0
|
|
1033
|
+
app.daemon(1234, 2)
|
|
1034
|
+
|
|
1035
|
+
// Use 3 subprocesses
|
|
1036
|
+
app.daemon(1234, 'localhost', 3)
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
## Logging
|
|
1040
|
+
|
|
1041
|
+
The framework provides global logging when using `daemon` mode (cluster). Enable it with the `globalLog` option, which supports file output or terminal output (in single-process `run` mode, logs go to the terminal but can be redirected to files).
|
|
1042
|
+
|
|
1043
|
+
**Note: Only `daemon` mode supports saving logs to files. In `run` mode, logs are output to the terminal but can be redirected.**
|
|
1044
|
+
|
|
1045
|
+
You can use the `logHandle` option to define a custom logging function, which overrides `logFile` and `errorLogFile`.
|
|
1046
|
+
|
|
1047
|
+
**Example:**
|
|
1048
|
+
|
|
1049
|
+
```javascript
|
|
1050
|
+
const titbit = require('titbit')
|
|
1051
|
+
|
|
1052
|
+
const app = new titbit({
|
|
1053
|
+
debug: true,
|
|
1054
|
+
globalLog: true,
|
|
1055
|
+
logType: 'file', // 'file' for file output, 'stdio' for terminal
|
|
1056
|
+
logFile: '/tmp/titbit.log', // Successful requests (2xx, 3xx)
|
|
1057
|
+
errorLogFile: '/tmp/titbit-error.log', // Error requests (4xx, 5xx)
|
|
1058
|
+
logHandle: (w, msg) => {
|
|
1059
|
+
// Custom log handler; overrides logFile and errorLogFile
|
|
1060
|
+
// `msg` format: { type: '_log', success: true, log: '@ GET | https://localhost:2021/randst | 200 | 2020-10-31 20:27:7 | 127.0.0.1 | User-Agent' }
|
|
1061
|
+
console.log(w.id, msg)
|
|
1062
|
+
}
|
|
1063
|
+
})
|
|
1064
|
+
|
|
1065
|
+
app.daemon(1234, 3)
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Middleware-based logging does not conflict with global logging but cannot capture 404 errors (no route found), as the framework returns early without creating a request context.
|
|
1069
|
+
|
|
1070
|
+
## Message Event Handling
|
|
1071
|
+
|
|
1072
|
+
In `daemon` mode (using `cluster`), the `setMsgEvent` function handles messages sent by subprocesses. Messages must be objects with a required `type` property indicating the event name.
|
|
1073
|
+
|
|
1074
|
+
**Example:**
|
|
1075
|
+
|
|
1076
|
+
```javascript
|
|
1077
|
+
const titbit = require('titbit')
|
|
1078
|
+
const cluster = require('cluster')
|
|
1079
|
+
|
|
1080
|
+
const app = new titbit({
|
|
1081
|
+
debug: true,
|
|
1082
|
+
loadInfoFile: '/tmp/loadinfo.log'
|
|
1083
|
+
})
|
|
1084
|
+
|
|
1085
|
+
if (cluster.isMaster) {
|
|
1086
|
+
app.setMsgEvent('test-msg', (worker, msg, handle) => {
|
|
1087
|
+
worker.send({
|
|
1088
|
+
id: worker.id,
|
|
1089
|
+
data: 'ok'
|
|
1090
|
+
})
|
|
1091
|
+
console.log(msg)
|
|
1092
|
+
})
|
|
1093
|
+
} else {
|
|
1094
|
+
process.on('message', msg => {
|
|
1095
|
+
console.log(msg)
|
|
1096
|
+
})
|
|
1097
|
+
|
|
1098
|
+
setInterval(() => {
|
|
1099
|
+
process.send({
|
|
1100
|
+
type: 'test-msg',
|
|
1101
|
+
pid: process.pid,
|
|
1102
|
+
time: new Date().toLocaleString()
|
|
1103
|
+
})
|
|
1104
|
+
}, 1000)
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
Since v22.4.0, the `app.send` method simplifies sending messages from workers to the master process.
|
|
1109
|
+
|
|
1110
|
+
## `app.send` and `app.workerMsg`
|
|
1111
|
+
|
|
1112
|
+
Rewriting the above example using `app.send` and `app.workerMsg`:
|
|
1113
|
+
|
|
1114
|
+
```javascript
|
|
1115
|
+
const titbit = require('titbit')
|
|
1116
|
+
|
|
1117
|
+
const app = new titbit({
|
|
1118
|
+
debug: true,
|
|
1119
|
+
loadInfoFile: '/tmp/loadinfo.log'
|
|
1120
|
+
})
|
|
1121
|
+
|
|
1122
|
+
app.setMsgEvent('test-msg', (worker, msg, handle) => {
|
|
1123
|
+
worker.send({
|
|
1124
|
+
id: worker.id,
|
|
1125
|
+
data: 'ok'
|
|
1126
|
+
})
|
|
1127
|
+
console.log(msg)
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
app.workerMsg(msg => {
|
|
1131
|
+
console.log(msg)
|
|
1132
|
+
})
|
|
1133
|
+
|
|
1134
|
+
cluster.isWorker &&
|
|
1135
|
+
setInterval(() => {
|
|
1136
|
+
app.send('test-msg', {
|
|
1137
|
+
pid: process.pid,
|
|
1138
|
+
time: new Date().toLocaleString()
|
|
1139
|
+
})
|
|
1140
|
+
}, 1000)
|
|
1141
|
+
|
|
1142
|
+
app.daemon(1234, 2)
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
## Automatic Subprocess Adjustment
|
|
1146
|
+
|
|
1147
|
+
The `daemon` interface sets the base number of subprocesses:
|
|
1148
|
+
|
|
1149
|
+
```javascript
|
|
1150
|
+
// Use 2 subprocesses
|
|
1151
|
+
app.daemon(1234, 2)
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
To automatically adjust subprocesses based on load, use `autoWorker` to set a maximum number of subprocesses (must be greater than the base number):
|
|
1155
|
+
|
|
1156
|
+
```javascript
|
|
1157
|
+
// Maximum 9 subprocesses
|
|
1158
|
+
app.autoWorker(9)
|
|
1159
|
+
|
|
1160
|
+
app.daemon(1234, 2)
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
When load is high, new subprocesses are created. When idle, subprocesses with zero connections are terminated to revert to the base number.
|
|
1164
|
+
|
|
1165
|
+
**Available since v21.9.6. Use the latest version for improved stability and performance.**
|
|
1166
|
+
|
|
1167
|
+
## Strong Mode
|
|
1168
|
+
|
|
1169
|
+
Enable `strong` mode to handle `uncaughtException` and `unhandledRejection` events, ensuring program stability. Simply set `strong: true`.
|
|
1170
|
+
|
|
1171
|
+
**All `strong` mode features can be implemented manually using the `process` module; this just simplifies the process.**
|
|
1172
|
+
|
|
1173
|
+
```javascript
|
|
1174
|
+
'use strict'
|
|
1175
|
+
|
|
1176
|
+
const titbit = require('titbit')
|
|
1177
|
+
|
|
1178
|
+
setTimeout(() => {
|
|
1179
|
+
throw new Error('test error')
|
|
1180
|
+
}, 2000)
|
|
1181
|
+
|
|
1182
|
+
const app = new titbit({
|
|
1183
|
+
debug: true,
|
|
1184
|
+
strong: true
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
app.run(1234)
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
By default, `strong` mode catches:
|
|
1191
|
+
|
|
1192
|
+
```
|
|
1193
|
+
TypeError, ReferenceError, RangeError, AssertionError, URIError, Error
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
Customize handling with an object:
|
|
1197
|
+
|
|
1198
|
+
```javascript
|
|
1199
|
+
const app = new titbit({
|
|
1200
|
+
debug: true,
|
|
1201
|
+
strong: {
|
|
1202
|
+
quiet: true, // Suppress error output
|
|
1203
|
+
errorHandle: (err, errname) => {
|
|
1204
|
+
// Custom error handling
|
|
1205
|
+
},
|
|
1206
|
+
catchErrors: ['TypeError', 'URIError', 'Error', 'RangeError']
|
|
1207
|
+
}
|
|
1208
|
+
})
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
## Running HTTP and HTTPS Simultaneously?
|
|
1212
|
+
|
|
1213
|
+
**This is not recommended in production.** If HTTPS is enabled, HTTP is unnecessary, and some front-end features require HTTPS.
|
|
1214
|
+
|
|
1215
|
+
For testing, you can do:
|
|
1216
|
+
|
|
1217
|
+
```javascript
|
|
1218
|
+
'use strict'
|
|
1219
|
+
|
|
1220
|
+
const Titbit = require('titbit')
|
|
1221
|
+
const http = require('node:http')
|
|
1222
|
+
const https = require('node:https')
|
|
1223
|
+
|
|
1224
|
+
const app = new Titbit({
|
|
1225
|
+
debug: true
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
let http_server = http.createServer(app.httpServ.onRequest())
|
|
1229
|
+
let https_server = https.createServer(app.httpServ.onRequest())
|
|
1230
|
+
|
|
1231
|
+
http_server.listen(2025)
|
|
1232
|
+
https_server.listen(2026)
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
**Note: This setup does not support HTTP/2. Use HTTP/2 with `allowHTTP1` for compatibility.**
|
|
1236
|
+
|
|
1237
|
+
## Miscellaneous
|
|
1238
|
+
|
|
1239
|
+
- A final middleware handles responses, automatically setting `content-type` (e.g., `text/plain`, `text/html`, `application/json`) if not set.
|
|
1240
|
+
- Default limits on URL length and memory usage are based on hardware.
|
|
1241
|
+
- Configurations and middleware allow for extension and overrides.
|
|
1242
|
+
- The framework is optimized for speed. For performance comparisons, test with multiple middleware and hundreds of routes.
|
|
1243
|
+
- The `sched` function sets cluster scheduling policy (`'rr'` or `'none'`), equivalent to `cluster.schedulingPolicy`.
|
|
1244
|
+
|
|
1245
|
+
The framework auto-detects memory size and sets limits, adjustable via the `secure` object in `daemon` mode:
|
|
1246
|
+
|
|
1247
|
+
```javascript
|
|
1248
|
+
'use strict'
|
|
1249
|
+
|
|
1250
|
+
const Titbit = require('titbit')
|
|
1251
|
+
|
|
1252
|
+
let app = new Titbit()
|
|
1253
|
+
|
|
1254
|
+
// Max memory 600MB, restarts only when connections are 0.
|
|
1255
|
+
app.secure.maxmem = 600_000_000
|
|
1256
|
+
|
|
1257
|
+
// Hard limit 900MB; restarts if exceeded, even with active connections.
|
|
1258
|
+
app.secure.diemem = 900_000_000
|
|
1259
|
+
|
|
1260
|
+
// Max RSS memory 800MB (excludes Buffer allocations).
|
|
1261
|
+
app.secure.maxrss = 800_000_000
|
|
1262
|
+
|
|
1263
|
+
app.get('/', async c => {
|
|
1264
|
+
c.send('ok')
|
|
1265
|
+
})
|
|
1266
|
+
|
|
1267
|
+
app.daemon(8008, 2)
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
**Requires `loadMonitor: true` (default unless set to `false`).**
|
|
1271
|
+
|
|
1272
|
+
Use default configurations unless specific control is needed.
|