rtjscomp 0.8.8 → 0.9.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ easy to use http server that allows for using javascript just as php was used ba
6
6
  > as the issues indicate, a lot of breaking changes are ahead.
7
7
  > make sure to check for these in future until version 1.0.0 is relessed.
8
8
 
9
- ## Usage
9
+ ## usage
10
10
 
11
11
  go into the directory where you want to have your project and run
12
12
 
@@ -29,23 +29,78 @@ $ npm install
29
29
  $ npm start
30
30
  ```
31
31
 
32
- ## Files
32
+ ## rtjscomp.json (optional)
33
+
34
+ it is only needed if you want to change default settings. it has the following properties:
35
+
36
+ - `path_aliases`: object where the key is the url path and the value is the file path
37
+ - `path_ghosts`: array of paths that are simply ignored, no response is sent
38
+ - `path_hiddens`: array of paths that give 404
39
+ - `path_statics`: array of paths in which nothing is transpiled, but the file is sent as is
40
+ - `port_http`: port for http server, default is 8080
41
+ - `port_https`: port for https server
42
+ - `services`: paths to (enabled) service files, prepend an entry with # to disable it
43
+ - `type_dynamics`: dynamic file types, see [api](#api)
44
+ - `type_mimes`: file type to mime type map (most common are already set, only overrides)
45
+ - `type_raws`: array of file types that are sent uncompressed
46
+
47
+ here is an example for a customized setup:
48
+
49
+ ```json
50
+ {
51
+ "path_aliases": {
52
+ "": "index.html",
53
+ "blog": "modules/blog/index.html",
54
+ "blog/*id": "modules/blog/article.html"
55
+ },
56
+ "path_ghosts": [ "admin", "wp-admin" ],
57
+ "path_hiddens": [ "secrets.html" ],
58
+ "path_statics": [ "rawtruth.html" ],
59
+ "port_http": 8080,
60
+ "services": [
61
+ "modules/blog/blog",
62
+ "#modules/not/needed"
63
+ ],
64
+ "type_dynamics": [ "html", "js" ],
65
+ "type_mimes": {
66
+ "html": "text/html; charset=UTF-8",
67
+ "js": "application/javascript; charset=UTF-8",
68
+ "custom": "application/custom"
69
+ },
70
+ "type_raws": [ "png", "jpg", "pdf" ]
71
+ }
72
+ ```
33
73
 
34
- when run first time, it will be created with some defaults. all files in config/public are watched, no reload command needed.
74
+ ## directories
35
75
 
36
- ### Directories
76
+ on first run, they will be created with some defaults.
37
77
 
38
- - config dir, has simple txt files (this will change soon!)
39
- - data dir, services store their data here
78
+ - data dir, services store their data or read custom config files here
40
79
  - public dir, containing dynamic and static offerings and also services
41
80
 
42
- ### File types
81
+ ## files in public dir
43
82
 
44
- - static files like .png in public dir are sent to requests 1:1
45
- - dynamic files like .html are transpiled to javascript and thereby compiled to machine code by the js engine as they are first time requested -> "rtjscomp"
83
+ - static files like .png in public dir are sent to requests 1:1, maybe compressed
84
+ - dynamic files like .html are internally transpiled/compiled and executed -> "rtjscomp"
46
85
  - .service.js files are executed and their `this` can be accessed by other files, they are not accessible to outside
47
86
 
48
- ## API
87
+ example:
88
+
89
+ ```
90
+ public
91
+ ├── index.html
92
+ ├── modules
93
+ │ ├── blog
94
+ │ │ ├── article.html
95
+ │ │ ├── blog.service.js
96
+ │ │ └── index.html
97
+ │ └── not
98
+ │ └── needed.service.js
99
+ ├── rawtruth.html
100
+ └── secrets.html
101
+ ```
102
+
103
+ ## api
49
104
 
50
105
  in every served dynamic file (like .html), you can insert `<?` and `?>` tags to insert javascript code that is executed server-side. `<?= ... ?>` can be used to insert the result of an expression.
51
106
  request-independent services can be created using .service.js files referenced in services.txt.
@@ -54,14 +109,14 @@ in both file types (dynamic served and services), you can use all node/bun metho
54
109
  - `service_require(service path)`: returns the matching service object
55
110
  - `service_require_try(service path)`: returns the matching service object or null if not found or if disabled
56
111
  - `rtjscomp`: has these properties/methods:
57
- - `actions`: an object with methods for server control (http[s]_[start|stop|restart|kill], log_clear, halt, exit)
112
+ - `actions`: an object with methods for server control (http[s]_[start|stop|kill], log_clear, halt, exit)
58
113
  - `async data_load(path)`: reads the file in data directory and returns its content or null
59
114
  - `async data_load_watch(path, callback(content))`: executes callback first and on every change
60
115
  - `async data_save(path, content)`: writes the content to the file in data directory
61
116
 
62
- ## Supported environments
117
+ ## supported environments
63
118
 
64
- - node 4.0.0 or higher
119
+ - node 8.0.0 or higher
65
120
  - bun
66
121
 
67
122
  any os
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtjscomp",
3
- "version": "0.8.8",
3
+ "version": "0.9.1",
4
4
  "description": "php-like server but with javascript",
5
5
  "repository": {
6
6
  "type": "git",
package/public/index.html CHANGED
@@ -1,4 +1,16 @@
1
- <?
2
- const zahl = Number(input.zahl || 42);
3
- ?><h1>Hallo, Welt!</h1>
4
- <p>Zahl: <?= zahl ?></p>
1
+ <!DOCTYPE html><?
2
+
3
+ const code = Number(input.code || 42);
4
+ // this is not php but javascript!!!!!!!!!!!!!
5
+ // but for better syntax highlighting, set ide language to php.
6
+ // open your local website and look into its source code. this script will be gone.
7
+
8
+ ?>
9
+ <html>
10
+ <body>
11
+ <h1>Hello, World!</h1>
12
+ <p>Current reason: <?= code ?></p>
13
+ <p><a href="/?code=<?= code + 1 ?>">One more</a></p>
14
+ <p>See <a href="https://github.com/L3P3/rtjscomp">the readme</a>!</p>
15
+ </body>
16
+ </html>
package/rtjscomp.js CHANGED
@@ -7,52 +7,102 @@
7
7
 
8
8
  (async () => {
9
9
 
10
- const http = require('http');
11
- const url = require('url');
10
+ // node libs
12
11
  const fs = require('fs');
13
12
  const fsp = require('fs/promises');
14
- const multipart_parse = require('parse-multipart-data').parse;
13
+ const http = require('http');
14
+ const url = require('url');
15
15
  const zlib = require('zlib');
16
- const request_ip_get = require('ipware')().get_ip;
16
+
17
+ // external libs
18
+ const multipart_parse = require('parse-multipart-data').parse;
17
19
  const querystring_parse = require('querystring').decode;
18
- const resolve_options = {paths: [require('path').resolve()]};
20
+ const request_ip_get = require('ipware')().get_ip;
19
21
 
20
- const VERSION = require('./package.json').version;
21
- const PATH_PUBLIC = 'public/';
22
- const PATH_CONFIG = 'config/';
23
- const PATH_DATA = 'data/';
24
- const GZIP_OPTIONS = {level: 9};
25
- const WATCH_OPTIONS = {persistent: true, interval: 1000};
22
+ // constants
26
23
  const AGENT_CHECK_BOT = /bot|googlebot|crawler|spider|robot|crawling|favicon/i;
27
24
  const AGENT_CHECK_MOBIL = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i;
25
+ const GZIP_OPTIONS = {level: 9};
28
26
  const HTTP_LIST_REG = /,\s*/;
29
27
  const IMPORT_REG = /import\(/g;
28
+ const IS_BUN = typeof Bun !== 'undefined';
29
+ const PATH_CONFIG = 'config/';
30
+ const PATH_DATA = 'data/';
31
+ const PATH_PUBLIC = 'public/';
32
+ const RESOLVE_OPTIONS = {paths: [require('path').resolve()]};
33
+ const VERSION = require('./package.json').version;
34
+ const WATCH_OPTIONS = {persistent: true, interval: 1000};
30
35
 
36
+ // config
31
37
  let log_verbose = process.argv.includes('-v');
32
38
  let port_http = 0;
33
39
  let port_https = 0;
34
- const file_type_mimes = new Map;
35
- const file_type_dyns = new Set;
36
- const file_type_nocompress = new Set;
37
- /// forced static files
38
- const file_raws = new Set;
39
- /// hidden files
40
- const file_privates = new Set;
41
- /// files where requests should be totally ignored
42
- const file_blocks = new Set;
43
40
  /// any path -> file
44
- const path_aliases = new Map;
45
- const path_aliases_reverse = new Map;
41
+ const path_aliases = new Map([
42
+ ['', 'index.html'],
43
+ ]);
44
+ const path_aliases_reverse = new Map([
45
+ ['index.html', '/'],
46
+ ]);
46
47
  const path_aliases_templates = new Map;
47
- const services = new Set;
48
+ /// files where requests should be totally ignored
49
+ const path_ghosts = new Set;
50
+ /// hidden files
51
+ const path_hiddens = new Set;
52
+ /// forced static files
53
+ const path_statics = new Set;
54
+ const type_dynamics = new Set([
55
+ 'events',
56
+ 'html',
57
+ 'json',
58
+ 'txt',
59
+ ]);
60
+ const type_mimes = new Map([
61
+ ['apk', 'application/zip'],
62
+ ['bpg', 'image/bpg'],
63
+ ['css', 'text/css; charset=utf-8'],
64
+ ['events', 'text/event-stream'],
65
+ ['flac', 'audio/flac'],
66
+ ['gz', 'application/gzip'],
67
+ ['hta', 'application/hta'],
68
+ ['html', 'text/html; charset=utf-8'],
69
+ ['ico', 'image/x-icon'],
70
+ ['jpg', 'image/jpeg'],
71
+ ['js', 'text/javascript; charset=utf-8'],
72
+ ['json', 'application/json; charset=utf-8'],
73
+ ['mid', 'audio/midi'],
74
+ ['mp3', 'audio/mpeg3'],
75
+ ['pdf', 'application/pdf'],
76
+ ['png', 'image/png'],
77
+ ['rss', 'application/rss+xml; charset=utf-8'],
78
+ ['txt', 'text/plain; charset=utf-8'],
79
+ ['xml', 'application/xml; charset=utf-8'],
80
+ ['xz', 'application/x-xz'],
81
+ ['zip', 'application/zip'],
82
+ ]);
83
+ const type_raws = new Set([
84
+ 'apk',
85
+ 'bpg',
86
+ 'flac',
87
+ 'gz',
88
+ 'jpg',
89
+ 'mp3',
90
+ 'pdf',
91
+ 'png',
92
+ 'xz',
93
+ 'zip',
94
+ ]);
95
+
48
96
  /// compiled file handlers
49
97
  const file_cache_functions = new Map;
98
+
50
99
  const actions = {};
51
100
  const rtjscomp = global.rtjscomp = {
52
101
  actions,
53
102
  version: VERSION,
54
103
  };
55
104
 
105
+ // polyfills
56
106
  if (!Object.fromEntries) {
57
107
  Object.fromEntries = entries => {
58
108
  const object = {};
@@ -63,7 +113,7 @@ if (!Object.fromEntries) {
63
113
 
64
114
  // workaround for bun: https://github.com/oven-sh/bun/issues/18919
65
115
  let fs_watch = fs.watch;
66
- if (typeof Bun !== 'undefined') {
116
+ if (IS_BUN) {
67
117
  const fs_watch_original = fs_watch;
68
118
  const watch_callbacks = new Map;
69
119
  fs_watch = (path, options, callback) => {
@@ -88,7 +138,7 @@ if (typeof Bun !== 'undefined') {
88
138
  global.globals = rtjscomp;
89
139
  global.actions = rtjscomp.actions;
90
140
  global.data_load = name => {
91
- log('[deprecated] synchronous load: ' + PATH_DATA + name);
141
+ log('[deprecated] synchronous load file: ' + PATH_DATA + name);
92
142
  try {
93
143
  return fs.readFileSync(PATH_DATA + name, 'utf8');
94
144
  }
@@ -97,7 +147,7 @@ global.data_load = name => {
97
147
  }
98
148
  }
99
149
  global.data_save = (name, data) => (
100
- log('[deprecated] synchronous save: ' + PATH_DATA + name),
150
+ log('[deprecated] synchronous save file: ' + PATH_DATA + name),
101
151
  fs.writeFileSync(PATH_DATA + name, data, 'utf8')
102
152
  )
103
153
  global.number_check_int = number => (
@@ -108,7 +158,7 @@ global.number_check_uint = number => (
108
158
  )
109
159
 
110
160
  rtjscomp.data_load = async name => {
111
- if (log_verbose) log('load: ' + PATH_DATA + name);
161
+ if (log_verbose) log('load file: ' + PATH_DATA + name);
112
162
  const data = await fsp.readFile(PATH_DATA + name, 'utf8').catch(() => null);
113
163
  return name.endsWith('.json') ? JSON.parse(data || null) : data;
114
164
  }
@@ -122,7 +172,7 @@ rtjscomp.data_load_watch = (name, callback) => (
122
172
  ))
123
173
  )
124
174
  rtjscomp.data_save = (name, data) => (
125
- log_verbose && log('save: ' + PATH_DATA + name),
175
+ log_verbose && log('save file: ' + PATH_DATA + name),
126
176
  fsp.writeFile(
127
177
  PATH_DATA + name,
128
178
  name.endsWith('.json') ? JSON.stringify(data) : data,
@@ -137,8 +187,8 @@ const custom_require = path => {
137
187
  let result = custom_require_cache.get(path);
138
188
  if (result != null) return result;
139
189
 
140
- log_verbose && log('require: ' + path);
141
- const path_real = require.resolve(path, resolve_options);
190
+ log_verbose && log('require module: ' + path);
191
+ const path_real = require.resolve(path, RESOLVE_OPTIONS);
142
192
  custom_require_paths.add(path_real);
143
193
  custom_require_cache.set(
144
194
  path,
@@ -150,11 +200,11 @@ const custom_import = async path => {
150
200
  let result = custom_import_cache.get(path);
151
201
  if (result != null) return result;
152
202
 
153
- log_verbose && log('import: ' + path);
203
+ log_verbose && log('import module: ' + path);
154
204
  custom_import_cache.set(
155
205
  path,
156
206
  result = await import(
157
- 'file://' + require.resolve(path, resolve_options)
207
+ 'file://' + require.resolve(path, RESOLVE_OPTIONS)
158
208
  )
159
209
  );
160
210
  return result;
@@ -170,14 +220,17 @@ const AsyncFunction = custom_import.constructor;
170
220
 
171
221
  const services_active = new Map;
172
222
  const services_loading = new Set;
173
- const services_list_react = async () => {
223
+ const services_list_react = async list => {
174
224
  await Promise.all(
175
225
  Array.from(services_active.entries())
176
- .filter(([path, _]) => !services.has(path))
226
+ .filter(([path, _]) => !list.includes(path))
177
227
  .map(([_, service_object]) => service_stop(service_object, true))
178
228
  );
179
- for (const path of services)
180
- if (!services_active.has(path)) {
229
+ for (const path of list)
230
+ if (
231
+ path.charCodeAt(0) !== 35 &&
232
+ !services_active.has(path)
233
+ ) {
181
234
  await service_start(path);
182
235
  }
183
236
  }
@@ -272,7 +325,6 @@ const service_start_inner = async (path, service_object, file_content) => {
272
325
  if (log_verbose) log('started service: ' + path);
273
326
  }
274
327
  const services_shutdown = () => (
275
- log_verbose && log('shutdown services...'),
276
328
  Promise.all(
277
329
  Array.from(services_active.values())
278
330
  .map(service_object => service_stop(service_object, true))
@@ -317,28 +369,6 @@ global.service_require_try = path => {
317
369
  );
318
370
  }
319
371
 
320
- const map_generate_bol = (set, data) => {
321
- set.clear();
322
- for (const key of data.split('\n'))
323
- if (
324
- key.length > 0 &&
325
- key.charCodeAt(0) !== 35
326
- ) {
327
- set.add(key);
328
- }
329
- }
330
- const map_generate_equ = (map, data) => {
331
- map.clear();
332
- for (const entry of data.split('\n'))
333
- if (
334
- entry.length > 0 &&
335
- entry.charCodeAt(0) !== 35
336
- ) {
337
- const equ = entry.split(':');
338
- map.set(equ[0], equ[1] || '');
339
- }
340
- }
341
-
342
372
  const file_watch_once = (path, callback) => {
343
373
  const watcher = fs_watch(path, WATCH_OPTIONS, () => (
344
374
  watcher.close(),
@@ -369,6 +399,75 @@ const file_keep_new = async (path, callback) => {
369
399
  ));
370
400
  }
371
401
 
402
+ const get_prop_uint = (obj, prop, fallback) => {
403
+ if (
404
+ obj === null ||
405
+ !(prop in obj)
406
+ ) return fallback;
407
+ const value = obj[prop];
408
+ if (
409
+ typeof value !== 'number' ||
410
+ value < 0 ||
411
+ value % 1 > 0
412
+ ) {
413
+ throw prop + ' must be positive integer';
414
+ }
415
+ delete obj[prop];
416
+ return value;
417
+ }
418
+ const get_prop_list = (obj, prop) => {
419
+ if (
420
+ obj === null ||
421
+ !(prop in obj)
422
+ ) return null;
423
+ const value = obj[prop];
424
+ if (
425
+ typeof value !== 'object' ||
426
+ value.length == null ||
427
+ value.some(item => typeof item !== 'string')
428
+ ) {
429
+ throw prop + ' must be array of strings';
430
+ }
431
+ delete obj[prop];
432
+ return value;
433
+ }
434
+ const get_prop_map = (obj, prop) => {
435
+ if (
436
+ obj === null ||
437
+ !(prop in obj)
438
+ ) return null;
439
+ let value = obj[prop];
440
+ if (
441
+ typeof value !== 'object' ||
442
+ (
443
+ value = Object.entries(value)
444
+ ).some(([_, item]) => typeof item !== 'string')
445
+ ) {
446
+ throw prop + ' must be object of strings';
447
+ }
448
+ delete obj[prop];
449
+ return value;
450
+ }
451
+ const parse_old_list = data => (
452
+ data
453
+ .split('\n')
454
+ .filter(entry =>
455
+ entry.length > 0 &&
456
+ entry.charCodeAt(0) !== 35
457
+ )
458
+ )
459
+ const parse_old_map = data => (
460
+ Object.fromEntries(
461
+ data
462
+ .split('\n')
463
+ .filter(entry =>
464
+ entry.length > 0 &&
465
+ entry.charCodeAt(0) !== 35
466
+ )
467
+ .map(entry => entry.split(':'))
468
+ )
469
+ )
470
+
372
471
  let log_history = rtjscomp.log_history = [];
373
472
  actions.log_clear = () => {
374
473
  log_history = rtjscomp.log_history = [];
@@ -429,8 +528,12 @@ const request_handle = async (request, response, https) => {
429
528
  path.includes('//')
430
529
  ) throw 404;
431
530
  if (path.length > 1 && path.endsWith('/')) {
432
- response.setHeader('Location', path.slice(0, -1));
433
- throw 301;
531
+ path = path.slice(0, -1);
532
+ // is file with extension?
533
+ if (path.lastIndexOf('/') < path.lastIndexOf('.')) {
534
+ response.setHeader('Location', path);
535
+ throw 301;
536
+ }
434
537
  }
435
538
  path = path.slice(1);
436
539
 
@@ -438,7 +541,7 @@ const request_handle = async (request, response, https) => {
438
541
  if (
439
542
  path.includes('php') ||
440
543
  path.includes('sql') ||
441
- file_blocks.has(path)
544
+ path_ghosts.has(path)
442
545
  ) return;
443
546
 
444
547
  response.setHeader('Server', 'l3p3 rtjscomp v' + VERSION);
@@ -496,13 +599,13 @@ const request_handle = async (request, response, https) => {
496
599
 
497
600
  let file_gz_enabled = (
498
601
  'accept-encoding' in request_headers &&
499
- !file_type_nocompress.has(file_type) &&
602
+ !type_raws.has(file_type) &&
500
603
  request_headers['accept-encoding'].split(HTTP_LIST_REG).includes('gzip')
501
604
  );
502
605
 
503
606
  const file_dyn_enabled = (
504
- file_type_dyns.has(file_type) &&
505
- !file_raws.has(path)
607
+ type_dynamics.has(file_type) &&
608
+ !path_statics.has(path)
506
609
  );
507
610
 
508
611
  if (request_method !== 'GET') {
@@ -539,7 +642,7 @@ const request_handle = async (request, response, https) => {
539
642
  }ic file: ${path_real}`);
