birdpack 1.0.9 → 1.0.11
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/method.js +3 -2
- package/lib/server.js +32 -11
- package/lib/websocket.js +5 -1
- 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/method.js
CHANGED
|
@@ -64,7 +64,7 @@ module.exports = {
|
|
|
64
64
|
return this;
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
|
-
websocket:(app, use, domain)=>{
|
|
67
|
+
websocket:(app, use, {domain, next})=>{
|
|
68
68
|
return (url, a, b, c)=>{
|
|
69
69
|
let typeA = typeof a == 'function';
|
|
70
70
|
let typeB = typeof b == 'function';
|
|
@@ -95,7 +95,8 @@ module.exports = {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
app.setUpgrade({
|
|
98
|
-
domain,
|
|
98
|
+
domain,
|
|
99
|
+
next,
|
|
99
100
|
url,
|
|
100
101
|
callback: app.setCall(callback)
|
|
101
102
|
});
|
package/lib/server.js
CHANGED
|
@@ -87,7 +87,7 @@ module.exports = class{
|
|
|
87
87
|
methodPlugin.basic(this, this, {domain:'*'});
|
|
88
88
|
this.routes = methodPlugin.routes(this);
|
|
89
89
|
this.directory = methodPlugin.directory(this);
|
|
90
|
-
this.websocket = methodPlugin.websocket(this, this, '*');
|
|
90
|
+
this.websocket = methodPlugin.websocket(this, this, {domain:'*'});
|
|
91
91
|
}
|
|
92
92
|
setupSSL(){
|
|
93
93
|
if(this.opt.use !== 'https'){
|
|
@@ -236,13 +236,19 @@ module.exports = class{
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
if(call && call.hasOwnProperty(ws.path)){
|
|
239
|
-
call = call[ws.path]
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
239
|
+
call = call[ws.path];
|
|
240
|
+
|
|
241
|
+
if(call.next !== false){
|
|
242
|
+
this.getCall(call.next)(ws, ()=>{
|
|
243
|
+
const run = this.getCall(call.callback);
|
|
244
|
+
ws.update(run);
|
|
245
|
+
run(ws, 'start');
|
|
246
|
+
});
|
|
247
|
+
}else{
|
|
248
|
+
const run = this.getCall(call.callback);
|
|
249
|
+
ws.update(run);
|
|
250
|
+
run(ws, 'start');
|
|
251
|
+
}
|
|
246
252
|
}else{
|
|
247
253
|
ws.error(404);
|
|
248
254
|
}
|
|
@@ -317,6 +323,7 @@ module.exports = class{
|
|
|
317
323
|
methodPlugin.basic(this, next, {domain, next:this.setCall(n)});
|
|
318
324
|
next.routes = methodPlugin.routes(next);
|
|
319
325
|
next.directory = methodPlugin.directory(next);
|
|
326
|
+
next.websocket = methodPlugin.websocket(this, next, {domain, next:this.setCall(n)});
|
|
320
327
|
|
|
321
328
|
return next;
|
|
322
329
|
}
|
|
@@ -324,7 +331,7 @@ module.exports = class{
|
|
|
324
331
|
methodPlugin.basic(this, config, {domain});
|
|
325
332
|
config.routes = methodPlugin.routes(config);
|
|
326
333
|
config.directory = methodPlugin.directory(config);
|
|
327
|
-
config.websocket = methodPlugin.websocket(this, config, domain);
|
|
334
|
+
config.websocket = methodPlugin.websocket(this, config, {domain});
|
|
328
335
|
|
|
329
336
|
return config;
|
|
330
337
|
}
|
|
@@ -377,7 +384,7 @@ module.exports = class{
|
|
|
377
384
|
this.maps[domain][method].params[mode == 'params' ? 0 : 1].push(route);
|
|
378
385
|
}
|
|
379
386
|
}
|
|
380
|
-
setUpgrade({domain, url, callback}){
|
|
387
|
+
setUpgrade({domain, next, url, callback}){
|
|
381
388
|
if(typeof domain != 'string'){
|
|
382
389
|
domain = '*';
|
|
383
390
|
}
|
|
@@ -390,12 +397,17 @@ module.exports = class{
|
|
|
390
397
|
return false;
|
|
391
398
|
}
|
|
392
399
|
|
|
400
|
+
if(typeof next != 'number'){
|
|
401
|
+
next = false;
|
|
402
|
+
}
|
|
403
|
+
|
|
393
404
|
if(!this.mapws.hasOwnProperty(domain)){
|
|
394
405
|
this.mapws[domain] = {};
|
|
395
406
|
}
|
|
396
407
|
|
|
397
408
|
this.mapws[domain][url] = {
|
|
398
|
-
callback
|
|
409
|
+
callback,
|
|
410
|
+
next,
|
|
399
411
|
};
|
|
400
412
|
}
|
|
401
413
|
setCall(callback){
|
|
@@ -408,5 +420,14 @@ module.exports = class{
|
|
|
408
420
|
}
|
|
409
421
|
return ()=>{};
|
|
410
422
|
}
|
|
423
|
+
next(n){
|
|
424
|
+
let next = {};
|
|
425
|
+
methodPlugin.basic(this, next, {domain:'*', next:this.setCall(n)});
|
|
426
|
+
next.routes = methodPlugin.routes(next);
|
|
427
|
+
next.directory = methodPlugin.directory(next);
|
|
428
|
+
next.websocket = methodPlugin.websocket(this, next, {domain:'*', next:this.setCall(n)});
|
|
429
|
+
|
|
430
|
+
return next;
|
|
431
|
+
}
|
|
411
432
|
}
|
|
412
433
|
|
package/lib/websocket.js
CHANGED
|
@@ -11,6 +11,7 @@ module.exports = class{
|
|
|
11
11
|
query = {}
|
|
12
12
|
method = ''
|
|
13
13
|
ip = ''
|
|
14
|
+
isUID = false
|
|
14
15
|
callback = ()=>{}
|
|
15
16
|
log = ()=>{}
|
|
16
17
|
constructor({req, socket, head}){
|
|
@@ -170,6 +171,7 @@ module.exports = class{
|
|
|
170
171
|
const head = tools.head2line(this.header);
|
|
171
172
|
const statusLine = `HTTP/1.1 ${this.status} ${tools.statusText(this.status)}`;
|
|
172
173
|
const headLine = `${statusLine}\r\n${head}\r\n\r\n`;
|
|
174
|
+
this.isUID = true;
|
|
173
175
|
this.write(Buffer.from(headLine));
|
|
174
176
|
|
|
175
177
|
this.log();
|
|
@@ -177,7 +179,9 @@ module.exports = class{
|
|
|
177
179
|
return this;
|
|
178
180
|
}
|
|
179
181
|
write(data, callback){
|
|
180
|
-
if(this.
|
|
182
|
+
if(!this.isUID){
|
|
183
|
+
this.error(400);
|
|
184
|
+
}else if(this.socket.writable){
|
|
181
185
|
this.socket.write(data, callback);
|
|
182
186
|
}else if(this.req.destroyed){
|
|
183
187
|
this.req.destroy();
|