rtjscomp 0.8.7 → 0.9.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/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.7",
3
+ "version": "0.9.0",
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 = [];
@@ -438,7 +537,7 @@ const request_handle = async (request, response, https) => {
438
537
  if (
439
538
  path.includes('php') ||
440
539
  path.includes('sql') ||
441
- file_blocks.has(path)
540
+ path_ghosts.has(path)
442
541
  ) return;
443
542
 
444
543
  response.setHeader('Server', 'l3p3 rtjscomp v' + VERSION);
@@ -496,13 +595,13 @@ const request_handle = async (request, response, https) => {
496
595
 
497
596
  let file_gz_enabled = (
498
597
  'accept-encoding' in request_headers &&
499
- !file_type_nocompress.has(file_type) &&
598
+ !type_raws.has(file_type) &&
500
599
  request_headers['accept-encoding'].split(HTTP_LIST_REG).includes('gzip')
501
600
  );
502
601
 
503
602
  const file_dyn_enabled = (
504
- file_type_dyns.has(file_type) &&
505
- !file_raws.has(path)
603
+ type_dynamics.has(file_type) &&
604
+ !path_statics.has(path)
506
605
  );
507
606
 
508
607
  if (request_method !== 'GET') {
@@ -539,7 +638,7 @@ const request_handle = async (request, response, https) => {
539
638
  }ic file: ${path_real}`);
540
639
 
541
640
  if (
542
- file_privates.has(path) ||
641
+ path_hiddens.has(path) ||
543
642
  path.endsWith('.service.js')
544
643
  ) throw 403;
545
644
  if (!fs.existsSync(path_real)) throw 404;
@@ -662,7 +761,7 @@ const request_handle = async (request, response, https) => {
662
761
  response.statusCode = 200;
663
762
  response.setHeader(
664
763
  'Content-Type',
665
- file_type_mimes.get(file_type) || file_type_mimes.get('txt')
764
+ type_mimes.get(file_type) || type_mimes.get('txt')
666
765
  );
667
766
 
668
767
  if (file_dyn_enabled) { // dynamic file
@@ -848,7 +947,10 @@ const request_handle = async (request, response, https) => {
848
947
  err = 500;
849
948
  }
850
949
 
851
- if (err >= 400 && log_verbose) {
950
+ if (
951
+ err >= 500 ||
952
+ log_verbose && err >= 400
953
+ ) {
852
954
  log(`[error] request failed: ${err}; ${request_ip}; ${request.url}`);
853
955
  }
854
956
 
@@ -862,20 +964,21 @@ const request_handle = async (request, response, https) => {
862
964
 
863
965
  let exiting = false;
864
966
  actions.halt = async () => {
967
+ if (log_verbose) log('stop all');
865
968
  await Promise.all([
866
969
  actions.http_stop && actions.http_stop(),
867
970
  actions.https_stop && actions.https_stop(),
868
971
  services_shutdown(),
869
972
  ]);
870
973
  await actions.spam_save();
871
- log('stopped everything');
974
+ log('stopped all');
872
975
  }
873
976
  actions.exit = async status => {
874
977
  if (exiting) return;
875
978
  exiting = true;
876
979
  if (typeof status !== 'number') status = 0;
877
980
  await actions.halt();
878
- log('exiting...');
981
+ if (log_verbose) log('exit');
879
982
  process.exit(status);
880
983
  }
881
984
 
@@ -898,25 +1001,43 @@ process.on('SIGINT', actions.exit);
898
1001
  process.on('SIGUSR2', actions.exit);
899
1002
  process.on('SIGTERM', actions.exit);
900
1003
 
901
- log(`rtjscomp v${VERSION} in ${typeof Bun === 'undefined' ? 'node' : 'bun'} on ${process.platform}`);
1004
+ log(`rtjscomp v${
1005
+ VERSION
1006
+ } in ${
1007
+ IS_BUN ? 'bun' : 'node'
1008
+ } on ${
1009
+ process.platform
1010
+ }`);
1011
+
1012
+ await file_keep_new(PATH_CONFIG + 'init.js', async data => {
1013
+ if (!data) return;
1014
+ log('[deprecated] run global init script');
1015
+ try {
1016
+ await (
1017
+ new AsyncFunction('require', data)
1018
+ )(custom_require);
1019
+ }
1020
+ catch (err) {
1021
+ log('[error] init.js: ' + err.message);
1022
+ }
1023
+ });
902
1024
 
903
1025
  // 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
- }
1026
+ {
1027
+ const [
1028
+ stat_data,
1029
+ stat_public,
1030
+ ...files_config
1031
+ ] = await Promise.all([
1032
+ fsp.stat(PATH_DATA).catch(_ => null),
1033
+ fsp.stat(PATH_PUBLIC).catch(_ => null),
1034
+ ...(
1035
+ 'file_blocks,file_privates,file_raws,file_type_dyns,file_type_mimes,file_type_nocompress,path_aliases,port_http,port_https,services'
1036
+ .split(',')
1037
+ .map(name => fsp.readFile(PATH_CONFIG + name + '.txt', 'utf8').catch(_ => null))
1038
+ ),
1039
+ ]);
1040
+
920
1041
  if (!stat_data) {
921
1042
  fs.mkdirSync(PATH_DATA);
922
1043
  }
@@ -927,79 +1048,74 @@ await Promise.all([
927
1048
  {recursive: true}
928
1049
  );
929
1050
  }
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);
1051
+ if (
1052
+ files_config.some(file => file !== null)
1053
+ ) {
1054
+ const json = JSON.parse(
1055
+ await fsp.readFile('rtjscomp.json', 'utf8').catch(_ => null)
1056
+ ) || {};
1057
+
1058
+ const [
1059
+ old_file_blocks,
1060
+ old_file_privates,
1061
+ old_file_raws,
1062
+ old_file_type_dyns,
1063
+ old_file_type_mimes,
1064
+ old_file_type_nocompress,
1065
+ old_path_aliases,
1066
+ old_port_http,
1067
+ old_port_https,
1068
+ old_services,
1069
+ ] = files_config;
1070
+
1071
+ if (old_file_blocks) {
1072
+ json['path_ghosts'] = parse_old_list(old_file_blocks);
945
1073
  }
946
- catch (err) {
947
- log('[error] init.js: ' + err.message);
1074
+ if (old_file_privates) {
1075
+ json['path_hiddens'] = parse_old_list(old_file_privates);
948
1076
  }
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');
1077
+ if (old_file_raws) {
1078
+ json['path_statics'] = parse_old_list(old_file_raws);
954
1079
  }
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
- }
1080
+ if (old_file_type_dyns) {
1081
+ json['type_dynamics'] = parse_old_list(old_file_type_dyns);
1082
+ }
1083
+ if (old_file_type_mimes) {
1084
+ json['type_mimes'] = parse_old_map(old_file_type_mimes);
1085
+ }
1086
+ if (old_file_type_nocompress) {
1087
+ json['type_raws'] = parse_old_list(old_file_type_nocompress);
1088
+ }
1089
+ if (old_path_aliases) {
1090
+ json['path_aliases'] = parse_old_map(old_path_aliases);
1091
+ }
1092
+ if (old_port_http) {
1093
+ const number = parseInt(old_port_http);
1094
+ if (!isNaN(number)) {
1095
+ json['port_http'] = number;
975
1096
  }
976
- else {
977
- path_aliases_reverse.set(value, key);
1097
+ }
1098
+ if (old_port_https) {
1099
+ const number = parseInt(old_port_https);
1100
+ if (!isNaN(number)) {
1101
+ json['port_https'] = number;
978
1102
  }
979
1103
  }
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
- ]);
1104
+ if (old_services) {
1105
+ json['services'] = old_services.split('\n').filter(path => path.length > 0);
1106
+ }
1000
1107
 
1108
+ await fsp.writeFile(
1109
+ 'rtjscomp.json',
1110
+ JSON.stringify(json, null, 2),
1111
+ 'utf8'
1112
+ );
1113
+ log('[deprecated] config files found, rtjscomp.json written, please delete config files');
1114
+ }
1115
+ }
1116
+
1117
+ // http(s)
1001
1118
  let connections_count = 0;
1002
- let http_status = false;
1003
1119
  let http_status_target = false;
1004
1120
  let http_listened_resolve = null;
1005
1121
  const http_connections = new Map;
@@ -1020,14 +1136,12 @@ server_http.on('connection', connection => {
1020
1136
 
1021
1137
  actions.http_start = async () => {
1022
1138
  await actions.http_stop();
1139
+ if (!port_http) return;
1023
1140
  http_status_target = true;
1024
1141
  log('start http: http://localhost:' + port_http);
1025
1142
  await new Promise(resolve => server_http.listen(port_http, http_listened_resolve = resolve));
1026
1143
  if (http_listened_resolve) http_listened_resolve = null;
1027
- else{
1028
- http_status = true;
1029
- if (log_verbose) log('started http');
1030
- }
1144
+ else if (log_verbose) log('started http');
1031
1145
  }
1032
1146
  actions.http_stop = async () => {
1033
1147
  if (!http_status_target) return;
@@ -1036,7 +1150,6 @@ actions.http_stop = async () => {
1036
1150
  const kill_timeout = setTimeout(actions.http_kill, 5e3);
1037
1151
  await new Promise(resolve => server_http.close(resolve));
1038
1152
  clearTimeout(kill_timeout);
1039
- http_status = false;
1040
1153
  if (log_verbose) log('stopped http');
1041
1154
  }
1042
1155
  actions.http_kill = async () => {
@@ -1050,24 +1163,9 @@ actions.http_kill = async () => {
1050
1163
  http_connections.clear();
1051
1164
  }
1052
1165
 
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
1166
  try {
1068
1167
  const https_key = fs.readFileSync(PATH_CONFIG + 'ssl/domain.key');
1069
1168
  const https_cert = fs.readFileSync(PATH_CONFIG + 'ssl/chained.pem');
1070
- let https_status = false;
1071
1169
  let https_status_target = false;
1072
1170
  let https_listened_resolve = null;
1073
1171
  const https_connections = new Map;
@@ -1089,14 +1187,12 @@ try {
1089
1187
 
1090
1188
  actions.https_start = async () => {
1091
1189
  await actions.https_stop();
1190
+ if (!port_https) return;
1092
1191
  https_status_target = true;
1093
1192
  log('start https: https://localhost:' + port_https);
1094
1193
  await new Promise(resolve => server_https.listen(port_https, https_listened_resolve = resolve));
1095
1194
  if (https_listened_resolve) https_listened_resolve = null;
1096
- else{
1097
- https_status = true;
1098
- if (log_verbose) log('started https');
1099
- }
1195
+ else if (log_verbose) log('started https');
1100
1196
  }
1101
1197
  actions.https_stop = async () => {
1102
1198
  if (!https_status_target) return;
@@ -1105,7 +1201,6 @@ try {
1105
1201
  const kill_timeout = setTimeout(actions.https_kill, 5000);
1106
1202
  await new Promise(resolve => server_https.close(resolve));
1107
1203
  clearTimeout(kill_timeout);
1108
- https_status = false;
1109
1204
  if (log_verbose) log('stopped https');
1110
1205
  }
1111
1206
  actions.https_kill = async () => {
@@ -1117,23 +1212,130 @@ try {
1117
1212
  if (log_verbose) log('killed https');
1118
1213
  https_connections.clear();
1119
1214
  }
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
1215
  }
1135
1216
  catch (err) {
1136
1217
  if (log_verbose) log('https: no cert, disabled');
1137
1218
  }
1138
1219
 
1220
+ // config
1221
+ await file_keep_new('rtjscomp.json', data => {
1222
+ try {
1223
+ data = JSON.parse(data);
1224
+ if (typeof data !== 'object') {
1225
+ throw 'must contain {}';
1226
+ }
1227
+
1228
+ const path_aliases_new = get_prop_map(data, 'path_aliases');
1229
+ const path_ghosts_new = get_prop_list(data, 'path_ghosts');
1230
+ const path_hiddens_new = get_prop_list(data, 'path_hiddens');
1231
+ const path_statics_new = get_prop_list(data, 'path_statics');
1232
+ const port_http_new = get_prop_uint(data, 'port_http', 8080);
1233
+ const port_https_new = get_prop_uint(data, 'port_https', 0);
1234
+ const services_new = get_prop_list(data, 'services') || [];
1235
+ const type_dynamics_new = get_prop_list(data, 'type_dynamics');
1236
+ const type_mimes_new = get_prop_map(data, 'type_mimes');
1237
+ const type_raws_new = get_prop_list(data, 'type_raws');
1238
+
1239
+ if (data) {
1240
+ const keys_left = Object.keys(data);
1241
+ if (keys_left.length > 0) {
1242
+ throw 'unknown: ' + keys_left.join(', ');
1243
+ }
1244
+ }
1245
+
1246
+ if (path_ghosts_new) {
1247
+ path_ghosts.clear();
1248
+ for (const key of path_ghosts_new) {
1249
+ path_ghosts.add(key);
1250
+ }
1251
+ }
1252
+ if (path_hiddens_new) {
1253
+ path_hiddens.clear();
1254
+ for (const key of path_hiddens_new) {
1255
+ path_hiddens.add(key);
1256
+ }
1257
+ }
1258
+ if (path_statics_new) {
1259
+ path_statics.clear();
1260
+ for (const key of path_statics_new) {
1261
+ path_statics.add(key);
1262
+ }
1263
+ }
1264
+ if (path_aliases_new) {
1265
+ path_aliases.clear();
1266
+ path_aliases_reverse.clear();
1267
+ path_aliases_templates.clear();
1268
+ for (const [key, value] of path_aliases_new) {
1269
+ if (key.includes('*')) {
1270
+ const path_split = key.split('/');
1271
+ const first = path_split.shift();
1272
+ if (path_aliases_templates.has(first)) {
1273
+ path_aliases_templates.get(first).push(
1274
+ {path_split, value}
1275
+ );
1276
+ }
1277
+ else {
1278
+ path_aliases_templates.set(first, [
1279
+ {path_split, value},
1280
+ ]);
1281
+ }
1282
+ }
1283
+ else {
1284
+ path_aliases.set(key, value);
1285
+ path_aliases_reverse.set(value, '/' + key);
1286
+ }
1287
+ }
1288
+ }
1289
+ if (type_dynamics_new) {
1290
+ type_dynamics.clear();
1291
+ for (const key of type_dynamics_new) {
1292
+ type_dynamics.add(key);
1293
+ }
1294
+ }
1295
+ if (type_mimes_new) {
1296
+ for (const [key, value] of type_mimes_new) {
1297
+ type_mimes.set(key, value);
1298
+ }
1299
+ }
1300
+ if (type_raws_new) {
1301
+ type_raws.clear();
1302
+ for (const key of type_raws_new) {
1303
+ type_raws.add(key);
1304
+ }
1305
+ }
1306
+
1307
+ const promises = [
1308
+ services_list_react(services_new),
1309
+ ];
1310
+
1311
+ if (port_http_new !== port_http) {
1312
+ port_http = port_http_new;
1313
+ promises.push(
1314
+ port_http_new > 0
1315
+ ? actions.http_start()
1316
+ : actions.http_stop()
1317
+ );
1318
+ }
1319
+
1320
+ if (
1321
+ actions.https_stop != null &&
1322
+ port_https_new !== port_https
1323
+ ) {
1324
+ port_https = port_https_new;
1325
+ promises.push(
1326
+ port_https_new > 0
1327
+ ? actions.https_start()
1328
+ : actions.https_stop()
1329
+ );
1330
+ }
1331
+
1332
+ return Promise.all(promises);
1333
+ }
1334
+ catch (err) {
1335
+ log('[error] rtjscomp.json: ' + (err.message || err));
1336
+ }
1337
+ });
1338
+
1339
+ if (log_verbose) log('started all');
1340
+
1139
1341
  })();
@@ -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