birdpack 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -0
- package/lib/core.js +58 -32
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# 🐦 birdpack
|
|
2
|
+
|
|
3
|
+
**birdpack** คือ Web Framework สำหรับ Node.js ที่ถูกออกแบบมาให้ **เบา, เร็ว, ใช้ง่าย, รองรับ SSL หลายโดเมน** พร้อมระบบ Routing และ Static File Server
|
|
4
|
+
|
|
5
|
+
- - -
|
|
6
|
+
|
|
7
|
+
## 📦 การติดตั้ง
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
npm install birdpack
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- - -
|
|
14
|
+
|
|
15
|
+
## 🚀 เริ่มต้นใช้งาน (Quick Start)
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const birdpack = require('birdpack');
|
|
21
|
+
|
|
22
|
+
const app = new birdpack({
|
|
23
|
+
ssl: [
|
|
24
|
+
{
|
|
25
|
+
key: fs.readFileSync(__dirname + "/backend/ssl/test.dev.key"),
|
|
26
|
+
cert: fs.readFileSync(__dirname + "/backend/ssl/test.dev.crt"),
|
|
27
|
+
domain: ['test.dev'],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
traffic: '$time [$method] $path $ip',
|
|
31
|
+
log: 'console',
|
|
32
|
+
logFile: `${__dirname}/server.log`,
|
|
33
|
+
port: 443,
|
|
34
|
+
use: 'https',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Routing พื้นฐาน
|
|
38
|
+
app.get('/', (req) => req.send('hi'))
|
|
39
|
+
.post('/json', (req) => req.code(400).json({ err: 400 }))
|
|
40
|
+
.all('/allpath', (req) => req.send('test'))
|
|
41
|
+
.directory('/public'); // Static files
|
|
42
|
+
|
|
43
|
+
// Routing เฉพาะ domain
|
|
44
|
+
let test = app.domain('test.dev');
|
|
45
|
+
test.next((req, next) => {
|
|
46
|
+
if (req.look('token')) next({ rank: 'admin' });
|
|
47
|
+
else next({ rank: 'user' });
|
|
48
|
+
})
|
|
49
|
+
.get('/user/:id', (req, data) => {
|
|
50
|
+
req.json({ userId: req.params.id, role: data.rank });
|
|
51
|
+
})
|
|
52
|
+
.get('/home', (req) => req.file('index.html'))
|
|
53
|
+
.routes(__dirname + '/backend/routes');
|
|
54
|
+
|
|
55
|
+
// เริ่ม server
|
|
56
|
+
app.listen();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- - -
|
|
60
|
+
|
|
61
|
+
## 📂 โครงสร้างโปรเจกต์แนะนำ
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
my-app/
|
|
66
|
+
├── backend/
|
|
67
|
+
│ ├── routes/
|
|
68
|
+
│ │ └── index.js
|
|
69
|
+
│ └── ssl/
|
|
70
|
+
│ ├── test.dev.crt
|
|
71
|
+
│ └── test.dev.key
|
|
72
|
+
├── public/
|
|
73
|
+
├── server.log
|
|
74
|
+
└── index.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- - -
|
|
78
|
+
|
|
79
|
+
## ⚙️ การตั้งค่า (Config Options)
|
|
80
|
+
|
|
81
|
+
| Key | Type | Default | Description |
|
|
82
|
+
| --- | --- | --- | --- |
|
|
83
|
+
| ssl | Array | \[\] | SSL Certificates `{ key, cert, domain[] }` |
|
|
84
|
+
| traffic | string | false | รูปแบบ log เช่น `$time $method $path $ip` |
|
|
85
|
+
| log | string | 'console' | 'console' \| 'file' \| false |
|
|
86
|
+
| logFile | string | \- | path log file (ใช้เมื่อ `log:'file'`) |
|
|
87
|
+
| port | number | 80 | Port ที่ server listen |
|
|
88
|
+
| use | string | 'http' | 'http' หรือ 'https' |
|
|
89
|
+
|
|
90
|
+
- - -
|
|
91
|
+
|
|
92
|
+
## 🔀 Routing Methods
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
|
|
96
|
+
app.get('/hello', req => req.send('Hello World'));
|
|
97
|
+
app.post('/data', req => req.json({ ok: 1 }));
|
|
98
|
+
app.all('/all', req => req.send('Match any method'));
|
|
99
|
+
app.directory('/public');
|
|
100
|
+
app.routes(__dirname + '/backend/routes');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Method | Description |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| .get(path, handler) | รับเฉพาะ GET |
|
|
106
|
+
| .post(path, handler) | รับเฉพาะ POST |
|
|
107
|
+
| .all(path, handler) | รองรับทุก Method |
|
|
108
|
+
| .directory(folder) | เสิร์ฟ static files |
|
|
109
|
+
| .routes(path) | โหลด routes จากไฟล์/โฟลเดอร์ |
|
|
110
|
+
| .domain(domain) | แยก routing ตามโดเมน |
|
|
111
|
+
|
|
112
|
+
- - -
|
|
113
|
+
|
|
114
|
+
## 🧩 Request Object (req)
|
|
115
|
+
|
|
116
|
+
### 📤 ส่ง Response
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
req.send("text");
|
|
121
|
+
req.json({ hello: "world" });
|
|
122
|
+
req.html("<h1>Hi</h1>");
|
|
123
|
+
req.code(201).send("Created");
|
|
124
|
+
req.file("index.html");
|
|
125
|
+
req.redirect("https://google.com");
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 📌 Headers
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
req.set("Content-Type", "text/plain");
|
|
133
|
+
let ua = req.get("user-agent");
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 🔍 Query Parameters (?id=1&user=2)
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
req.u("id"); // "1"
|
|
141
|
+
req.look("id,user"); // ตรวจว่ามี id และ user
|
|
142
|
+
req.query; // {id:"1", user:"2"}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 📥 Payload
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
req.q("username");
|
|
150
|
+
req.check("id,user");
|
|
151
|
+
req.body; // payload object
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 🍪 Cookies
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
req.scookie("token", "abc123", { httpOnly: true });
|
|
159
|
+
req.rcookie("token");
|
|
160
|
+
console.log(req.cookie);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### ⚙️ Response Config
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
req.length(1024);
|
|
168
|
+
req.type("application/json");
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### 📌 Properties
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
req.ip; // IP client
|
|
176
|
+
req.host; // Host
|
|
177
|
+
req.method; // GET/POST
|
|
178
|
+
req.path; // Path
|
|
179
|
+
req.params; // เช่น /user/:id => {id:"123"}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- - -
|
|
183
|
+
|
|
184
|
+
## 🧭 Middleware + Domain Routing
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
let dashboard = app.domain('dashboard.dev');
|
|
189
|
+
|
|
190
|
+
dashboard.next((req, next) => {
|
|
191
|
+
if (!req.look('token'))
|
|
192
|
+
return req.code(401).send('Unauthorized');
|
|
193
|
+
next({ user: 'admin' });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
dashboard.get('/stats', (req, data) => {
|
|
197
|
+
req.json({ status: 'ok', user: data.user });
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- - -
|
|
202
|
+
|
|
203
|
+
## 📂 การใช้งาน Routes แบบอัตโนมัติ
|
|
204
|
+
|
|
205
|
+
คุณสามารถใช้ `app.routes(folderPath)` เพื่อให้ **birdpack** โหลดเส้นทาง (routes) จากไฟล์ในโฟลเดอร์ที่กำหนด โดย **ชื่อไฟล์** จะถูกแปลงเป็น Method และ Path อัตโนมัติ
|
|
206
|
+
|
|
207
|
+
### 📌 กฎการตั้งชื่อไฟล์
|
|
208
|
+
|
|
209
|
+
| รูปแบบชื่อไฟล์ | HTTP Method | Path ที่ได้ |
|
|
210
|
+
| --- | --- | --- |
|
|
211
|
+
| `get@user-id-$id.js` | GET | `/user/id/:id` |
|
|
212
|
+
| `post@user-*.js` | POST | `/user/*` |
|
|
213
|
+
| `all@health.js` | ALL | `/health` |
|
|
214
|
+
|
|
215
|
+
**💡 รูปแบบ:** `{method}@{path}.js`
|
|
216
|
+
ใช้ `$param` เพื่อกำหนด dynamic params เช่น `$id`
|
|
217
|
+
ใช้ `*` เพื่อ match ทุก segment ที่เหลือ
|
|
218
|
+
|
|
219
|
+
### 📂 โครงสร้างตัวอย่าง
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
backend/
|
|
224
|
+
└── routes/
|
|
225
|
+
├── get@user-id-$id.js
|
|
226
|
+
├── post@user-*.js
|
|
227
|
+
└── all@health.js
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 📌 ตัวอย่างไฟล์ route
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
|
|
234
|
+
// backend/routes/get@user-id-$id.js
|
|
235
|
+
module.exports = (req) => {
|
|
236
|
+
req.json({ userId: req.params.id });
|
|
237
|
+
};
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
|
|
242
|
+
// backend/routes/post@user-*.js
|
|
243
|
+
module.exports = (req) => {
|
|
244
|
+
req.html('<h1>Post request matched!</h1>');
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
|
|
250
|
+
// backend/routes/all@health.js
|
|
251
|
+
module.exports = (req) => {
|
|
252
|
+
req.send('OK');
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 📌 การใช้งานใน main server
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
|
|
260
|
+
const app = new birdpack({...});
|
|
261
|
+
app.routes(__dirname + '/backend/routes');
|
|
262
|
+
app.listen();
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- - -
|
|
266
|
+
|
|
267
|
+
## 📝 Logging Variables
|
|
268
|
+
|
|
269
|
+
| Placeholder | Description |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| $time | เวลา |
|
|
272
|
+
| $method | HTTP Method |
|
|
273
|
+
| $path | Path ของ request |
|
|
274
|
+
| $ip | IP ของ client |
|
|
275
|
+
| $status | HTTP Status |
|
|
276
|
+
| $body | Payload |
|
|
277
|
+
| $head(key) | Header value เช่น $head(user-agent) |
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
traffic: '$time - $ip - $method $path [$status] UA:$head(user-agent)'
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
- - -
|
|
285
|
+
|
|
286
|
+
## 📌 License
|
|
287
|
+
|
|
288
|
+
R938 Service
|
package/lib/core.js
CHANGED
|
@@ -387,46 +387,72 @@ module.exports = class{
|
|
|
387
387
|
this.end();
|
|
388
388
|
}
|
|
389
389
|
}else if(fileRange == 'file'){
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
if(options.parameter){
|
|
391
|
+
let data = fs.readFileSync(options.file, { encoding: 'utf8', flag: 'r' });
|
|
392
|
+
|
|
393
|
+
data = data.replace(/\{\{[a-zA-Z0-9\_]*\}\}/g, (a)=>{
|
|
394
|
+
let key = a.slice(2,-2);
|
|
395
|
+
if(key != ''){
|
|
396
|
+
if(options.parameter.hasOwnProperty(key)){
|
|
397
|
+
return options.parameter[key]
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return '';
|
|
401
|
+
});
|
|
394
402
|
|
|
395
|
-
|
|
396
|
-
const fileDescriptor = fs.openSync(options.file, "r");
|
|
403
|
+
data = Buffer.from(data, "utf8");
|
|
397
404
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
fs.closeSync(fileDescriptor);
|
|
401
|
-
return;
|
|
405
|
+
if(options.download === true){
|
|
406
|
+
this.set('content-disposition', `attachment; filename="${options.filename}"`);
|
|
402
407
|
}
|
|
403
408
|
|
|
404
|
-
|
|
405
|
-
|
|
409
|
+
this.code(200)
|
|
410
|
+
.length(data.length)
|
|
411
|
+
.type(options.type);
|
|
412
|
+
|
|
413
|
+
this.send(data);
|
|
414
|
+
}else{
|
|
415
|
+
const totalSize = fileStat.size;
|
|
406
416
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (err) {
|
|
410
|
-
fs.closeSync(fileDescriptor);
|
|
411
|
-
} else {
|
|
412
|
-
sendChunk(position + bytesRead);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
}else{
|
|
416
|
-
fs.closeSync(fileDescriptor);
|
|
417
|
-
}
|
|
418
|
-
};
|
|
417
|
+
const start = 0;
|
|
418
|
+
const end = totalSize - 1;
|
|
419
419
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
420
|
+
const chunkSize = end - start + 1;
|
|
421
|
+
const fileDescriptor = fs.openSync(options.file, "r");
|
|
422
|
+
|
|
423
|
+
const sendChunk = (position) => {
|
|
424
|
+
if(position > end){
|
|
425
|
+
fs.closeSync(fileDescriptor);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
423
428
|
|
|
424
|
-
|
|
425
|
-
|
|
429
|
+
const bytesToRead = Math.min(bufferSize, end - position + 1);
|
|
430
|
+
const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
|
|
431
|
+
|
|
432
|
+
if(bytesRead > 0 && this.res.writable){
|
|
433
|
+
this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
|
|
434
|
+
if (err) {
|
|
435
|
+
fs.closeSync(fileDescriptor);
|
|
436
|
+
} else {
|
|
437
|
+
sendChunk(position + bytesRead);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}else{
|
|
441
|
+
fs.closeSync(fileDescriptor);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
this.code(200)
|
|
446
|
+
.length(chunkSize)
|
|
447
|
+
.type(options.type);
|
|
448
|
+
|
|
449
|
+
if(options.download === true){
|
|
450
|
+
this.set('content-disposition', `attachment; filename="${options.filename}"`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
this.writeHead();
|
|
454
|
+
sendChunk(start);
|
|
426
455
|
}
|
|
427
|
-
|
|
428
|
-
this.writeHead();
|
|
429
|
-
sendChunk(start);
|
|
430
456
|
}else{
|
|
431
457
|
options.error(400, 'This file not supported.');
|
|
432
458
|
}
|