birdpack 1.1.0 → 1.1.1

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.
Files changed (2) hide show
  1. package/lib/core.js +74 -120
  2. package/package.json +1 -1
package/lib/core.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const tools = require('./tools');
2
2
  const powered = require('./powered');
3
3
  const fs = require('fs');
4
+ const pathLib = require('path');
4
5
  const fileType = require('./fileType');
5
6
 
6
7
  module.exports = class{
@@ -300,153 +301,106 @@ module.exports = class{
300
301
  }
301
302
  }
302
303
  file(options){
303
- if(typeof options != 'object'){
304
- options = {file: options};
304
+ if (typeof options !== 'object') {
305
+ options = { file: options };
305
306
  }
306
307
 
307
- if(typeof options.error !== 'function'){
308
- options.error = (code, text)=>{
309
- this.error(code, text);
310
- }
308
+ if (typeof options.error !== 'function') {
309
+ options.error = (code, text) => this.error(code, text);
311
310
  }
312
311
 
313
- const bufferSize = 64 * 1024;
314
- const buffer = Buffer.alloc(bufferSize);
315
- let fileStat = fs.existsSync(options.file) ? fs.statSync(options.file) : false;
312
+ const filePath = options.file;
316
313
 
317
- if(fileStat !== false && fileStat.isFile()){
318
- let fileExt = options.file.split('.').pop().toLowerCase();
314
+ fs.stat(filePath, (err, stat) => {
315
+ if (err || !stat.isFile()) {
316
+ return options.error(404, 'File not found.');
317
+ }
319
318
 
320
- if(fileStat.size == 0){
319
+ if (stat.size === 0) {
321
320
  return options.error(400, 'This file is empty.');
322
321
  }
323
322
 
324
- let fileRange = 'file';
325
- if(options.hasOwnProperty('type')){
326
- }else if(fileType.hasOwnProperty(fileExt)){
327
- if(options.download !== true && ['mp4','mp3','ogg','weba','webm','mp4','avi','wav'].includes(fileExt)){
328
- fileRange = 'stream';
329
- }
330
- options.type = fileType[fileExt];
331
- }else{
332
- options.type = 'application';
323
+ const ext = pathLib.extname(filePath).slice(1).toLowerCase();
324
+
325
+ if (!options.type) {
326
+ options.type = fileType?.[ext] || 'application/octet-stream';
333
327
  }
334
328
 
335
- if(options.stream === true){
336
- fileRange = 'stream';
329
+ const isMedia = ['mp4','mp3','ogg','weba','webm','avi','wav'].includes(ext);
330
+ const forceStream = options.stream === true;
331
+ const allowStream = isMedia && options.download !== true;
332
+
333
+ const rangeHeader = this.get('range');
334
+
335
+ this.set('accept-ranges', 'bytes')
336
+ .type(options.type);
337
+
338
+ if (options.download === true) {
339
+ this.set(
340
+ 'content-disposition',
341
+ `attachment; filename="${options.filename || pathLib.basename(filePath)}"`
342
+ );
337
343
  }
338
344
 
339
- if(fileRange == 'stream'){
340
- const totalSize = fileStat.size;
341
- const range = this.get('range') || '';
342
- let match = range.slice(6).split('-');
345
+ if ((forceStream || allowStream) && rangeHeader) {
346
+ const total = stat.size;
347
+ const [startStr, endStr] = rangeHeader.replace(/bytes=/, '').split('-');
343
348
 
344
- const start = parseInt(match[0], 10) || 0;
345
- const end = parseInt(match[1], 10) || totalSize - 1;
349
+ const start = parseInt(startStr, 10) || 0;
350
+ const end = endStr ? parseInt(endStr, 10) : total - 1;
351
+
352
+ if (start >= total || end >= total) {
353
+ this.code(416)
354
+ .set('content-range', `bytes */${total}`)
355
+ .writeHead();
356
+ return;
357
+ }
346
358
 
347
359
  const chunkSize = end - start + 1;
348
- const fileDescriptor = fs.openSync(options.file, "r");
349
360
 
350
- const sendChunk = (position) => {
351
- if(position > end){
352
- fs.closeSync(fileDescriptor);
353
- return;
354
- }
361
+ this.code(206)
362
+ .length(chunkSize)
363
+ .set('content-range', `bytes ${start}-${end}/${total}`)
364
+ .writeHead();
355
365
 
356
- const bytesToRead = Math.min(bufferSize, end - position + 1);
357
- const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
358
-
359
- if(bytesRead > 0 && this.res.writable){
360
- this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
361
- if(err){
362
- fs.closeSync(fileDescriptor);
363
- }else{
364
- sendChunk(position + bytesRead);
365
- }
366
- });
367
- }else{
368
- fs.closeSync(fileDescriptor);
369
- }
370
- };
371
-
372
- this.code(range == '' ? 200 : 206)
373
- .length(chunkSize)
374
- .type(options.type)
375
- .set('accept-ranges', 'bytes')
376
- .set('content-range', `bytes ${start}-${end}/${totalSize}`)
377
- .writeHead();
378
- sendChunk(start);
379
- }else if(fileRange == 'file'){
380
- if(options.parameter){
381
- let data = fs.readFileSync(options.file, { encoding: 'utf8', flag: 'r' });
382
-
383
- data = data.replace(/\{\{[a-zA-Z0-9\_]*\}\}/g, (a)=>{
384
- let key = a.slice(2,-2);
385
- if(key != ''){
386
- if(options.parameter.hasOwnProperty(key)){
387
- return options.parameter[key]
388
- }
389
- }
390
- return '';
391
- });
366
+ const stream = fs.createReadStream(filePath, { start, end });
392
367
 
393
- data = Buffer.from(data, "utf8");
368
+ stream.pipe(this.res);
369
+ this.res.on('close', () => stream.destroy());
370
+ stream.on('error', () => stream.destroy());
371
+
372
+ return;
373
+ }
394
374
 
395
- if(options.download === true){
396
- this.set('content-disposition', `attachment; filename="${options.filename}"`);
375
+ if (options.parameter) {
376
+ fs.readFile(filePath, 'utf8', (err, data) => {
377
+ if (err) {
378
+ return options.error(500, 'File read error.');
397
379
  }
398
380
 
399
- this.code(200)
400
- .type(options.type);
401
-
402
- this.send(data);
403
- }else{
404
- const totalSize = fileStat.size;
381
+ data = data.replace(/\{\{(\w+)\}\}/g, (_, key) =>
382
+ options.parameter?.[key] ?? ''
383
+ );
405
384
 
406
- const start = 0;
407
- const end = totalSize - 1;
385
+ const buf = Buffer.from(data, 'utf8');
408
386
 
409
- const chunkSize = end - start + 1;
410
- const fileDescriptor = fs.openSync(options.file, "r");
387
+ this.code(200)
388
+ .send(buf);
389
+ });
411
390
 
412
- const sendChunk = (position) => {
413
- if(position > end){
414
- fs.closeSync(fileDescriptor);
415
- return;
416
- }
391
+ return;
392
+ }
417
393
 
418
- const bytesToRead = Math.min(bufferSize, end - position + 1);
419
- const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
420
-
421
- if(bytesRead > 0 && this.res.writable){
422
- this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
423
- if (err) {
424
- fs.closeSync(fileDescriptor);
425
- } else {
426
- sendChunk(position + bytesRead);
427
- }
428
- });
429
- }else{
430
- fs.closeSync(fileDescriptor);
431
- }
432
- };
394
+ this.code(200)
395
+ .length(stat.size)
396
+ .writeHead();
433
397
 
434
- this.code(200)
435
- .length(chunkSize)
436
- .type(options.type);
398
+ const stream = fs.createReadStream(filePath);
437
399
 
438
- if(options.download === true){
439
- this.set('content-disposition', `attachment; filename="${options.filename}"`);
440
- }
441
-
442
- this.writeHead();
443
- sendChunk(start);
444
- }
445
- }else{
446
- options.error(400, 'This file not supported.');
447
- }
448
- }else{
449
- options.error(404, 'File not found.');
450
- }
400
+ stream.pipe(this.res);
401
+
402
+ this.res.on('close', () => stream.destroy());
403
+ stream.on('error', () => stream.destroy());
404
+ });
451
405
  }
452
406
  }
package/package.json CHANGED
@@ -3,6 +3,6 @@
3
3
  "description": "BirdPack web framework is a tool for web server via TCP HTTP supporting websocket focusing on speed.",
4
4
  "author":"R938",
5
5
  "license":"R938",
6
- "version":"1.1.0",
6
+ "version":"1.1.1",
7
7
  "main": "index.js"
8
8
  }