birdpack 1.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/lib/core.js +411 -0
- package/lib/fileType.json +35 -0
- package/lib/server.js +426 -0
- package/lib/tools.js +330 -0
- package/lib/websocket.js +312 -0
- package/package.json +17 -0
package/lib/core.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
const tools = require('./tools');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const fileType = require('./fileType');
|
|
4
|
+
|
|
5
|
+
module.exports = class{
|
|
6
|
+
status = 0
|
|
7
|
+
header = {}
|
|
8
|
+
cookie = {}
|
|
9
|
+
buffers = []
|
|
10
|
+
body = {}
|
|
11
|
+
host = ''
|
|
12
|
+
url = ''
|
|
13
|
+
query = {}
|
|
14
|
+
method = ''
|
|
15
|
+
callrouter = ()=>{}
|
|
16
|
+
params = {}
|
|
17
|
+
constructor({req, res}){
|
|
18
|
+
this.req = req;
|
|
19
|
+
this.res = res;
|
|
20
|
+
|
|
21
|
+
this.req.on('data', chunk => {
|
|
22
|
+
this.buffers.push(chunk);
|
|
23
|
+
})
|
|
24
|
+
.on('end', () => {
|
|
25
|
+
if(this.buffers.length !== 0){
|
|
26
|
+
this.buffers = Buffer.concat(this.buffers);
|
|
27
|
+
}else{
|
|
28
|
+
this.buffers = Buffer.alloc(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if(this.process() !== false){
|
|
32
|
+
let type = this.get('content-type');
|
|
33
|
+
if(type !== false){
|
|
34
|
+
type = type.split('; ');
|
|
35
|
+
|
|
36
|
+
if(type[0] == 'application/x-www-form-urlencoded'){
|
|
37
|
+
this.body = tools.wwwform(this.buffers);
|
|
38
|
+
}else if(type[0] == 'application/json'){
|
|
39
|
+
try{
|
|
40
|
+
this.body = JSON.parse(this.buffers);
|
|
41
|
+
}catch(e){
|
|
42
|
+
this.error(500, 'Invalid JSON.');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}else if(type[0] == 'multipart/form-data' && type[1]){
|
|
46
|
+
let key = type[1].slice(9);
|
|
47
|
+
this.body = tools.formdata(this.buffers, key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
this.callrouter();
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
.on('error', (e) => {
|
|
54
|
+
console.log(e);
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
process(){
|
|
58
|
+
if(!this.req.headers.host){
|
|
59
|
+
this.error(400, 'Invalid Host.');
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const findHost = this.req.headers.host.indexOf(':');
|
|
64
|
+
this.host = findHost === -1 ? this.req.headers.host : this.req.headers.host.slice(0, findHost);
|
|
65
|
+
|
|
66
|
+
this.method = this.req.method.toLowerCase();
|
|
67
|
+
|
|
68
|
+
const url = this.req.url.split('?');
|
|
69
|
+
this.path = url[0];
|
|
70
|
+
if(url[1]){
|
|
71
|
+
this.query = tools.wwwform(url[1]);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if(this.req.headers.hasOwnProperty('cookie')){
|
|
75
|
+
let cookie = [];
|
|
76
|
+
if(Array.isArray(this.req.headers.cookie)){
|
|
77
|
+
cookie = this.req.headers.cookie;
|
|
78
|
+
}else{
|
|
79
|
+
cookie.push(this.req.headers.cookie);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for(let x of cookie){
|
|
83
|
+
for(let i of x.split('; ')){
|
|
84
|
+
let index = i.indexOf('=');
|
|
85
|
+
let key = i.slice(0, index);
|
|
86
|
+
let value = i.slice(index + 1);
|
|
87
|
+
|
|
88
|
+
try{
|
|
89
|
+
key = decodeURIComponent(key);
|
|
90
|
+
value = decodeURIComponent(value);
|
|
91
|
+
}catch(e){}
|
|
92
|
+
|
|
93
|
+
this.cookie[key] = value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
router(call){
|
|
100
|
+
this.callrouter = call;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
code(status){
|
|
104
|
+
this.status = status;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
set(key, value){
|
|
108
|
+
if(this.header.hasOwnProperty(key)){
|
|
109
|
+
this.header[key].push(value);
|
|
110
|
+
}else{
|
|
111
|
+
this.header[key] = [value];
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
get(key){
|
|
116
|
+
if(this.req.headers.hasOwnProperty(key)){
|
|
117
|
+
let header = this.req.headers[key];
|
|
118
|
+
if(typeof header == 'string'){
|
|
119
|
+
return header;
|
|
120
|
+
}else if(Array.isArray(header)){
|
|
121
|
+
return header[header.length - 1];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
param(key, value){
|
|
127
|
+
this.params[key] =value ;
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
look(keys){
|
|
131
|
+
for(let x of keys.split(/[\s]*,[\s]*/)){
|
|
132
|
+
if(!this.query.hasOwnProperty(x)){
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
u(key){
|
|
139
|
+
if(this.query.hasOwnProperty(key)){
|
|
140
|
+
if(Array.isArray(this.query[key])){
|
|
141
|
+
return this.query[key][this.query[key].length - 1];
|
|
142
|
+
}else{
|
|
143
|
+
return this.query[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
scookie = (name, value, option) => {
|
|
149
|
+
let text = [`${name}=${encodeURI((value || '').toString())}`];
|
|
150
|
+
for(let x in option){
|
|
151
|
+
if(['domain', 'expires', 'httponly', 'max-age', 'partitioned', 'path', 'secure', 'sameSite'].includes(x)){
|
|
152
|
+
if(['string','number'].includes(typeof option[x])){
|
|
153
|
+
text.push(`${x}=${encodeURI(option[x].toString())}`);
|
|
154
|
+
}else{
|
|
155
|
+
text.push(`${x}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
this.set('set-cookie', text.join('; '));
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
rcookie(name, option){
|
|
163
|
+
if(typeof option != 'object'){
|
|
164
|
+
option = {};
|
|
165
|
+
}
|
|
166
|
+
option['max-age'] = '-1';
|
|
167
|
+
this.scookie(name, '', option);
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
writeHead(){
|
|
172
|
+
this.header['x-powered-by'] = `R938`;
|
|
173
|
+
if(this.res.writable){
|
|
174
|
+
this.res.writeHead(this.status ? this.status : 200, this.header);
|
|
175
|
+
}else if(this.req.destroyed){
|
|
176
|
+
this.req.destroy();
|
|
177
|
+
}
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
write(data, callback){
|
|
181
|
+
if(this.res.writable){
|
|
182
|
+
this.res.write(data, callback);
|
|
183
|
+
}else if(this.req.destroyed){
|
|
184
|
+
this.req.destroy();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
end(data, callback){
|
|
188
|
+
if(this.res.writable){
|
|
189
|
+
this.res.end(data, callback);
|
|
190
|
+
}else if(this.req.destroyed){
|
|
191
|
+
this.req.destroy();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
error(status, text){
|
|
195
|
+
this.code(status).send(text || '');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
length(length){
|
|
200
|
+
this.set('content-length', length);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
type(type){
|
|
204
|
+
this.set('content-type', type);
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
check(keys){
|
|
209
|
+
for(let x of keys.split(/[\s]*,[\s]*/)){
|
|
210
|
+
if(!this.body.hasOwnProperty(x)){
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
q(key){
|
|
217
|
+
if(this.body.hasOwnProperty(key)){
|
|
218
|
+
if(Array.isArray(this.body[key])){
|
|
219
|
+
return this.body[key][this.body[key].length - 1];
|
|
220
|
+
}else{
|
|
221
|
+
return this.body[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
send(data){
|
|
229
|
+
if(typeof data === 'string' || typeof data === 'number'){
|
|
230
|
+
data = Buffer.from(data.toString(), 'utf8');
|
|
231
|
+
}else if(!Buffer.isBuffer(data)){
|
|
232
|
+
data = Buffer.alloc(0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if(!this.header.hasOwnProperty('content-type')){
|
|
236
|
+
this.type('text/plain; charset=UTF-8')
|
|
237
|
+
}
|
|
238
|
+
this.length(data.length);
|
|
239
|
+
|
|
240
|
+
this.writeHead();
|
|
241
|
+
this.end(data);
|
|
242
|
+
}
|
|
243
|
+
json(data){
|
|
244
|
+
try{
|
|
245
|
+
data = JSON.stringify(data);
|
|
246
|
+
this.type('application/json; charset=utf-8')
|
|
247
|
+
.send(data);
|
|
248
|
+
}catch(e){
|
|
249
|
+
this.error(500);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
html(data){
|
|
253
|
+
this.type('text/html; charset=utf-8')
|
|
254
|
+
.send(data);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
redirect(a, b){
|
|
258
|
+
const typeA = typeof a;
|
|
259
|
+
const typeB = typeof b;
|
|
260
|
+
let status = false;
|
|
261
|
+
let location = false;
|
|
262
|
+
|
|
263
|
+
if(typeA == 'number' && typeB == 'string'){
|
|
264
|
+
status = a;
|
|
265
|
+
location = b;
|
|
266
|
+
}else if(typeA == 'string'){
|
|
267
|
+
status = 301;
|
|
268
|
+
location = a;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if(status !== false){
|
|
272
|
+
this.code(status)
|
|
273
|
+
.set('location', location)
|
|
274
|
+
.writeHead();
|
|
275
|
+
this.end();
|
|
276
|
+
}else{
|
|
277
|
+
this.error(status);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
file(options){
|
|
281
|
+
if(typeof options != 'object'){
|
|
282
|
+
options = {file: options};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if(typeof options.error !== 'function'){
|
|
286
|
+
options.error = (code, text)=>{
|
|
287
|
+
this.error(code, text);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const bufferSize = 64 * 1024;
|
|
292
|
+
const buffer = Buffer.alloc(bufferSize);
|
|
293
|
+
let fileStat = fs.existsSync(options.file) ? fs.statSync(options.file) : false;
|
|
294
|
+
|
|
295
|
+
if(fileStat !== false && fileStat.isFile()){
|
|
296
|
+
let fileExt = options.file.split('.').pop().toLowerCase();
|
|
297
|
+
|
|
298
|
+
let fileRange = 'file';
|
|
299
|
+
if(options.hasOwnProperty('type')){
|
|
300
|
+
}else if(fileType.hasOwnProperty(fileExt)){
|
|
301
|
+
if(options.download !== true && ['mp4','mp3','ogg','weba','webm','mp4','avi','wav'].includes(fileExt)){
|
|
302
|
+
fileRange = 'stream';
|
|
303
|
+
}
|
|
304
|
+
options.type = fileType[fileExt];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if(options.stream === true){
|
|
308
|
+
fileRange = 'stream';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if(fileRange == 'stream'){
|
|
312
|
+
const totalSize = fileStat.size;
|
|
313
|
+
let match = (this.get('range') || '').slice(6).split('-');
|
|
314
|
+
|
|
315
|
+
this.set('accept-ranges', 'bytes')
|
|
316
|
+
.type(options.type);
|
|
317
|
+
|
|
318
|
+
if(match.length == 2){
|
|
319
|
+
const start = parseInt(match[0], 10) || 0;
|
|
320
|
+
const end = parseInt(match[1], 10) || totalSize - 1;
|
|
321
|
+
|
|
322
|
+
const chunkSize = end - start + 1;
|
|
323
|
+
const fileDescriptor = fs.openSync(options.file, "r");
|
|
324
|
+
|
|
325
|
+
const sendChunk = (position) => {
|
|
326
|
+
if(position > end){
|
|
327
|
+
fs.closeSync(fileDescriptor);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const bytesToRead = Math.min(bufferSize, end - position + 1);
|
|
332
|
+
const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
|
|
333
|
+
|
|
334
|
+
if(bytesRead > 0 && this.res.writable){
|
|
335
|
+
this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
|
|
336
|
+
if(err){
|
|
337
|
+
fs.closeSync(fileDescriptor);
|
|
338
|
+
}else{
|
|
339
|
+
sendChunk(position + bytesRead);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}else{
|
|
343
|
+
fs.closeSync(fileDescriptor);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
this.code(206)
|
|
348
|
+
.length(chunkSize)
|
|
349
|
+
.set('content-range', `bytes ${start}-${end}/${totalSize}`)
|
|
350
|
+
.writeHead();
|
|
351
|
+
sendChunk(start);
|
|
352
|
+
}else{
|
|
353
|
+
const start = 0;
|
|
354
|
+
const end = totalSize - 1;
|
|
355
|
+
const chunkSize = end - start + 1;
|
|
356
|
+
|
|
357
|
+
this.code(200)
|
|
358
|
+
.length(chunkSize)
|
|
359
|
+
.set('content-range', `bytes ${start}-${end}/${totalSize}`)
|
|
360
|
+
.writeHead();
|
|
361
|
+
this.end();
|
|
362
|
+
}
|
|
363
|
+
}else if(fileRange == 'file'){
|
|
364
|
+
const totalSize = fileStat.size;
|
|
365
|
+
|
|
366
|
+
const start = 0;
|
|
367
|
+
const end = totalSize - 1;
|
|
368
|
+
|
|
369
|
+
const chunkSize = end - start + 1;
|
|
370
|
+
const fileDescriptor = fs.openSync(options.file, "r");
|
|
371
|
+
|
|
372
|
+
const sendChunk = (position) => {
|
|
373
|
+
if(position > end){
|
|
374
|
+
fs.closeSync(fileDescriptor);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const bytesToRead = Math.min(bufferSize, end - position + 1);
|
|
379
|
+
const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
|
|
380
|
+
|
|
381
|
+
if(bytesRead > 0 && this.res.writable){
|
|
382
|
+
this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
|
|
383
|
+
if (err) {
|
|
384
|
+
fs.closeSync(fileDescriptor);
|
|
385
|
+
} else {
|
|
386
|
+
sendChunk(position + bytesRead);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}else{
|
|
390
|
+
fs.closeSync(fileDescriptor);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
this.code(200)
|
|
395
|
+
.length(chunkSize)
|
|
396
|
+
.type(options.type);
|
|
397
|
+
|
|
398
|
+
if(options.download === true){
|
|
399
|
+
this.set('content-disposition', `attachment; filename="${options.filename}"`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this.writeHead();
|
|
403
|
+
sendChunk(start);
|
|
404
|
+
}else{
|
|
405
|
+
options.error(400, 'This file not supported.');
|
|
406
|
+
}
|
|
407
|
+
}else{
|
|
408
|
+
options.error(404, 'File not found.');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"html": "text/html; charset=UTF-8",
|
|
3
|
+
"txt": "text/plain; charset=UTF-8",
|
|
4
|
+
"json": "application/json; charset=UTF-8",
|
|
5
|
+
"gif": "image/gif",
|
|
6
|
+
"jpeg": "image/jpeg",
|
|
7
|
+
"jpg": "image/jpg",
|
|
8
|
+
"webp": "image/webp",
|
|
9
|
+
"png": "image/png",
|
|
10
|
+
"svg": "image/svg+xml",
|
|
11
|
+
"ico": "image/x-icon",
|
|
12
|
+
"css": "text/css; charset=UTF-8",
|
|
13
|
+
"js": "application/javascript; charset=UTF-8",
|
|
14
|
+
"xml": "application/xml; charset=UTF-8",
|
|
15
|
+
"ttf": "font/ttf",
|
|
16
|
+
"eot": "font/eot",
|
|
17
|
+
"otf": "font/otf",
|
|
18
|
+
"woff": "font/woff",
|
|
19
|
+
"woff2": "font/woff2",
|
|
20
|
+
"mp3": "audio/mpeg",
|
|
21
|
+
"ogg": "audio/ogg",
|
|
22
|
+
"wav": "audio/wav",
|
|
23
|
+
"weba": "audio/weba",
|
|
24
|
+
"webm": "video/webm",
|
|
25
|
+
"mp4": "video/mp4",
|
|
26
|
+
"ts": "video/MP2T",
|
|
27
|
+
"m3u8": "application/x-mpegURL",
|
|
28
|
+
"avi": "video/x-msvideo",
|
|
29
|
+
"pdf": "application/pdf",
|
|
30
|
+
"rar": "application/rar",
|
|
31
|
+
"zip": "application/zip",
|
|
32
|
+
"tar": "application/tar",
|
|
33
|
+
"docx": "application/docx",
|
|
34
|
+
"xlsx": "application/xlsx"
|
|
35
|
+
}
|