rtjscomp 0.8.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 +67 -0
- package/changes_before_git.txt +58 -0
- package/config/file_type_dyns.txt +7 -0
- package/config/file_type_mimes.txt +21 -0
- package/config/file_type_nocompress.txt +8 -0
- package/config/path_aliases.txt +5 -0
- package/config/port_http.txt +1 -0
- package/config/port_https.txt +0 -0
- package/config/services.txt +2 -0
- package/package.json +23 -0
- package/public/index.html +4 -0
- package/rtjscomp.js +1016 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# rtjscomp
|
|
2
|
+
|
|
3
|
+
easy to use http server that allows for using javascript just as php was used back in the days
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> as the issues indicate, a lot of breaking changes are ahead.
|
|
7
|
+
> make sure to check for these in future until version 1.0.0 is relessed.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
go into the directory where you want to have your project and run
|
|
12
|
+
|
|
13
|
+
```console
|
|
14
|
+
$ npx rtjscomp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
or in case you prefer [bun](https://bun.sh):
|
|
18
|
+
|
|
19
|
+
```console
|
|
20
|
+
$ bunx rtjscomp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
and now http://localhost:8080 offers a greeting!
|
|
24
|
+
|
|
25
|
+
for developing rtjscomp itself, clone this repository and run
|
|
26
|
+
|
|
27
|
+
```console
|
|
28
|
+
$ npm install
|
|
29
|
+
$ npm start
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Files
|
|
33
|
+
|
|
34
|
+
when run first time, it will be created with some defaults. all files in config/public are watched, no reload command needed.
|
|
35
|
+
|
|
36
|
+
### Directories
|
|
37
|
+
|
|
38
|
+
- config dir, has simple txt files (this will change soon!)
|
|
39
|
+
- data dir, services store their data here
|
|
40
|
+
- public dir, containing dynamic and static offerings and also services
|
|
41
|
+
|
|
42
|
+
### File types
|
|
43
|
+
|
|
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"
|
|
46
|
+
- .service.js files are executed and their `this` can be accessed by other files, they are not accessible to outside
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
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
|
+
request-independent services can be created using .service.js files referenced in services.txt.
|
|
52
|
+
in both file types (dynamic served and services), you can use all node/bun methods including `require`, but also those:
|
|
53
|
+
|
|
54
|
+
- `service_require(service path)`: returns the matching service object
|
|
55
|
+
- `service_require_try(service path)`: returns the matching service object or null if not found or if disabled
|
|
56
|
+
- `rtjscomp`: has these properties/methods:
|
|
57
|
+
- `actions`: an object with methods for server control (http[s]_[start|stop|restart|kill], log_clear, halt, exit)
|
|
58
|
+
- `async data_load(path)`: reads the file in data directory and returns its content or null
|
|
59
|
+
- `async data_load_watch(path, callback(content))`: executes callback first and on every change
|
|
60
|
+
- `async data_save(path, content)`: writes the content to the file in data directory
|
|
61
|
+
|
|
62
|
+
## Supported environments
|
|
63
|
+
|
|
64
|
+
- node 4.0.0 or higher
|
|
65
|
+
- bun
|
|
66
|
+
|
|
67
|
+
any os
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
???
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
8080
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rtjscomp",
|
|
3
|
+
"author": "L3P3 <dev@l3p3.de> (https://l3p3.de)",
|
|
4
|
+
"license": "Zlib",
|
|
5
|
+
"version": "0.8.1",
|
|
6
|
+
"description": "php-like server but with javascript",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"http",
|
|
9
|
+
"javascript",
|
|
10
|
+
"server"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"ipware": "latest",
|
|
14
|
+
"parse-multipart-data": "latest",
|
|
15
|
+
"querystring": "latest"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"start": "./rtjscomp.js"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"rtjscomp": "./rtjscomp.js"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/rtjscomp.js
ADDED
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
RTJSCOMP by L3P3, 2017-2025
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
(async () => {
|
|
9
|
+
|
|
10
|
+
const http = require('http');
|
|
11
|
+
const url = require('url');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const fsp = require('fs/promises');
|
|
14
|
+
const multipart_parse = require('parse-multipart-data').parse;
|
|
15
|
+
const zlib = require('zlib');
|
|
16
|
+
const request_ip_get = require('ipware')().get_ip;
|
|
17
|
+
const querystring_parse = require('querystring').decode;
|
|
18
|
+
const resolve_options = {paths: [require('path').resolve()]};
|
|
19
|
+
|
|
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 AGENT_CHECK_BOT = /bot|googlebot|crawler|spider|robot|crawling|favicon/i;
|
|
26
|
+
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;
|
|
27
|
+
const HTTP_LIST_REG = /,\s*/;
|
|
28
|
+
|
|
29
|
+
let port_http = 0;
|
|
30
|
+
let port_https = 0;
|
|
31
|
+
const file_type_mimes = new Map;
|
|
32
|
+
const file_type_dyns = new Set;
|
|
33
|
+
const file_type_nocompress = new Set;
|
|
34
|
+
/// forced static files
|
|
35
|
+
const file_raws = new Set;
|
|
36
|
+
/// hidden files
|
|
37
|
+
const file_privates = new Set;
|
|
38
|
+
/// files where requests should be totally ignored
|
|
39
|
+
const file_blocks = new Set;
|
|
40
|
+
/// any path -> file
|
|
41
|
+
const path_aliases = new Map;
|
|
42
|
+
const path_aliases_templates = new Map;
|
|
43
|
+
const services = new Set;
|
|
44
|
+
/// compiled file handlers
|
|
45
|
+
const file_cache_functions = new Map;
|
|
46
|
+
const actions = {};
|
|
47
|
+
const rtjscomp = global.rtjscomp = {
|
|
48
|
+
actions,
|
|
49
|
+
version: VERSION,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (!Object.fromEntries) {
|
|
53
|
+
Object.fromEntries = entries => {
|
|
54
|
+
const object = {};
|
|
55
|
+
for (const entry of entries) object[entry[0]] = entry[1];
|
|
56
|
+
return object;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// legacy, will be removed soon!
|
|
61
|
+
global.globals = rtjscomp;
|
|
62
|
+
global.actions = rtjscomp.actions;
|
|
63
|
+
global.data_load = name => {
|
|
64
|
+
log('[deprecated!] load: ' + name);
|
|
65
|
+
try {
|
|
66
|
+
return fs.readFileSync(PATH_DATA + name, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
global.data_save = (name, data) => (
|
|
73
|
+
log('[deprecated!] save: ' + name),
|
|
74
|
+
fs.writeFileSync(PATH_DATA + name, data, 'utf8')
|
|
75
|
+
)
|
|
76
|
+
global.number_check_int = number => (
|
|
77
|
+
Math.floor(number) === number
|
|
78
|
+
)
|
|
79
|
+
global.number_check_uint = number => (
|
|
80
|
+
number >= 0 && number_check_int(number)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
rtjscomp.data_load = async name => {
|
|
84
|
+
log('load: ' + name);
|
|
85
|
+
const data = await fsp.readFile(PATH_DATA + name, 'utf8').catch(() => null);
|
|
86
|
+
return name.endsWith('.json') ? JSON.parse(data || null) : data;
|
|
87
|
+
}
|
|
88
|
+
rtjscomp.data_load_watch = (name, callback) => (
|
|
89
|
+
file_keep_new(PATH_DATA + name, data => (
|
|
90
|
+
log('load: ' + name),
|
|
91
|
+
callback(
|
|
92
|
+
name.endsWith('.json')
|
|
93
|
+
? JSON.parse(data || null)
|
|
94
|
+
: data
|
|
95
|
+
)
|
|
96
|
+
))
|
|
97
|
+
)
|
|
98
|
+
rtjscomp.data_save = (name, data) => (
|
|
99
|
+
log('save: ' + name),
|
|
100
|
+
fsp.writeFile(
|
|
101
|
+
PATH_DATA + name,
|
|
102
|
+
name.endsWith('.json') ? JSON.stringify(data) : data,
|
|
103
|
+
'utf8'
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const custom_require_cache = new Map;
|
|
108
|
+
const custom_require = path => {
|
|
109
|
+
let result = custom_require_cache.get(path);
|
|
110
|
+
if (result != null) return result;
|
|
111
|
+
|
|
112
|
+
custom_require_cache.set(
|
|
113
|
+
path,
|
|
114
|
+
result = require(
|
|
115
|
+
require.resolve(path, resolve_options)
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const services_active = new Map;
|
|
122
|
+
const services_list_react = async () => {
|
|
123
|
+
await Promise.all(
|
|
124
|
+
Array.from(services_active.entries())
|
|
125
|
+
.filter(([path, _]) => !services.has(path))
|
|
126
|
+
.map(([_, service_object]) => service_stop(service_object, true))
|
|
127
|
+
);
|
|
128
|
+
for (const path of services) {
|
|
129
|
+
if (!services_active.has(path)) await service_start(path);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const service_start = async path => {
|
|
133
|
+
const service_object = {
|
|
134
|
+
path,
|
|
135
|
+
start: null,
|
|
136
|
+
started: false,
|
|
137
|
+
stop: null,
|
|
138
|
+
stopped: false,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await file_keep_new(PATH_PUBLIC + path + '.service.js', async file_content => {
|
|
142
|
+
if (file_content === null) {
|
|
143
|
+
log('error, service file not found: ' + path);
|
|
144
|
+
await service_stop(service_object, true);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
await service_stop_handler(service_object);
|
|
148
|
+
await service_start_inner(path, service_object, file_content);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const service_start_inner = async (path, service_object, file_content) => {
|
|
152
|
+
try {
|
|
153
|
+
const fun = new Function(
|
|
154
|
+
'require',
|
|
155
|
+
`const log=a=>rtjscomp.log(${
|
|
156
|
+
JSON.stringify(path + ': ')
|
|
157
|
+
}+a);${file_content}`
|
|
158
|
+
);
|
|
159
|
+
fun.call(service_object, custom_require);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
log(`error in service ${path}: ${err.message}`);
|
|
163
|
+
await service_stop(service_object, false);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (service_object.start) {
|
|
168
|
+
try {
|
|
169
|
+
await service_object.start();
|
|
170
|
+
service_object.start = null;
|
|
171
|
+
service_object.started = true;
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
services_active.delete(path);
|
|
175
|
+
log(`error while starting ${path}: ${err.message}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
services_active.set(path, service_object);
|
|
180
|
+
log('service started: ' + path);
|
|
181
|
+
}
|
|
182
|
+
const services_shutdown = () => (
|
|
183
|
+
log('shutdown services...'),
|
|
184
|
+
Promise.all(
|
|
185
|
+
Array.from(services_active.values())
|
|
186
|
+
.map(service_object => service_stop(service_object, true))
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
const service_stop = async (service_object, forget) => {
|
|
190
|
+
service_object.stopped = true;
|
|
191
|
+
if (forget) fs.unwatchFile(PATH_PUBLIC + service_object.path + '.service.js');
|
|
192
|
+
await service_stop_handler(service_object);
|
|
193
|
+
services_active.delete(service_object.path);
|
|
194
|
+
log('service stopped: ' + service_object.path);
|
|
195
|
+
}
|
|
196
|
+
const service_stop_handler = async service_object => {
|
|
197
|
+
if (service_object.stop) {
|
|
198
|
+
try {
|
|
199
|
+
await service_object.stop();
|
|
200
|
+
service_object.stop = null;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
log(`error while stopping ${service_object.path}: ${err.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
global.service_require = path => {
|
|
208
|
+
const service = services_active.get(path);
|
|
209
|
+
if (service) return service;
|
|
210
|
+
throw new Error('service required: ' + path);
|
|
211
|
+
}
|
|
212
|
+
global.service_require_try = path => (
|
|
213
|
+
services_active.get(path) || null
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const map_generate_bol = (set, data) => {
|
|
217
|
+
set.clear();
|
|
218
|
+
for (const key of data.split('\n'))
|
|
219
|
+
if (
|
|
220
|
+
key.length > 0 &&
|
|
221
|
+
key.charCodeAt(0) !== 35
|
|
222
|
+
) {
|
|
223
|
+
set.add(key);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const map_generate_equ = (map, data) => {
|
|
227
|
+
map.clear();
|
|
228
|
+
for (const entry of data.split('\n'))
|
|
229
|
+
if (
|
|
230
|
+
entry.length > 0 &&
|
|
231
|
+
entry.charCodeAt(0) !== 35
|
|
232
|
+
) {
|
|
233
|
+
const equ = entry.split(':');
|
|
234
|
+
map.set(equ[0], equ[1] || '');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const file_compare = (curr, prev, path) => (
|
|
239
|
+
curr.mtime > prev.mtime && (
|
|
240
|
+
log('file changed: ' + path),
|
|
241
|
+
true
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
const file_watch = (path, callback) => (
|
|
245
|
+
fs.watchFile(path, (curr, prev) => {
|
|
246
|
+
if (file_compare(curr, prev, path)) {
|
|
247
|
+
fs.unwatchFile(path);
|
|
248
|
+
callback();
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
)
|
|
252
|
+
const file_keep_new = async (path, callback) => {
|
|
253
|
+
try {
|
|
254
|
+
await callback(await fsp.readFile(path, 'utf8'));
|
|
255
|
+
fs.watchFile(path, async (curr, prev) => {
|
|
256
|
+
if (file_compare(curr, prev, path)) {
|
|
257
|
+
await callback(
|
|
258
|
+
await fsp.readFile(path, 'utf8').catch(() => null)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
await callback(null);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let log_history = rtjscomp.log_history = [];
|
|
269
|
+
actions.log_clear = () => {
|
|
270
|
+
log_history = rtjscomp.log_history = [];
|
|
271
|
+
}
|
|
272
|
+
const log = rtjscomp.log = msg => (
|
|
273
|
+
console.log(msg),
|
|
274
|
+
log_history.push(msg),
|
|
275
|
+
spam('log', [msg])
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
const spam_enabled = fs.existsSync('spam.csv');
|
|
279
|
+
rtjscomp.spam_history = '';
|
|
280
|
+
actions.spam_save = async (muted = false) => {
|
|
281
|
+
if (!spam_enabled) return;
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
fsp.appendFile('spam.csv', rtjscomp.spam_history, 'utf8');
|
|
285
|
+
rtjscomp.spam_history = '';
|
|
286
|
+
muted || log('spam.csv saved');
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
log('error saving spam.csv: ' + err.message);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const spam = (type, data) => {
|
|
293
|
+
if (!spam_enabled) return;
|
|
294
|
+
|
|
295
|
+
rtjscomp.spam_history += (
|
|
296
|
+
Date.now() +
|
|
297
|
+
',' +
|
|
298
|
+
type +
|
|
299
|
+
',' +
|
|
300
|
+
JSON.stringify(data) +
|
|
301
|
+
'\n'
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (rtjscomp.spam_history.length >= 1e5) {
|
|
305
|
+
actions.spam_save();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const request_handle = async (request, response, https) => {
|
|
310
|
+
const request_method = request.method;
|
|
311
|
+
const request_headers = request.headers;
|
|
312
|
+
const request_ip = request_ip_get(request).clientIp;
|
|
313
|
+
|
|
314
|
+
if ('x-forwarded-proto' in request_headers) {
|
|
315
|
+
https = request_headers['x-forwarded-proto'] === 'https';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
spam('request', [https, request.url, request_ip]);
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
const request_url_parsed = url.parse(request.url, false);
|
|
322
|
+
|
|
323
|
+
let path = request_url_parsed.pathname || '';
|
|
324
|
+
|
|
325
|
+
// ignore (timeout) many hack attempts
|
|
326
|
+
if (path.includes('php') || path.includes('sql')) return;
|
|
327
|
+
|
|
328
|
+
// remove leading/trailing /
|
|
329
|
+
while (path.charCodeAt(0) === 47) {
|
|
330
|
+
path = path.substring(1);
|
|
331
|
+
}
|
|
332
|
+
while (path.charCodeAt(path.length - 1) === 47) {
|
|
333
|
+
path = path.substring(0, path.length - 1);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (path.includes('..') || path.includes('~')) throw 403;
|
|
337
|
+
|
|
338
|
+
if (file_blocks.has(path)) return;
|
|
339
|
+
|
|
340
|
+
response.setHeader('Server', 'l3p3 rtjscomp v' + VERSION);
|
|
341
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
342
|
+
|
|
343
|
+
let path_params = null;
|
|
344
|
+
let request_body_promise = null;
|
|
345
|
+
|
|
346
|
+
if (path_aliases.has(path)) {
|
|
347
|
+
response.setHeader(
|
|
348
|
+
'Content-Location',
|
|
349
|
+
path = path_aliases.get(path)
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
else { // aliases with *
|
|
353
|
+
const path_split = path.split('/');
|
|
354
|
+
const templates = path_aliases_templates.get(path_split[0]);
|
|
355
|
+
if (templates) {
|
|
356
|
+
path_split.shift();
|
|
357
|
+
template: for (const template_pair of templates) {
|
|
358
|
+
const template = template_pair[0];
|
|
359
|
+
const template_length = template.length;
|
|
360
|
+
if (template_length !== path_split.length) continue;
|
|
361
|
+
const params = {};
|
|
362
|
+
for (let i = 0; i < template_length; ++i) {
|
|
363
|
+
if (template[i].charCodeAt(0) === 42) {
|
|
364
|
+
if (template[i].length > 1) params[template[i].substr(1)] = path_split[i];
|
|
365
|
+
}
|
|
366
|
+
else if (template[i] !== path_split[i]) continue template;
|
|
367
|
+
}
|
|
368
|
+
response.setHeader('Content-Location', path = template_pair[1]);
|
|
369
|
+
path_params = params;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const file_type_index = path.lastIndexOf('.');
|
|
376
|
+
// no type ending -> dir?
|
|
377
|
+
if (file_type_index <= path.lastIndexOf('/')) throw 404;
|
|
378
|
+
const file_type = path.substring(
|
|
379
|
+
file_type_index + 1
|
|
380
|
+
).toLowerCase();
|
|
381
|
+
|
|
382
|
+
let file_gz_enabled = (
|
|
383
|
+
'accept-encoding' in request_headers &&
|
|
384
|
+
!file_type_nocompress.has(file_type) &&
|
|
385
|
+
request_headers['accept-encoding'].split(HTTP_LIST_REG).includes('gzip')
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const file_dyn_enabled = (
|
|
389
|
+
file_type_dyns.has(file_type) &&
|
|
390
|
+
!file_raws.has(path)
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (file_dyn_enabled) {
|
|
394
|
+
if (
|
|
395
|
+
request_method !== 'GET' &&
|
|
396
|
+
'content-length' in request_headers
|
|
397
|
+
) {
|
|
398
|
+
request_body_promise = new Promise(resolve => {
|
|
399
|
+
const request_body_chunks = [];
|
|
400
|
+
request.on('data', chunk => {
|
|
401
|
+
request_body_chunks.push(chunk);
|
|
402
|
+
});
|
|
403
|
+
request.on('end', chunk => {
|
|
404
|
+
chunk && request_body_chunks.push(chunk);
|
|
405
|
+
resolve(Buffer.concat(request_body_chunks));
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else if (request_method !== 'GET') {
|
|
411
|
+
throw 400;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
let file_function = null;
|
|
415
|
+
let file_stat = null;
|
|
416
|
+
const path_real = PATH_PUBLIC + path;
|
|
417
|
+
|
|
418
|
+
if (
|
|
419
|
+
file_dyn_enabled &&
|
|
420
|
+
file_cache_functions.has(path)
|
|
421
|
+
) {
|
|
422
|
+
file_function = file_cache_functions.get(path);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
log(`load ${
|
|
426
|
+
file_dyn_enabled
|
|
427
|
+
? 'dynam'
|
|
428
|
+
: 'stat'
|
|
429
|
+
}ic file: ${path}`);
|
|
430
|
+
|
|
431
|
+
if (
|
|
432
|
+
file_privates.has(path) ||
|
|
433
|
+
path.endsWith('.service.js')
|
|
434
|
+
) throw 403;
|
|
435
|
+
if (!fs.existsSync(path_real)) throw 404;
|
|
436
|
+
file_stat = fs.statSync(path_real);
|
|
437
|
+
if (file_stat.isDirectory()) throw 403;
|
|
438
|
+
|
|
439
|
+
if (file_dyn_enabled) { // compile file
|
|
440
|
+
const file_content = fs.readFileSync(path_real, 'utf8');
|
|
441
|
+
try {
|
|
442
|
+
if (file_content.includes('\r')) {
|
|
443
|
+
throw 'illegal line break, must be unix';
|
|
444
|
+
}
|
|
445
|
+
const file_content_length = file_content.length;
|
|
446
|
+
|
|
447
|
+
let code = `async (input,output,request,response,require)=>{const log=a=>rtjscomp.log(${
|
|
448
|
+
JSON.stringify(path + ': ')
|
|
449
|
+
}+a);`;
|
|
450
|
+
|
|
451
|
+
let section_dynamic = false;
|
|
452
|
+
let index_start = 0;
|
|
453
|
+
let index_end = 0;
|
|
454
|
+
|
|
455
|
+
while (index_end < file_content_length) {
|
|
456
|
+
if (section_dynamic) {
|
|
457
|
+
if (
|
|
458
|
+
(
|
|
459
|
+
index_end = file_content.indexOf(
|
|
460
|
+
'?>',
|
|
461
|
+
// skip `<?`
|
|
462
|
+
index_start = index_end + 2
|
|
463
|
+
)
|
|
464
|
+
) < 0
|
|
465
|
+
) throw '"?>" missing';
|
|
466
|
+
section_dynamic = false;
|
|
467
|
+
// section not empty?
|
|
468
|
+
if (index_start < index_end) {
|
|
469
|
+
// `<?`?
|
|
470
|
+
if (file_content.charCodeAt(index_start) !== 61) {
|
|
471
|
+
code += (
|
|
472
|
+
file_content.substring(
|
|
473
|
+
index_start,
|
|
474
|
+
index_end
|
|
475
|
+
) +
|
|
476
|
+
';'
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
else { // `<?=`?
|
|
480
|
+
code += `output.write(''+(${
|
|
481
|
+
file_content.substring(
|
|
482
|
+
++index_start,
|
|
483
|
+
index_end
|
|
484
|
+
)
|
|
485
|
+
}));`;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// skip `?>`
|
|
489
|
+
index_end += 2;
|
|
490
|
+
}
|
|
491
|
+
else { // static section
|
|
492
|
+
// still something dynamic coming?
|
|
493
|
+
if (
|
|
494
|
+
(
|
|
495
|
+
index_end = file_content.indexOf(
|
|
496
|
+
'<?',
|
|
497
|
+
index_start = index_end
|
|
498
|
+
)
|
|
499
|
+
) > -1
|
|
500
|
+
) {
|
|
501
|
+
section_dynamic = true;
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
index_end = file_content_length;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// section not empty?
|
|
508
|
+
if (index_start < index_end) {
|
|
509
|
+
code += `output.write(${
|
|
510
|
+
JSON.stringify(
|
|
511
|
+
file_content.substring(index_start, index_end)
|
|
512
|
+
)
|
|
513
|
+
});`;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
file_function = eval(code += '}');
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
throw err.message;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (err) {
|
|
526
|
+
log('compile error: ' + err);
|
|
527
|
+
throw 500;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
file_cache_functions.set(path, file_function);
|
|
531
|
+
file_watch(path_real, () => {
|
|
532
|
+
file_cache_functions.delete(path);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
response.statusCode = 200;
|
|
538
|
+
response.setHeader(
|
|
539
|
+
'Content-Type',
|
|
540
|
+
file_type_mimes.get(file_type) || file_type_mimes.get('txt')
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
if (file_dyn_enabled) { // dynamic file
|
|
544
|
+
const file_function_input = path_params || {};
|
|
545
|
+
|
|
546
|
+
if (request_headers['cookie'])
|
|
547
|
+
for (let cookie of request_headers['cookie'].split(';')) {
|
|
548
|
+
cookie = cookie.trim();
|
|
549
|
+
const index_equ = cookie.indexOf('=');
|
|
550
|
+
if (index_equ > 0) {
|
|
551
|
+
file_function_input[
|
|
552
|
+
cookie
|
|
553
|
+
.substring(0, index_equ)
|
|
554
|
+
.trimRight()
|
|
555
|
+
] = decodeURI(
|
|
556
|
+
cookie
|
|
557
|
+
.substr(index_equ + 1)
|
|
558
|
+
.trimLeft()
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
else if (index_equ < 0) {
|
|
562
|
+
file_function_input[cookie] = undefined;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (request_headers['x-input']) {
|
|
567
|
+
Object.assign(
|
|
568
|
+
file_function_input,
|
|
569
|
+
querystring_parse(request_headers['x-input'])
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (request_url_parsed.query) {
|
|
574
|
+
try {
|
|
575
|
+
Object.assign(
|
|
576
|
+
file_function_input,
|
|
577
|
+
querystring_parse(request_url_parsed.query)
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
log('request query error: ' + err.message);
|
|
582
|
+
throw 400;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (request_body_promise) {
|
|
587
|
+
try {
|
|
588
|
+
const content_type = request.headers['content-type'] || '';
|
|
589
|
+
const body_raw = file_function_input['body'] = await request_body_promise;
|
|
590
|
+
let body = null;
|
|
591
|
+
switch (content_type.split(';')[0]) {
|
|
592
|
+
case 'application/x-www-form-urlencoded':
|
|
593
|
+
body = querystring_parse(body_raw.toString());
|
|
594
|
+
break;
|
|
595
|
+
case 'application/json':
|
|
596
|
+
body = JSON.parse(body_raw.toString());
|
|
597
|
+
break;
|
|
598
|
+
case 'multipart/form-data': {
|
|
599
|
+
body = Object.fromEntries(
|
|
600
|
+
multipart_parse(
|
|
601
|
+
body_raw,
|
|
602
|
+
content_type.split('boundary=')[1].split(';')[0]
|
|
603
|
+
)
|
|
604
|
+
.map(({ name, ...value }) => [
|
|
605
|
+
name,
|
|
606
|
+
value.type ? value : value.data.toString()
|
|
607
|
+
])
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (body) {
|
|
612
|
+
Object.assign(file_function_input, body);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
log('request body error: ' + err.message);
|
|
617
|
+
throw 400;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const request_headers_user_agent = file_function_input['user_agent'] = request_headers['user-agent'];
|
|
622
|
+
file_function_input['bot'] = AGENT_CHECK_BOT.test(request_headers_user_agent);
|
|
623
|
+
file_function_input['mobil'] = AGENT_CHECK_MOBIL.test(request_headers_user_agent);
|
|
624
|
+
|
|
625
|
+
file_function_input['https'] = https;
|
|
626
|
+
file_function_input['ip'] = request_ip;
|
|
627
|
+
file_function_input['method'] = request_method.toLowerCase();
|
|
628
|
+
file_function_input['path'] = request_url_parsed.pathname;
|
|
629
|
+
|
|
630
|
+
let file_function_output;
|
|
631
|
+
response.setHeader('Cache-Control', 'no-cache, no-store');
|
|
632
|
+
|
|
633
|
+
if (file_gz_enabled) {
|
|
634
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
635
|
+
|
|
636
|
+
(
|
|
637
|
+
file_function_output = zlib.createGzip(GZIP_OPTIONS)
|
|
638
|
+
).pipe(response);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
file_function_output = response;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
spam('execute', [
|
|
645
|
+
path,
|
|
646
|
+
Object.fromEntries(
|
|
647
|
+
Object.entries(file_function_input)
|
|
648
|
+
.filter(e => e[0] !== 'body')
|
|
649
|
+
.map(e => e[0] === 'password' ? [e[0], '***'] : e)
|
|
650
|
+
.map(e => e[0] === 'file' ? [e[0], '...'] : e)
|
|
651
|
+
.map(e => (typeof e[1] === 'object' && !e[1].length) ? [e[0], Object.keys(e[1]).slice(0, 20)] : e)
|
|
652
|
+
.map(e => (e[0] !== 'user_agent' && typeof e[1] === 'string' && e[1].length > 20) ? [e[0], e[1].substr(0, 20) + '...'] : e)
|
|
653
|
+
)
|
|
654
|
+
]);
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
await file_function(
|
|
658
|
+
file_function_input,
|
|
659
|
+
file_function_output,
|
|
660
|
+
request,
|
|
661
|
+
response,
|
|
662
|
+
custom_require
|
|
663
|
+
);
|
|
664
|
+
file_function_output.end();
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
if (err instanceof Error) {
|
|
668
|
+
log(`error in file ${path}: ${err.message}`);
|
|
669
|
+
|
|
670
|
+
if (err.message.startsWith('service required: ')) {
|
|
671
|
+
err = 503;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (typeof err === 'number') {
|
|
675
|
+
response.removeHeader('Content-Encoding');
|
|
676
|
+
throw err;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
file_function_output.end((
|
|
680
|
+
file_type === 'html'
|
|
681
|
+
? '<hr>'
|
|
682
|
+
: '\n\n---\n'
|
|
683
|
+
) + 'ERROR!');
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else { // static file
|
|
687
|
+
let file_data = null;
|
|
688
|
+
|
|
689
|
+
if (
|
|
690
|
+
file_gz_enabled &&
|
|
691
|
+
file_stat.size > 90 &&
|
|
692
|
+
fs.existsSync(path_real + '.gz')
|
|
693
|
+
) {
|
|
694
|
+
file_data = fs.createReadStream(path_real + '.gz');
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
file_gz_enabled = false;
|
|
698
|
+
file_data = fs.createReadStream(path_real);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
spam('static_send', [path, file_gz_enabled]);
|
|
702
|
+
response.setHeader('Cache-Control', 'public, max-age=600');
|
|
703
|
+
|
|
704
|
+
if (file_gz_enabled) {
|
|
705
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
response.setHeader('Content-Length', file_stat.size);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
file_data.pipe(response);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
// catch internal errors
|
|
716
|
+
if (typeof err !== 'number') {
|
|
717
|
+
console.error(err);
|
|
718
|
+
err = 500;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (err >= 400) {
|
|
722
|
+
log(`error ${err} at request: ${request_ip}; ${request.url}`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
response.writeHead(err, {
|
|
726
|
+
'Content-Type': 'text/html',
|
|
727
|
+
'Cache-Control': 'no-cache, no-store',
|
|
728
|
+
});
|
|
729
|
+
response.end(`<!DOCTYPE html><html><body><h1>HTTP ${err}: ${http.STATUS_CODES[err] || 'Error'}</h1></body></html>`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
let exiting = false;
|
|
734
|
+
actions.halt = async () => {
|
|
735
|
+
await Promise.all([
|
|
736
|
+
actions.http_stop(),
|
|
737
|
+
actions.https_stop && actions.https_stop(),
|
|
738
|
+
services_shutdown(),
|
|
739
|
+
].filter(Boolean));
|
|
740
|
+
await actions.spam_save();
|
|
741
|
+
log('stopped everything');
|
|
742
|
+
}
|
|
743
|
+
actions.exit = async status => {
|
|
744
|
+
if (exiting) return;
|
|
745
|
+
if (typeof status !== 'number') status = 0;
|
|
746
|
+
await actions.halt();
|
|
747
|
+
log('exiting...');
|
|
748
|
+
exiting = true;
|
|
749
|
+
process.exit(status);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
process.on('uncaughtException', err => {
|
|
753
|
+
err = err.message || err;
|
|
754
|
+
if (typeof err === 'symbol') err = err.toString();
|
|
755
|
+
log('error uncaughtException: ' + err);
|
|
756
|
+
console.log(err);
|
|
757
|
+
actions.exit(1);
|
|
758
|
+
});
|
|
759
|
+
process.on('unhandledRejection', err => {
|
|
760
|
+
log('error unhandledRejection: ' + (err.message || err));
|
|
761
|
+
console.log(err);
|
|
762
|
+
actions.exit(1);
|
|
763
|
+
});
|
|
764
|
+
process.on('exit', actions.exit);
|
|
765
|
+
process.on('SIGINT', actions.exit);
|
|
766
|
+
//process.on('SIGUSR1', actions.exit);
|
|
767
|
+
process.on('SIGUSR2', actions.exit);
|
|
768
|
+
process.on('SIGTERM', actions.exit);
|
|
769
|
+
|
|
770
|
+
log(`rtjscomp v${VERSION} in ${typeof Bun === 'undefined' ? 'node' : 'bun'} on ${process.platform}`);
|
|
771
|
+
|
|
772
|
+
// initial
|
|
773
|
+
await Promise.all([
|
|
774
|
+
fsp.stat(PATH_CONFIG).catch(_ => null),
|
|
775
|
+
fsp.stat(PATH_DATA).catch(_ => null),
|
|
776
|
+
fsp.stat(PATH_PUBLIC).catch(_ => null),
|
|
777
|
+
]).then(([stat_config, stat_data, stat_public]) => {
|
|
778
|
+
if (!stat_config) {
|
|
779
|
+
log('creating config template directory');
|
|
780
|
+
fs.mkdirSync(PATH_CONFIG);
|
|
781
|
+
fs.mkdirSync(PATH_CONFIG + 'ssl');
|
|
782
|
+
for (const file of 'file_type_dyns,file_type_mimes,file_type_nocompress,path_aliases,port_http,port_https,services'.split(',')) {
|
|
783
|
+
fs.copyFileSync(
|
|
784
|
+
__dirname + '/' + PATH_CONFIG + file + '.txt',
|
|
785
|
+
PATH_CONFIG + file + '.txt'
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (!stat_data) {
|
|
790
|
+
fs.mkdirSync(PATH_DATA);
|
|
791
|
+
}
|
|
792
|
+
if (!stat_public) {
|
|
793
|
+
fs.cpSync(
|
|
794
|
+
__dirname + '/' + PATH_PUBLIC,
|
|
795
|
+
PATH_PUBLIC,
|
|
796
|
+
{recursive: true}
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
await Promise.all([
|
|
802
|
+
file_keep_new(PATH_CONFIG + 'init.js', data => {
|
|
803
|
+
if (!data) return;
|
|
804
|
+
log('[deprecated!] run global init script');
|
|
805
|
+
try {
|
|
806
|
+
var require = custom_require;
|
|
807
|
+
eval(data);
|
|
808
|
+
}
|
|
809
|
+
catch (err) {
|
|
810
|
+
log('error in init.js: ' + err.message);
|
|
811
|
+
}
|
|
812
|
+
}),
|
|
813
|
+
file_keep_new(PATH_CONFIG + 'services.txt', async data => {
|
|
814
|
+
log('load service list');
|
|
815
|
+
map_generate_bol(services, data);
|
|
816
|
+
await services_list_react();
|
|
817
|
+
}),
|
|
818
|
+
file_keep_new(PATH_CONFIG + 'file_type_mimes.txt', data => {
|
|
819
|
+
log('load file type map');
|
|
820
|
+
map_generate_equ(file_type_mimes, data);
|
|
821
|
+
if (!file_type_mimes.has('txt')) {
|
|
822
|
+
file_type_mimes.set('txt', 'text/plain; charset=utf-8');
|
|
823
|
+
}
|
|
824
|
+
}),
|
|
825
|
+
file_keep_new(PATH_CONFIG + 'path_aliases.txt', data => {
|
|
826
|
+
log('load path aliases map');
|
|
827
|
+
map_generate_equ(path_aliases, data);
|
|
828
|
+
path_aliases_templates.clear();
|
|
829
|
+
for (const [key, value] of path_aliases.entries()) {
|
|
830
|
+
const star_index = key.indexOf('*');
|
|
831
|
+
if (star_index < 0) continue;
|
|
832
|
+
path_aliases.delete(key);
|
|
833
|
+
const template = key.split('/');
|
|
834
|
+
const first = template.shift();
|
|
835
|
+
if (path_aliases_templates.has(first)) {
|
|
836
|
+
path_aliases_templates.get(first).push([template, value]);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
path_aliases_templates.set(first, [
|
|
840
|
+
[template, value],
|
|
841
|
+
]);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}),
|
|
845
|
+
file_keep_new(PATH_CONFIG + 'file_type_dyns.txt', data => {
|
|
846
|
+
log('load dynamic file type list');
|
|
847
|
+
map_generate_bol(file_type_dyns, data);
|
|
848
|
+
}),
|
|
849
|
+
file_keep_new(PATH_CONFIG + 'file_type_nocompress.txt', data => {
|
|
850
|
+
log('load non-compressable file list');
|
|
851
|
+
map_generate_bol(file_type_nocompress, data);
|
|
852
|
+
}),
|
|
853
|
+
file_keep_new(PATH_CONFIG + 'file_raws.txt', data => {
|
|
854
|
+
if (!data) return;
|
|
855
|
+
log('load static file list');
|
|
856
|
+
map_generate_bol(file_raws, data);
|
|
857
|
+
}),
|
|
858
|
+
file_keep_new(PATH_CONFIG + 'file_privates.txt', data => {
|
|
859
|
+
if (!data) return;
|
|
860
|
+
log('load private file list');
|
|
861
|
+
map_generate_bol(file_privates, data);
|
|
862
|
+
}),
|
|
863
|
+
file_keep_new(PATH_CONFIG + 'file_blocks.txt', data => {
|
|
864
|
+
if (!data) return;
|
|
865
|
+
log('load blocked file list');
|
|
866
|
+
map_generate_bol(file_blocks, data);
|
|
867
|
+
}),
|
|
868
|
+
]);
|
|
869
|
+
|
|
870
|
+
let connections_count = 0;
|
|
871
|
+
const server_http = http.createServer(
|
|
872
|
+
(request, response) => request_handle(request, response, false)
|
|
873
|
+
);
|
|
874
|
+
let http_status = false;
|
|
875
|
+
let http_status_target = false;
|
|
876
|
+
const http_connections = new Map;
|
|
877
|
+
|
|
878
|
+
server_http.on('connection', connection => {
|
|
879
|
+
const id = ++connections_count;
|
|
880
|
+
http_connections.set(id, connection);
|
|
881
|
+
connection.on('close', () => {
|
|
882
|
+
http_connections.delete(id);
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
actions.http_start = () => {
|
|
887
|
+
if (http_status) return;
|
|
888
|
+
try {
|
|
889
|
+
server_http.listen(port_http);
|
|
890
|
+
http_status = http_status_target = true;
|
|
891
|
+
log('http started at port ' + port_http);
|
|
892
|
+
}
|
|
893
|
+
catch (err) {
|
|
894
|
+
log('error while starting http: ' + err.message);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
actions.http_restart = () => {
|
|
898
|
+
if (!http_status) actions.http_start();
|
|
899
|
+
else if (http_status_target) {
|
|
900
|
+
http_status_target = false;
|
|
901
|
+
log('http is restarting...');
|
|
902
|
+
server_http.close(() => {
|
|
903
|
+
http_status = false;
|
|
904
|
+
log('http stopped');
|
|
905
|
+
actions.http_start();
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
actions.http_stop = async () => {
|
|
910
|
+
if (!http_status_target || !http_status) return;
|
|
911
|
+
http_status_target = false;
|
|
912
|
+
log('http is stopping...');
|
|
913
|
+
await new Promise(resolve => server_http.close(resolve));
|
|
914
|
+
http_status = false;
|
|
915
|
+
log('http stopped');
|
|
916
|
+
}
|
|
917
|
+
actions.http_kill = () => {
|
|
918
|
+
if (http_status_target || !http_status) return;
|
|
919
|
+
log('killing http...');
|
|
920
|
+
for (const connection of http_connections.values()) connection.destroy();
|
|
921
|
+
http_connections.clear();
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
file_keep_new(PATH_CONFIG + 'port_http.txt', data => {
|
|
925
|
+
log('load http port number');
|
|
926
|
+
if (
|
|
927
|
+
!data ||
|
|
928
|
+
isNaN(data = Number(data)) ||
|
|
929
|
+
!number_check_uint(data)
|
|
930
|
+
) {
|
|
931
|
+
log('error: invalid http port number');
|
|
932
|
+
}
|
|
933
|
+
else if (data !== port_http) {
|
|
934
|
+
port_http = data;
|
|
935
|
+
actions.http_restart();
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
try {
|
|
940
|
+
const https_key = fs.readFileSync(PATH_CONFIG + 'ssl/domain.key');
|
|
941
|
+
const https_cert = fs.readFileSync(PATH_CONFIG + 'ssl/chained.pem');
|
|
942
|
+
const server_https = require('https').createServer(
|
|
943
|
+
{key: https_key, cert: https_cert},
|
|
944
|
+
(request, response) => request_handle(request, response, true)
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
let https_status = false;
|
|
948
|
+
let https_status_target = false;
|
|
949
|
+
const https_connections = new Map;
|
|
950
|
+
|
|
951
|
+
server_https.on('connection', connection => {
|
|
952
|
+
const id = ++connections_count;
|
|
953
|
+
https_connections.set(id, connection);
|
|
954
|
+
connection.on('close', () => {
|
|
955
|
+
https_connections.delete(id);
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
actions.https_start = () => {
|
|
960
|
+
if (https_status) return;
|
|
961
|
+
try {
|
|
962
|
+
server_https.listen(port_https);
|
|
963
|
+
https_status = https_status_target = true;
|
|
964
|
+
log('https started at port ' + port_https);
|
|
965
|
+
}
|
|
966
|
+
catch (err) {
|
|
967
|
+
log('error while starting https: ' + err.message);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
actions.https_restart = () => {
|
|
971
|
+
if (!https_status) actions.https_start();
|
|
972
|
+
else if (https_status_target) {
|
|
973
|
+
https_status_target = false;
|
|
974
|
+
log('https is restarting...');
|
|
975
|
+
server_https.close(function () {
|
|
976
|
+
https_status = false;
|
|
977
|
+
log('https stopped');
|
|
978
|
+
actions.https_start();
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
actions.https_stop = async () => {
|
|
983
|
+
if (!https_status_target || !https_status) return;
|
|
984
|
+
https_status_target = false;
|
|
985
|
+
log('https is stopping...');
|
|
986
|
+
await new Promise(resolve => server_https.close(resolve));
|
|
987
|
+
https_status = false;
|
|
988
|
+
log('https stopped');
|
|
989
|
+
}
|
|
990
|
+
actions.https_kill = () => {
|
|
991
|
+
if (https_status_target || !https_status) return;
|
|
992
|
+
log('killing https...');
|
|
993
|
+
for (const connection of https_connections.values()) connection.destroy();
|
|
994
|
+
https_connections.clear();
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
file_keep_new(PATH_CONFIG + 'port_https.txt', data => {
|
|
998
|
+
log('load https port number');
|
|
999
|
+
if (
|
|
1000
|
+
!data ||
|
|
1001
|
+
isNaN(data = Number(data)) ||
|
|
1002
|
+
!number_check_uint(data)
|
|
1003
|
+
) {
|
|
1004
|
+
log('error: invalid https port number');
|
|
1005
|
+
}
|
|
1006
|
+
else if (data !== port_https) {
|
|
1007
|
+
port_https = data;
|
|
1008
|
+
actions.https_restart();
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
catch (err) {
|
|
1013
|
+
log('https is disabled');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
})();
|