540
643
 
541
644
  if (
542
- file_privates.has(path) ||
645
+ path_hiddens.has(path) ||
543
646
  path.endsWith('.service.js')
544
647
  ) throw 403;
545
648
  if (!fs.existsSync(path_real)) throw 404;
@@ -662,7 +765,7 @@ const request_handle = async (request, response, https) => {
662
765
  response.statusCode = 200;
663
766
  response.setHeader(
664
767
  'Content-Type',
665
- file_type_mimes.get(file_type) || file_type_mimes.get('txt')
768
+ type_mimes.get(file_type) || type_mimes.get('txt')
666
769
  );
667
770
 
668
771
  if (file_dyn_enabled) { // dynamic file
@@ -848,7 +951,10 @@ const request_handle = async (request, response, https) => {
848
951
  err = 500;
849
952
  }
850
953
 
851
- if (err >= 400 && log_verbose) {
954
+ if (
955
+ err >= 500 ||
956
+ log_verbose && err >= 400
957
+ ) {
852
958
  log(`[error] request failed: ${err}; ${request_ip}; ${request.url}`);
853
959
  }
854
960
 
@@ -862,20 +968,21 @@ const request_handle = async (request, response, https) => {
862
968
 
863
969
  let exiting = false;
864
970
  actions.halt = async () => {
971
+ if (log_verbose) log('stop all');
865
972
  await Promise.all([
866
973
  actions.http_stop && actions.http_stop(),
867
974
  actions.https_stop && actions.https_stop(),
868
975
  services_shutdown(),
869
976
  ]);
870
977
  await actions.spam_save();
871
- log('stopped everything');
978
+ log('stopped all');
872
979
  }
873
980
  actions.exit = async status => {
874
981
  if (exiting) return;
875
982
  exiting = true;
876
983
  if (typeof status !== 'number') status = 0;
877
984
  await actions.halt();
878
- log('exiting...');
985
+ if (log_verbose) log('exit');
879
986
  process.exit(status);
880
987
  }
881
988
 
@@ -898,25 +1005,43 @@ process.on('SIGINT', actions.exit);
898
1005
  process.on('SIGUSR2', actions.exit);
899
1006
  process.on('SIGTERM', actions.exit);
900
1007
 
901
- log(`rtjscomp v${VERSION} in ${typeof Bun === 'undefined' ? 'node' : 'bun'} on ${process.platform}`);
1008
+ log(`rtjscomp v${
1009
+ VERSION
1010
+ } in ${
1011
+ IS_BUN ? 'bun' : 'node'
1012
+ } on ${
1013
+ process.platform
1014
+ }`);
1015
+
1016
+ await file_keep_new(PATH_CONFIG + 'init.js', async data => {
1017
+ if (!data) return;
1018
+ log('[deprecated] run global init script');
1019
+ try {
1020
+ await (
1021
+ new AsyncFunction('require', data)
1022
+ )(custom_require);
1023
+ }
1024
+ catch (err) {
1025
+ log('[error] init.js: ' + err.message);
1026
+ }
1027
+ });
902
1028
 
903
1029
  // initial
904
- await Promise.all([
905
- fsp.stat(PATH_CONFIG).catch(_ => null),
906
- fsp.stat(PATH_DATA).catch(_ => null),
907
- fsp.stat(PATH_PUBLIC).catch(_ => null),
908
- ]).then(([stat_config, stat_data, stat_public]) => {
909
- if (!stat_config) {
910
- log('create config template directory');
911
- fs.mkdirSync(PATH_CONFIG);
912
- fs.mkdirSync(PATH_CONFIG + 'ssl');
913
- for (const file of 'file_type_dyns,file_type_mimes,file_type_nocompress,path_aliases,port_http,port_https,services'.split(',')) {
914
- fs.copyFileSync(
915
- __dirname + '/' + PATH_CONFIG + file + '.txt',
916
- PATH_CONFIG + file + '.txt'
917
- );
918
- }
919
- }
1030
+ {
1031
+ const [
1032
+ stat_data,
1033
+ stat_public,
1034
+ ...files_config
1035
+ ] = await Promise.all([
1036
+ fsp.stat(PATH_DATA).catch(_ => null),
1037
+ fsp.stat(PATH_PUBLIC).catch(_ => null),
1038
+ ...(
1039
+ 'file_blocks,file_privates,file_raws,file_type_dyns,file_type_mimes,file_type_nocompress,path_aliases,port_http,port_https,services'
1040
+ .split(',')
1041
+ .map(name => fsp.readFile(PATH_CONFIG + name + '.txt', 'utf8').catch(_ => null))
1042
+ ),
1043
+ ]);
1044
+
920
1045
  if (!stat_data) {
921
1046
  fs.mkdirSync(PATH_DATA);
922
1047
  }
@@ -927,79 +1052,74 @@ await Promise.all([
927
1052
  {recursive: true}
928
1053
  );
929
1054
  }
930
- });
931
-
932
- file_keep_new(PATH_CONFIG + 'services.txt', data => (
933
- map_generate_bol(services, data),
934
- services_list_react()
935
- ));
936
-
937
- await Promise.all([
938
- file_keep_new(PATH_CONFIG + 'init.js', async data => {
939
- if (!data) return;
940
- log('[deprecated] run global init script');
941
- try {
942
- await (
943
- new AsyncFunction('require', data)
944
- )(custom_require);
1055
+ if (
1056
+ files_config.some(file => file !== null)
1057
+ ) {
1058
+ const json = JSON.parse(
1059
+ await fsp.readFile('rtjscomp.json', 'utf8').catch(_ => null)
1060
+ ) || {};
1061
+
1062
+ const [
1063
+ old_file_blocks,
1064
+ old_file_privates,
1065
+ old_file_raws,
1066
+ old_file_type_dyns,
1067
+ old_file_type_mimes,
1068
+ old_file_type_nocompress,
1069
+ old_path_aliases,
1070
+ old_port_http,
1071
+ old_port_https,
1072
+ old_services,
1073
+ ] = files_config;
1074
+
1075
+ if (old_file_blocks) {
1076
+ json['path_ghosts'] = parse_old_list(old_file_blocks);
945
1077
  }
946
- catch (err) {
947
- log('[error] init.js: ' + err.message);
1078
+ if (old_file_privates) {
1079
+ json['path_hiddens'] = parse_old_list(old_file_privates);
948
1080
  }
949
- }),
950
- file_keep_new(PATH_CONFIG + 'file_type_mimes.txt', data => {
951
- map_generate_equ(file_type_mimes, data);
952
- if (!file_type_mimes.has('txt')) {
953
- file_type_mimes.set('txt', 'text/plain; charset=utf-8');
1081
+ if (old_file_raws) {
1082
+ json['path_statics'] = parse_old_list(old_file_raws);
954
1083
  }
955
- }),
956
- file_keep_new(PATH_CONFIG + 'path_aliases.txt', data => {
957
- map_generate_equ(path_aliases, data);
958
- path_aliases_reverse.clear();
959
- path_aliases_templates.clear();
960
- for (const [key, value] of path_aliases.entries()) {
961
- if (key.includes('*')) {
962
- path_aliases.delete(key);
963
- const path_split = key.split('/');
964
- const first = path_split.shift();
965
- if (path_aliases_templates.has(first)) {
966
- path_aliases_templates.get(first).push(
967
- {path_split, value}
968
- );
969
- }
970
- else {
971
- path_aliases_templates.set(first, [
972
- {path_split, value},
973
- ]);
974
- }
1084
+ if (old_file_type_dyns) {
1085
+ json['type_dynamics'] = parse_old_list(old_file_type_dyns);
1086
+ }
1087
+ if (old_file_type_mimes) {
1088
+ json['type_mimes'] = parse_old_map(old_file_type_mimes);
1089
+ }
1090
+ if (old_file_type_nocompress) {
1091
+ json['type_raws'] = parse_old_list(old_file_type_nocompress);
1092
+ }
1093
+ if (old_path_aliases) {
1094
+ json['path_aliases'] = parse_old_map(old_path_aliases);
1095
+ }
1096
+ if (old_port_http) {
1097
+ const number = parseInt(old_port_http);
1098
+ if (!isNaN(number)) {
1099
+ json['port_http'] = number;
975
1100
  }
976
- else {
977
- path_aliases_reverse.set(value, '/' + key);
1101
+ }
1102
+ if (old_port_https) {
1103
+ const number = parseInt(old_port_https);
1104
+ if (!isNaN(number)) {
1105
+ json['port_https'] = number;
978
1106
  }
979
1107
  }
980
- }),
981
- file_keep_new(PATH_CONFIG + 'file_type_dyns.txt', data => {
982
- map_generate_bol(file_type_dyns, data);
983
- }),
984
- file_keep_new(PATH_CONFIG + 'file_type_nocompress.txt', data => {
985
- map_generate_bol(file_type_nocompress, data);
986
- }),
987
- file_keep_new(PATH_CONFIG + 'file_raws.txt', data => {
988
- if (!data) return;
989
- map_generate_bol(file_raws, data);
990
- }),
991
- file_keep_new(PATH_CONFIG + 'file_privates.txt', data => {
992
- if (!data) return;
993
- map_generate_bol(file_privates, data);
994
- }),
995
- file_keep_new(PATH_CONFIG + 'file_blocks.txt', data => {
996
- if (!data) return;
997
- map_generate_bol(file_blocks, data);
998
- }),
999
- ]);
1108
+ if (old_services) {
1109
+ json['services'] = old_services.split('\n').filter(path => path.length > 0);
1110
+ }
1111
+
1112
+ await fsp.writeFile(
1113
+ 'rtjscomp.json',
1114
+ JSON.stringify(json, null, '\t') + '\n',
1115
+ 'utf8'
1116
+ );
1117
+ log('[deprecated] config files found, rtjscomp.json written, please delete config files');
1118
+ }
1119
+ }
1000
1120
 
1121
+ // http(s)
1001
1122
  let connections_count = 0;
1002
- let http_status = false;
1003
1123
  let http_status_target = false;
1004
1124
  let http_listened_resolve = null;
1005
1125
  const http_connections = new Map;
@@ -1020,14 +1140,12 @@ server_http.on('connection', connection => {
1020
1140
 
1021
1141
  actions.http_start = async () => {
1022
1142
  await actions.http_stop();
1143
+ if (!port_http) return;
1023
1144
  http_status_target = true;
1024
1145
  log('start http: http://localhost:' + port_http);
1025
1146
  await new Promise(resolve => server_http.listen(port_http, http_listened_resolve = resolve));
1026
1147
  if (http_listened_resolve) http_listened_resolve = null;
1027
- else{
1028
- http_status = true;
1029
- if (log_verbose) log('started http');
1030
- }
1148
+ else if (log_verbose) log('started http');
1031
1149
  }
1032
1150
  actions.http_stop = async () => {
1033
1151
  if (!http_status_target) return;
@@ -1036,7 +1154,6 @@ actions.http_stop = async () => {
1036
1154
  const kill_timeout = setTimeout(actions.http_kill, 5e3);
1037
1155
  await new Promise(resolve => server_http.close(resolve));
1038
1156
  clearTimeout(kill_timeout);
1039
- http_status = false;
1040
1157
  if (log_verbose) log('stopped http');
1041
1158
  }
1042
1159
  actions.http_kill = async () => {
@@ -1050,24 +1167,9 @@ actions.http_kill = async () => {
1050
1167
  http_connections.clear();
1051
1168
  }
1052
1169
 
1053
- file_keep_new(PATH_CONFIG + 'port_http.txt', data => {
1054
- if (
1055
- !data ||
1056
- isNaN(data = Number(data)) ||
1057
- !number_check_uint(data)
1058
- ) {
1059
- log('[error] http: invalid port number');
1060
- }
1061
- else if (data !== port_http) {
1062
- port_http = data;
1063
- actions.http_start();
1064
- }
1065
- });
1066
-
1067
1170
  try {
1068
1171
  const https_key = fs.readFileSync(PATH_CONFIG + 'ssl/domain.key');
1069
1172
  const https_cert = fs.readFileSync(PATH_CONFIG + 'ssl/chained.pem');
1070
- let https_status = false;
1071
1173
  let https_status_target = false;
1072
1174
  let https_listened_resolve = null;
1073
1175
  const https_connections = new Map;
@@ -1089,14 +1191,12 @@ try {
1089
1191
 
1090
1192
  actions.https_start = async () => {
1091
1193
  await actions.https_stop();
1194
+ if (!port_https) return;
1092
1195
  https_status_target = true;
1093
1196
  log('start https: https://localhost:' + port_https);
1094
1197
  await new Promise(resolve => server_https.listen(port_https, https_listened_resolve = resolve));
1095
1198
  if (https_listened_resolve) https_listened_resolve = null;
1096
- else{
1097
- https_status = true;
1098
- if (log_verbose) log('started https');
1099
- }
1199
+ else if (log_verbose) log('started https');
1100
1200
  }
1101
1201
  actions.https_stop = async () => {
1102
1202
  if (!https_status_target) return;
@@ -1105,7 +1205,6 @@ try {
1105
1205
  const kill_timeout = setTimeout(actions.https_kill, 5000);
1106
1206
  await new Promise(resolve => server_https.close(resolve));
1107
1207
  clearTimeout(kill_timeout);
1108
- https_status = false;
1109
1208
  if (log_verbose) log('stopped https');
1110
1209
  }
1111
1210
  actions.https_kill = async () => {
@@ -1117,23 +1216,136 @@ try {
1117
1216
  if (log_verbose) log('killed https');
1118
1217
  https_connections.clear();
1119
1218
  }
1120
-
1121
- file_keep_new(PATH_CONFIG + 'port_https.txt', data => {
1122
- if (
1123
- !data ||
1124
- isNaN(data = Number(data)) ||
1125
- !number_check_uint(data)
1126
- ) {
1127
- log('[error] https: invalid port number');
1128
- }
1129
- else if (data !== port_https) {
1130
- port_https = data;
1131
- actions.https_start();
1132
- }
1133
- });
1134
1219
  }
1135
1220
  catch (err) {
1136
1221
  if (log_verbose) log('https: no cert, disabled');
1137
1222
  }
1138
1223
 
1224
+ // config
1225
+ await file_keep_new('rtjscomp.json', data => {
1226
+ try {
1227
+ data = JSON.parse(data);
1228
+ if (typeof data !== 'object') {
1229
+ throw 'must contain {}';
1230
+ }
1231
+
1232
+ const path_aliases_new = get_prop_map(data, 'path_aliases');
1233
+ const path_ghosts_new = get_prop_list(data, 'path_ghosts');
1234
+ const path_hiddens_new = get_prop_list(data, 'path_hiddens');
1235
+ const path_statics_new = get_prop_list(data, 'path_statics');
1236
+ const port_http_new = get_prop_uint(data, 'port_http', 8080);
1237
+ const port_https_new = get_prop_uint(data, 'port_https', 0);
1238
+ const services_new = get_prop_list(data, 'services') || [];
1239
+ const type_dynamics_new = get_prop_list(data, 'type_dynamics');
1240
+ const type_mimes_new = get_prop_map(data, 'type_mimes');
1241
+ const type_raws_new = get_prop_list(data, 'type_raws');
1242
+
1243
+ if (data) {
1244
+ const keys_left = Object.keys(data);
1245
+ if (keys_left.length > 0) {
1246
+ throw 'unknown: ' + keys_left.join(', ');
1247
+ }
1248
+ }
1249
+
1250
+ if (path_ghosts_new) {
1251
+ path_ghosts.clear();
1252
+ for (const key of path_ghosts_new) {
1253
+ path_ghosts.add(key);
1254
+ }
1255
+ }
1256
+ if (path_hiddens_new) {
1257
+ path_hiddens.clear();
1258
+ for (const key of path_hiddens_new) {
1259
+ path_hiddens.add(key);
1260
+ }
1261
+ }
1262
+ if (path_statics_new) {
1263
+ path_statics.clear();
1264
+ for (const key of path_statics_new) {
1265
+ path_statics.add(key);
1266
+ }
1267
+ }
1268
+ if (path_aliases_new) {
1269
+ path_aliases.clear();
1270
+ path_aliases_reverse.clear();
1271
+ path_aliases_templates.clear();
1272
+ for (const [key, value] of path_aliases_new) {
1273
+ if (key.includes('*')) {
1274
+ const path_split = key.split('/');
1275
+ const first = path_split.shift();
1276
+ if (path_aliases_templates.has(first)) {
1277
+ path_aliases_templates.get(first).push(
1278
+ {path_split, value}
1279
+ );
1280
+ }
1281
+ else {
1282
+ path_aliases_templates.set(first, [
1283
+ {path_split, value},
1284
+ ]);
1285
+ }
1286
+ }
1287
+ else {
1288
+ path_aliases.set(key, value);
1289
+ const existing = path_aliases_reverse.get(value);
1290
+ if (
1291
+ existing == null ||
1292
+ existing.length - 1 > value.length
1293
+ ) {
1294
+ path_aliases_reverse.set(value, '/' + key);
1295
+ }
1296
+ }
1297
+ }
1298
+ }
1299
+ if (type_dynamics_new) {
1300
+ type_dynamics.clear();
1301
+ for (const key of type_dynamics_new) {
1302
+ type_dynamics.add(key);
1303
+ }
1304
+ }
1305
+ if (type_mimes_new) {
1306
+ for (const [key, value] of type_mimes_new) {
1307
+ type_mimes.set(key, value);
1308
+ }
1309
+ }
1310
+ if (type_raws_new) {
1311
+ type_raws.clear();
1312
+ for (const key of type_raws_new) {
1313
+ type_raws.add(key);
1314
+ }
1315
+ }
1316
+
1317
+ const promises = [
1318
+ services_list_react(services_new),
1319
+ ];
1320
+
1321
+ if (port_http_new !== port_http) {
1322
+ port_http = port_http_new;
1323
+ promises.push(
1324
+ port_http_new > 0
1325
+ ? actions.http_start()
1326
+ : actions.http_stop()
1327
+ );
1328
+ }
1329
+
1330
+ if (
1331
+ actions.https_stop != null &&
1332
+ port_https_new !== port_https
1333
+ ) {
1334
+ port_https = port_https_new;
1335
+ promises.push(
1336
+ port_https_new > 0
1337
+ ? actions.https_start()
1338
+ : actions.https_stop()
1339
+ );
1340
+ }
1341
+
1342
+ return Promise.all(promises);
1343
+ }
1344
+ catch (err) {
1345
+ log('[error] rtjscomp.json: ' + (err.message || err));
1346
+ }
1347
+ });
1348
+
1349
+ if (log_verbose) log('started all');
1350
+
1139
1351
  })();
@@ -1,58 +0,0 @@
1
- add compression of dynamic data
2
- add https
3
- add actions
4
- 0.5
5
- add shutdown action
6
- prefer gzip over lzma
7
- move public files into separate dir
8
- 0.51
9
- add list of raw files
10
- 0.52
11
- ignore empty areas (<??> or ?><?)
12
- 0.53
13
- move configuration data to separate files
14
- 0.54
15
- add path aliases
16
- block accessing directories
17
- 0.55
18
- add http-header Content-Location
19
- 0.56
20
- add list of file types not to compress
21
- keep raw files in ram
22
- remove lzma
23
- add compression of raw files
24
- load already compressed files
25
- just compress when at least 100 bytes of data
26
- 0.57
27
- cleanup code
28
- execute and send file in parallel
29
- add services
30
- 0.58
31
- add analytic logger
32
- use compressed file even if file type is not intended as being compressed
33
- 0.59
34
- remove static files from cache when precompressed files got changed
35
- 0.591
36
- set gzip level to 9
37
- save automatically compressed files
38
- 0.592
39
- block requests with invalid hostname
40
- 0.593
41
- add user data storage
42
- 0.594
43
- remove custom file init
44
- asynchronize request handling
45
- temporarily disable chaching of static files
46
- 0.595
47
- send partial files
48
- load and interpret file in parallel
49
- optimize source codes of sent files
50
- 0.596
51
- stop services before replacing them
52
- service_require return service object
53
- 0.597
54
- stop services on exit
55
- 0.598
56
- read request body
57
- 0.599
58
- ???
@@ -1,7 +0,0 @@
1
- # put file types here that should be compiled
2
- # <? ... ?> will only be executed in those!
3
-
4
- html
5
- txt
6
- json
7
- events
@@ -1,21 +0,0 @@
1
- html:text/html; charset=utf-8
2
- txt:text/plain; charset=utf-8
3
- xml:application/xml; charset=utf-8
4
- rss:application/rss+xml; charset=utf-8
5
- js:text/javascript; charset=utf-8
6
- json:application/json; charset=utf-8
7
- hta:application/hta
8
- css:text/css; charset=utf-8
9
- ico:image/x-icon
10
- jpg:image/jpeg
11
- png:image/png
12
- bpg:image/bpg
13
- zip:application/zip
14
- apk:application/zip
15
- gz:application/gzip
16
- xz:application/x-xz
17
- mp3:audio/mpeg3
18
- mid:audio/midi
19
- flac:audio/flac
20
- pdf:application/pdf
21
- events:text/event-stream
@@ -1,8 +0,0 @@
1
- # these file types should not be sent compressed
2
-
3
- png
4
- jpg
5
- pdf
6
- zip
7
- gz
8
- xz
@@ -1,5 +0,0 @@
1
- # the part before the : will be replaced by the part after it
2
- # insert * to have parameters extracted from the path
3
-
4
- :index.html
5
- zahl/*zahl:index.html
@@ -1 +0,0 @@
1
- 8080
File without changes
@@ -1,2 +0,0 @@
1
- # list paths to service.js files in your public directory
2
- # example "counter" to enable public/counter.service.js