@webqit/webflo 0.8.76 → 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/package.json +5 -12
- package/src/Cli.js +131 -0
- package/src/Configurator.js +97 -0
- package/src/Context.js +76 -0
- package/src/config-pi/deployment/Env.js +69 -0
- package/src/config-pi/deployment/Layout.js +65 -0
- package/src/config-pi/deployment/Origins.js +133 -0
- package/src/config-pi/deployment/Virtualization.js +65 -0
- package/src/config-pi/deployment/index.js +18 -0
- package/src/config-pi/index.js +16 -0
- package/src/config-pi/runtime/Client.js +59 -0
- package/src/config-pi/runtime/Server.js +174 -0
- package/src/config-pi/runtime/client/Worker.js +117 -0
- package/src/config-pi/runtime/client/index.js +12 -0
- package/src/config-pi/runtime/index.js +18 -0
- package/src/config-pi/runtime/server/Headers.js +90 -0
- package/src/config-pi/runtime/server/Redirects.js +108 -0
- package/src/config-pi/runtime/server/index.js +14 -0
- package/src/config-pi/static/Manifest.js +321 -0
- package/src/config-pi/static/Ssg.js +72 -0
- package/src/config-pi/static/index.js +14 -0
- package/src/deployment-pi/index.js +10 -0
- package/src/{services → deployment-pi}/origins/index.js +88 -58
- package/src/index.js +14 -147
- package/src/{runtime → runtime-pi}/Router.js +19 -19
- package/src/runtime-pi/client/Context.js +7 -0
- package/src/{runtime → runtime-pi}/client/Router.js +2 -2
- package/src/{runtime/client/Navigator.js → runtime-pi/client/Runtime.js} +143 -102
- package/src/runtime-pi/client/RuntimeClient.js +114 -0
- package/src/{runtime → runtime-pi}/client/Storage.js +8 -7
- package/src/{runtime → runtime-pi}/client/Url.js +2 -6
- package/src/{runtime/client/WorkerClient.js → runtime-pi/client/WorkerComm.js} +2 -2
- package/src/runtime-pi/client/generate.js +242 -0
- package/src/runtime-pi/client/generate.oohtml.js +7 -0
- package/src/runtime-pi/client/index.js +18 -0
- package/src/runtime-pi/client/whatwag.js +27 -0
- package/src/runtime-pi/client/worker/Context.js +7 -0
- package/src/runtime-pi/client/worker/Worker.js +243 -0
- package/src/runtime-pi/client/worker/WorkerClient.js +46 -0
- package/src/runtime-pi/client/worker/index.js +18 -0
- package/src/runtime-pi/index.js +14 -0
- package/src/runtime-pi/server/Context.js +16 -0
- package/src/{runtime → runtime-pi}/server/Router.js +6 -6
- package/src/runtime-pi/server/Runtime.js +531 -0
- package/src/runtime-pi/server/RuntimeClient.js +102 -0
- package/src/runtime-pi/server/index.js +41 -0
- package/src/runtime-pi/server/whatwag.js +35 -0
- package/src/{runtime → runtime-pi}/util.js +0 -0
- package/src/{runtime/_FormData.js → runtime-pi/xFormData.js} +2 -2
- package/src/{runtime/_Headers.js → runtime-pi/xHeaders.js} +4 -4
- package/src/runtime-pi/xHttpEvent.js +93 -0
- package/src/runtime-pi/xHttpMessage.js +179 -0
- package/src/runtime-pi/xRequest.js +67 -0
- package/src/runtime-pi/xRequestHeaders.js +95 -0
- package/src/runtime-pi/xResponse.js +62 -0
- package/src/{runtime/_ResponseHeaders.js → runtime-pi/xResponseHeaders.js} +38 -18
- package/src/{runtime/_URL.js → runtime-pi/xURL.js} +4 -4
- package/src/runtime-pi/xfetch.js +7 -0
- package/src/{services → services-pi}/certbot/http-auth-hook.js +0 -0
- package/src/{services → services-pi}/certbot/http-cleanup-hook.js +0 -0
- package/src/{services → services-pi}/certbot/index.js +21 -15
- package/src/services-pi/index.js +9 -0
- package/src/static-pi/index.js +11 -0
- package/src/webflo.js +33 -0
- package/test/index.test.js +26 -0
- package/src/build/client/index.js +0 -261
- package/src/build/index.js +0 -5
- package/src/config/client.js +0 -191
- package/src/config/headers.js +0 -121
- package/src/config/index.js +0 -14
- package/src/config/layout.js +0 -83
- package/src/config/manifest.js +0 -341
- package/src/config/origins.js +0 -165
- package/src/config/prerendering.js +0 -100
- package/src/config/redirects.js +0 -137
- package/src/config/server.js +0 -201
- package/src/config/variables.js +0 -102
- package/src/config/vhosts.js +0 -93
- package/src/runtime/_MessageStream.js +0 -195
- package/src/runtime/_NavigationEvent.js +0 -91
- package/src/runtime/_Request.js +0 -61
- package/src/runtime/_RequestHeaders.js +0 -72
- package/src/runtime/_Response.js +0 -56
- package/src/runtime/client/Cache.js +0 -38
- package/src/runtime/client/Http.js +0 -225
- package/src/runtime/client/NavigationEvent.js +0 -21
- package/src/runtime/client/Runtime.js +0 -126
- package/src/runtime/client/StdRequest.js +0 -74
- package/src/runtime/client/Worker.js +0 -312
- package/src/runtime/client/WorkerComm.js +0 -183
- package/src/runtime/client/effects/sounds.js +0 -64
- package/src/runtime/index.js +0 -5
- package/src/runtime/server/NavigationEvent.js +0 -39
- package/src/runtime/server/Runtime.js +0 -593
- package/src/runtime/server/index.js +0 -183
- package/src/runtime/server/index.mjs +0 -10
- package/src/services/index.js +0 -6
|
@@ -1,593 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* imports
|
|
4
|
-
*/
|
|
5
|
-
import Fs from 'fs';
|
|
6
|
-
import Path from 'path';
|
|
7
|
-
import Http from 'http';
|
|
8
|
-
import Https from 'https';
|
|
9
|
-
import Formidable from 'formidable';
|
|
10
|
-
import QueryString from 'querystring';
|
|
11
|
-
import Sessions from 'client-sessions';
|
|
12
|
-
import _each from '@webqit/util/obj/each.js';
|
|
13
|
-
import _arrFrom from '@webqit/util/arr/from.js';
|
|
14
|
-
import _promise from '@webqit/util/js/promise.js';
|
|
15
|
-
import _isObject from '@webqit/util/js/isObject.js';
|
|
16
|
-
import _isArray from '@webqit/util/js/isArray.js';
|
|
17
|
-
import { _isString, _isPlainObject, _isPlainArray } from '@webqit/util/js/index.js';
|
|
18
|
-
import _delay from '@webqit/util/js/delay.js';
|
|
19
|
-
import { slice as _streamSlice } from 'stream-slice';
|
|
20
|
-
import * as config from '../../config/index.js';
|
|
21
|
-
import * as services from '../../services/index.js';
|
|
22
|
-
import NavigationEvent from './NavigationEvent.js';
|
|
23
|
-
import Router from './Router.js';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The default initializer.
|
|
27
|
-
*
|
|
28
|
-
* @param Ui Ui
|
|
29
|
-
* @param Object flags
|
|
30
|
-
*
|
|
31
|
-
* @return void
|
|
32
|
-
*/
|
|
33
|
-
export default async function(Ui, flags = {}) {
|
|
34
|
-
|
|
35
|
-
const layout = await config.layout.read(flags, {});
|
|
36
|
-
const v_setup = {}, setup = {
|
|
37
|
-
layout,
|
|
38
|
-
server: await config.server.read(flags, layout),
|
|
39
|
-
variables: await config.variables.read(flags, layout),
|
|
40
|
-
};
|
|
41
|
-
if (setup.server.shared) {
|
|
42
|
-
await Promise.all(((await config.vhosts.read(flags, setup.layout)).entries || []).map(vh => new Promise(async resolve => {
|
|
43
|
-
const vlayout = await config.layout.read(flags, {ROOT: Path.join(setup.layout.ROOT, vh.path)});
|
|
44
|
-
v_setup[vh.host] = {
|
|
45
|
-
layout: vlayout,
|
|
46
|
-
server: await config.server.read(flags, vlayout),
|
|
47
|
-
variables: await config.variables.read(flags, vlayout),
|
|
48
|
-
vh,
|
|
49
|
-
};
|
|
50
|
-
resolve();
|
|
51
|
-
})));
|
|
52
|
-
} else if (setup.variables.autoload !== false) {
|
|
53
|
-
Object.keys(setup.variables.entries).forEach(key => {
|
|
54
|
-
if (!(key in process.env) || setup.variables.autoload === 2) {
|
|
55
|
-
process.env[key] = setup.variables.entries[key];
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------------------------------------
|
|
61
|
-
|
|
62
|
-
function handleRequest(protocol, request, response) {
|
|
63
|
-
let _setup = setup, hostname = (request.headers.host || '').split(':')[0];
|
|
64
|
-
if (setup.server.shared) {
|
|
65
|
-
if (!(_setup = v_setup[hostname])
|
|
66
|
-
&& ((hostname.startsWith('www.') && (_setup = v_setup[hostname.substr(4)]) && _setup.server.force_www)
|
|
67
|
-
&& (!hostname.startsWith('www.') && (_setup = v_setup['www.' + hostname]) && _setup.server.force_www))) {
|
|
68
|
-
response.statusCode = 500;
|
|
69
|
-
response.end('Unrecognized host');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
if (protocol === 'http' && _setup.server.https.force && !flags['http-only'] && /** main server */setup.server.https.port) {
|
|
74
|
-
response.statusCode = 302;
|
|
75
|
-
response.setHeader('Location', 'https://' + request.headers.host + request.url);
|
|
76
|
-
response.end();
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
if (hostname.startsWith('www.') && setup.server.force_www === 'remove') {
|
|
80
|
-
response.statusCode = 302;
|
|
81
|
-
response.setHeader('Location', protocol + '://' + hostname.substr(4) + request.url);
|
|
82
|
-
response.end();
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (!hostname.startsWith('www.') && setup.server.force_www === 'add') {
|
|
86
|
-
response.statusCode = 302;
|
|
87
|
-
response.setHeader('Location', protocol + '://www.' + hostname + request.url);
|
|
88
|
-
response.end();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
run(_setup, request, response, Ui, flags, protocol);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// ---------------------------------------------
|
|
95
|
-
|
|
96
|
-
if (!flags['https-only']) {
|
|
97
|
-
Http.createServer((request, response) => handleRequest('http', request, response)).listen(process.env.PORT || setup.server.port);
|
|
98
|
-
}
|
|
99
|
-
if (!flags['http-only'] && setup.server.https.port) {
|
|
100
|
-
const httpsServer = Https.createServer((request, response) => handleRequest('https', request, response));
|
|
101
|
-
if (setup.server.shared) {
|
|
102
|
-
_each(v_setup, (host, _setup) => {
|
|
103
|
-
if (Fs.existsSync(_setup.server.https.keyfile)) {
|
|
104
|
-
const cert = {
|
|
105
|
-
key: Fs.readFileSync(_setup.server.https.keyfile),
|
|
106
|
-
cert: Fs.readFileSync(_setup.server.https.certfile),
|
|
107
|
-
};
|
|
108
|
-
var domains = _arrFrom(_setup.server.https.certdoms);
|
|
109
|
-
if (!domains[0] || domains[0].trim() === '*') {
|
|
110
|
-
httpsServer.addContext(host, cert);
|
|
111
|
-
if (_setup.server.force_www) {
|
|
112
|
-
httpsServer.addContext(host.startsWith('www.') ? host.substr(4) : 'www.' + host, cert);
|
|
113
|
-
}
|
|
114
|
-
} else {
|
|
115
|
-
domains.forEach(domain => {
|
|
116
|
-
httpsServer.addContext(domain, cert);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
} else {
|
|
122
|
-
if (Fs.existsSync(setup.server.https.keyfile)) {
|
|
123
|
-
var domains = _arrFrom(setup.server.https.certdoms);
|
|
124
|
-
var cert = {
|
|
125
|
-
key: Fs.readFileSync(setup.server.https.keyfile),
|
|
126
|
-
cert: Fs.readFileSync(setup.server.https.certfile),
|
|
127
|
-
};
|
|
128
|
-
if (!domains[0]) {
|
|
129
|
-
domains = ['*'];
|
|
130
|
-
}
|
|
131
|
-
domains.forEach(domain => {
|
|
132
|
-
httpsServer.addContext(domain, cert);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
httpsServer.listen(process.env.PORT2 || setup.server.https.port);
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* The Server.
|
|
142
|
-
*
|
|
143
|
-
* @param Object hostSetup
|
|
144
|
-
* @param Request request
|
|
145
|
-
* @param Response response
|
|
146
|
-
* @param Ui Ui
|
|
147
|
-
* @param Object flags
|
|
148
|
-
* @param String protocol
|
|
149
|
-
*
|
|
150
|
-
* @return void
|
|
151
|
-
*/
|
|
152
|
-
export async function run(hostSetup, request, response, Ui, flags = {}, protocol = 'http') {
|
|
153
|
-
|
|
154
|
-
// --------
|
|
155
|
-
// Request parsing
|
|
156
|
-
// --------
|
|
157
|
-
|
|
158
|
-
const requestInit = { method: request.method, headers: request.headers };
|
|
159
|
-
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
160
|
-
requestInit.body = await new Promise((resolve, reject) => {
|
|
161
|
-
var formidable = new Formidable.IncomingForm({ multiples: true, allowEmptyFiles: false, keepExtensions: true });
|
|
162
|
-
formidable.parse(request, (error, fields, files) => {
|
|
163
|
-
if (error) {
|
|
164
|
-
reject(error);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (request.headers['content-type'] === 'application/json') {
|
|
168
|
-
return resolve(fields);
|
|
169
|
-
}
|
|
170
|
-
const formData = new NavigationEvent.globals.FormData;
|
|
171
|
-
Object.keys(fields).forEach(name => {
|
|
172
|
-
if (Array.isArray(fields[name])) {
|
|
173
|
-
const values = Array.isArray(fields[name][0])
|
|
174
|
-
? fields[name][0]/* bugly a nested array when there are actually more than entry */
|
|
175
|
-
: fields[name];
|
|
176
|
-
values.forEach(value => {
|
|
177
|
-
formData.append(!name.endsWith(']') ? name + '[]' : name, value);
|
|
178
|
-
});
|
|
179
|
-
} else {
|
|
180
|
-
formData.append(name, fields[name]);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
Object.keys(files).forEach(name => {
|
|
184
|
-
const fileCompat = file => {
|
|
185
|
-
// IMPORTANT
|
|
186
|
-
// Path up the "formidable" file in a way that "formdata-node"
|
|
187
|
-
// to can translate it into its own file instance
|
|
188
|
-
file[Symbol.toStringTag] = 'File';
|
|
189
|
-
file.stream = () => Fs.createReadStream(file.path);
|
|
190
|
-
// Done pathcing
|
|
191
|
-
return file;
|
|
192
|
-
}
|
|
193
|
-
if (Array.isArray(files[name])) {
|
|
194
|
-
files[name].forEach(value => {
|
|
195
|
-
formData.append(name, fileCompat(value));
|
|
196
|
-
});
|
|
197
|
-
} else {
|
|
198
|
-
formData.append(name, fileCompat(files[name]));
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
resolve(formData);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// --------
|
|
207
|
-
// NavigationEvent instance
|
|
208
|
-
// --------
|
|
209
|
-
|
|
210
|
-
const fullUrl = protocol + '://' + request.headers.host + request.url;
|
|
211
|
-
const _sessionFactory = function(id, params = {}, callback = null) {
|
|
212
|
-
let factory, secret = hostSetup.variables.entries.SESSION_KEY;
|
|
213
|
-
Sessions({
|
|
214
|
-
duration: 0, // how long the session will stay valid in ms
|
|
215
|
-
activeDuration: 0, // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
|
|
216
|
-
...params,
|
|
217
|
-
cookieName: id, // cookie name dictates the key name added to the request object
|
|
218
|
-
secret, // should be a large unguessable string
|
|
219
|
-
})(request, response, e => {
|
|
220
|
-
factory = Object.getOwnPropertyDescriptor(request, id);
|
|
221
|
-
if (callback) {
|
|
222
|
-
callback(e, factory);
|
|
223
|
-
} else if (e) {
|
|
224
|
-
// TODO
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
// Where theres no error, factory is available Sync
|
|
228
|
-
return !callback ? factory : undefined;
|
|
229
|
-
};
|
|
230
|
-
const serverNavigationEvent = new NavigationEvent(
|
|
231
|
-
new NavigationEvent.Request(fullUrl, requestInit),
|
|
232
|
-
_sessionFactory('_session', { duration: 60 * 60, activeDuration: 60 * 60 }).get(),
|
|
233
|
-
_sessionFactory
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
const $context = {
|
|
237
|
-
rdr: null,
|
|
238
|
-
layout: hostSetup.layout,
|
|
239
|
-
env: {},
|
|
240
|
-
response: null,
|
|
241
|
-
fatal: false,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
if (hostSetup.variables.autoload !== false) {
|
|
245
|
-
Object.keys(hostSetup.variables.entries).forEach(key => {
|
|
246
|
-
$context.env[key] = hostSetup.variables.entries[key];
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
$context.headers = config.headers ? await config.headers.match(serverNavigationEvent.request.url, flags, hostSetup.layout) : [];
|
|
250
|
-
const resolveSetHeader = header => {
|
|
251
|
-
var headerName = header.name.toLowerCase(),
|
|
252
|
-
headerValue = header.value,
|
|
253
|
-
isAppend = headerName.startsWith('+') ? (headerName = headerName.substr(1), true) : false,
|
|
254
|
-
isPrepend = headerName.endsWith('+') ? (headerName = headerName.substr(0, headerName.length - 1), true) : false;
|
|
255
|
-
if (isAppend || isPrepend) {
|
|
256
|
-
headerValue = [ serverNavigationEvent.request.headers.get(headerName) || '' , headerValue].filter(v => v);
|
|
257
|
-
headerValue = isPrepend ? headerValue.reverse().join(',') : headerValue.join(',');
|
|
258
|
-
}
|
|
259
|
-
return { name: headerName, value: headerValue };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// -------------------
|
|
263
|
-
// Handle redirects
|
|
264
|
-
// -------------------
|
|
265
|
-
|
|
266
|
-
if (config.redirects) {
|
|
267
|
-
if ($context.rdr = await config.redirects.match(serverNavigationEvent.request.url, flags, hostSetup.layout)) {
|
|
268
|
-
var redirectCode = $context.rdr.code || 301 /* Permanent */;
|
|
269
|
-
response.statusCode = redirectCode;
|
|
270
|
-
response.setHeader('Location', $context.rdr.target);
|
|
271
|
-
response.end();
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// -------------------
|
|
276
|
-
// Automatic request headers
|
|
277
|
-
// -------------------
|
|
278
|
-
|
|
279
|
-
$context.headers.filter(header => header.type === 'request').forEach(header => {
|
|
280
|
-
const { name, value } = resolveSetHeader(header);
|
|
281
|
-
serverNavigationEvent.request.headers.set(name, value);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// -------------------
|
|
285
|
-
// Handle request
|
|
286
|
-
// -------------------
|
|
287
|
-
|
|
288
|
-
if (!$context.fatal && !$context.rdr) {
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
|
|
292
|
-
// The app router
|
|
293
|
-
const router = new Router(serverNavigationEvent.url.pathname, hostSetup.layout, $context);
|
|
294
|
-
|
|
295
|
-
// --------
|
|
296
|
-
// ROUTE FOR DEPLOY
|
|
297
|
-
// --------
|
|
298
|
-
if (services.origins) {
|
|
299
|
-
await services.origins.hook(Ui, serverNavigationEvent, async (payload, defaultPeployFn) => {
|
|
300
|
-
var exitCode = await router.route('deploy', serverNavigationEvent, payload, function(event, _payload) {
|
|
301
|
-
return defaultPeployFn(_payload);
|
|
302
|
-
});
|
|
303
|
-
// -----------
|
|
304
|
-
response.statusCode = 200;
|
|
305
|
-
response.end(exitCode);
|
|
306
|
-
// -----------
|
|
307
|
-
return exitCode;
|
|
308
|
-
}, flags, hostSetup.layout);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// --------
|
|
312
|
-
// ROUTE FOR DATA
|
|
313
|
-
// --------
|
|
314
|
-
const httpMethodName = serverNavigationEvent.request.method.toLowerCase();
|
|
315
|
-
$context.response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], serverNavigationEvent, null, async function(event) {
|
|
316
|
-
var file = await router.fetch(event);
|
|
317
|
-
// JSON request should ignore static files
|
|
318
|
-
if (file && event.request.headers.get('Accept') && !event.request.headers.accept.match(file.headers.contentType)) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
// ----------------
|
|
322
|
-
// PRE-RENDERING
|
|
323
|
-
// ----------------
|
|
324
|
-
if (file && file.headers.contentType === 'text/html' && (file.body + '').startsWith(`<!-- PRE-RENDERED -->`)) {
|
|
325
|
-
if (config.prerendering && !(await !config.prerendering.match(serverNavigationEvent.url.pathname, flags, hostSetup.layout))) {
|
|
326
|
-
router.deletePreRendered(file.filename);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return file;
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// --------
|
|
334
|
-
if (!($context.response instanceof serverNavigationEvent.Response)) {
|
|
335
|
-
$context.response = new serverNavigationEvent.Response($context.response);
|
|
336
|
-
}
|
|
337
|
-
// --------
|
|
338
|
-
|
|
339
|
-
// --------
|
|
340
|
-
// API CALL OR PAGE REQUEST?
|
|
341
|
-
// --------
|
|
342
|
-
|
|
343
|
-
if (!$context.response.meta.static && (_isPlainObject($context.response.original) || _isPlainArray($context.response.original))) {
|
|
344
|
-
if (serverNavigationEvent.request.headers.accept.match('text/html')) {
|
|
345
|
-
// --------
|
|
346
|
-
// Render
|
|
347
|
-
// --------
|
|
348
|
-
var rendering = await router.route('render', serverNavigationEvent, $context.response.original, async function(event, data) {
|
|
349
|
-
// --------
|
|
350
|
-
if (!hostSetup.layout.renderFileCache) {
|
|
351
|
-
hostSetup.layout.renderFileCache = {};
|
|
352
|
-
}
|
|
353
|
-
var renderFile, pathnameSplit = event.url.pathname.split('/');
|
|
354
|
-
while ((renderFile = Path.join(hostSetup.layout.ROOT, hostSetup.layout.PUBLIC_DIR, './' + pathnameSplit.join('/'), 'index.html'))
|
|
355
|
-
&& pathnameSplit.length && (hostSetup.layout.renderFileCache[renderFile] === false || !(hostSetup.layout.renderFileCache[renderFile] && Fs.existsSync(renderFile)))) {
|
|
356
|
-
hostSetup.layout.renderFileCache[renderFile] === false;
|
|
357
|
-
pathnameSplit.pop();
|
|
358
|
-
}
|
|
359
|
-
hostSetup.layout.renderFileCache[renderFile] === true;
|
|
360
|
-
const instanceParams = QueryString.stringify({
|
|
361
|
-
SOURCE: renderFile,
|
|
362
|
-
URL: event.url.href,
|
|
363
|
-
ROOT: hostSetup.layout.ROOT,
|
|
364
|
-
});
|
|
365
|
-
const { window } = await import('@webqit/pseudo-browser/instance.js?' + instanceParams);
|
|
366
|
-
// --------
|
|
367
|
-
// OOHTML would waiting for DOM-ready in order to be initialized
|
|
368
|
-
await new Promise(res => window.WebQit.DOM.ready(res));
|
|
369
|
-
if (!window.document.state.env) {
|
|
370
|
-
window.document.setState({
|
|
371
|
-
env: 'server',
|
|
372
|
-
}, {update: true});
|
|
373
|
-
}
|
|
374
|
-
window.document.setState({page: data, url: event.url}, {update: 'merge'});
|
|
375
|
-
window.document.body.setAttribute('template', 'page/' + event.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
376
|
-
return new Promise(res => {
|
|
377
|
-
window.document.addEventListener('templatesreadystatechange', () => res(window));
|
|
378
|
-
if (window.document.templatesReadyState === 'complete') {
|
|
379
|
-
res(window);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
// --------
|
|
384
|
-
// Serialize rendering?
|
|
385
|
-
// --------
|
|
386
|
-
if (_isObject(rendering) && rendering.document) {
|
|
387
|
-
await _delay(2000);
|
|
388
|
-
rendering = rendering.print();
|
|
389
|
-
}
|
|
390
|
-
if (!_isString(rendering)) throw new Error('render() must return a window object or a string response.')
|
|
391
|
-
$context.response = new serverNavigationEvent.Response(rendering, {
|
|
392
|
-
status: $context.response.status,
|
|
393
|
-
headers: { ...$context.response.headers.json(), contentType: 'text/html' },
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// --------
|
|
399
|
-
// SEND RESPONSE
|
|
400
|
-
// --------
|
|
401
|
-
|
|
402
|
-
if (!response.headersSent) {
|
|
403
|
-
|
|
404
|
-
// -------------------
|
|
405
|
-
// Streaming headers
|
|
406
|
-
// -------------------
|
|
407
|
-
// Chrome needs this for audio elements to play
|
|
408
|
-
response.setHeader('Accept-Ranges', 'bytes');
|
|
409
|
-
|
|
410
|
-
// -------------------
|
|
411
|
-
// Automatic response headers
|
|
412
|
-
// -------------------
|
|
413
|
-
//response.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
414
|
-
//response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
415
|
-
$context.headers.filter(header => header.type === 'response').forEach(header => {
|
|
416
|
-
const { name, value } = resolveSetHeader(header);
|
|
417
|
-
response.setHeader(name, value);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
// -------------------
|
|
421
|
-
// Route response cookies
|
|
422
|
-
// -------------------
|
|
423
|
-
const cookieAtts = [ 'Expires', 'Max-Age', 'Domain', 'Path', 'Secure', 'HttpOnly', 'SameSite' ];
|
|
424
|
-
const setCookies = (cookies, nameContext = null) => {
|
|
425
|
-
_each(cookies, (name, cookie) => {
|
|
426
|
-
name = nameContext ? `${nameContext}[${name}]` : name;
|
|
427
|
-
cookie = !_isObject(cookie) ? { value: cookie } : cookie;
|
|
428
|
-
var expr = `${name}=${cookie.value}`;
|
|
429
|
-
if (cookie.value === false && !('maxAge' in cookie)) {
|
|
430
|
-
cookie.maxAge = 0;
|
|
431
|
-
}
|
|
432
|
-
Object.keys(cookie).forEach(attr => {
|
|
433
|
-
if (attr === 'value') return;
|
|
434
|
-
var __attr = cookieAtts.reduce((match, _attr) => match || (
|
|
435
|
-
[_attr.toLowerCase(), _attr.replace('-', '').toLowerCase()].includes(attr.toLowerCase()) ? _attr : null
|
|
436
|
-
), null);
|
|
437
|
-
if (!__attr) throw new Error(`Invalid cookie attribute: ${attr}`);
|
|
438
|
-
expr += cookie[attr] === true ? `; ${__attr}` : `; ${__attr}=${cookie[attr]}`;
|
|
439
|
-
});
|
|
440
|
-
response.setHeader('Set-Cookie', expr);
|
|
441
|
-
if (_isObject(cookie.children)) {
|
|
442
|
-
setCookies(cookie.children, name);
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
// -------------------
|
|
448
|
-
// Route response headers
|
|
449
|
-
// -------------------
|
|
450
|
-
_each($context.response.headers.json(), (name, value) => {
|
|
451
|
-
if ([ 'autoindex', 'filename', 'static' ].includes(name)) return;
|
|
452
|
-
if (name === 'set-cookie') {
|
|
453
|
-
setCookies(value);
|
|
454
|
-
} else {
|
|
455
|
-
if (name.toLowerCase() === 'location' && $context.response.status === 200) {
|
|
456
|
-
response.statusCode = 302 /* Temporary */;
|
|
457
|
-
}
|
|
458
|
-
response.setHeader(name, value);
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// -------------------
|
|
463
|
-
// Important no-caching for non-"get" requests
|
|
464
|
-
// -------------------
|
|
465
|
-
if (httpMethodName !== 'get' && !response.getHeader('Cache-Control')) {
|
|
466
|
-
response.setHeader('Cache-Control', 'no-store');
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// -------------------
|
|
470
|
-
// Send
|
|
471
|
-
// -------------------
|
|
472
|
-
if ($context.response.headers.redirect) {
|
|
473
|
-
let xRedirectPolicy = serverNavigationEvent.request.headers.get('X-Redirect-Policy');
|
|
474
|
-
let xRedirectCode = serverNavigationEvent.request.headers.get('X-Redirect-Code') || 300;
|
|
475
|
-
let isSameOriginRedirect = (new serverNavigationEvent.globals.URL($context.response.headers.location)).origin === serverNavigationEvent.url.origin;
|
|
476
|
-
if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (isSameOriginRedirect && xRedirectPolicy === 'manual-when-same-origin')) {
|
|
477
|
-
response.statusCode = xRedirectCode;
|
|
478
|
-
response.setHeader('X-Redirect-Code', $context.response.status);
|
|
479
|
-
response.setHeader('Cache-Control', 'no-store');
|
|
480
|
-
} else {
|
|
481
|
-
response.statusCode = $context.response.status;
|
|
482
|
-
}
|
|
483
|
-
response.end();
|
|
484
|
-
} else if ($context.response.original !== undefined && $context.response.original !== null) {
|
|
485
|
-
response.statusCode = $context.response.status;
|
|
486
|
-
response.statusMessage = $context.response.statusText;
|
|
487
|
-
|
|
488
|
-
// ----------------
|
|
489
|
-
// SENDING RESPONSE
|
|
490
|
-
// ----------------
|
|
491
|
-
var body = $context.response.body;
|
|
492
|
-
if ((body instanceof serverNavigationEvent.globals.ReadableStream)
|
|
493
|
-
|| (ArrayBuffer.isView(body) && (body = serverNavigationEvent.globals.ReadableStream.from(body)))) {
|
|
494
|
-
|
|
495
|
-
// We support streaming
|
|
496
|
-
const rangeRequest = serverNavigationEvent.request.headers.range;
|
|
497
|
-
if (rangeRequest) {
|
|
498
|
-
// ...in partials
|
|
499
|
-
const totalLength = $context.response.headers.contentLength;
|
|
500
|
-
// Validate offsets
|
|
501
|
-
if (rangeRequest[0] < 0 || (totalLength && rangeRequest[0] > totalLength)
|
|
502
|
-
|| (rangeRequest[1] > -1 && (rangeRequest[1] <= rangeRequest[0] || (totalLength && rangeRequest[1] >= totalLength)))) {
|
|
503
|
-
response.statusCode = 416;
|
|
504
|
-
response.setHeader('Content-Range', `bytes */${totalLength || '*'}`);
|
|
505
|
-
response.setHeader('Content-Length', 0);
|
|
506
|
-
response.end();
|
|
507
|
-
} else {
|
|
508
|
-
if (totalLength) {
|
|
509
|
-
rangeRequest.clamp(totalLength);
|
|
510
|
-
}
|
|
511
|
-
// Set new headers
|
|
512
|
-
response.writeHead(206, {
|
|
513
|
-
'Content-Range': `bytes ${rangeRequest[0]}-${rangeRequest[1]}/${totalLength || '*'}`,
|
|
514
|
-
'Content-Length': rangeRequest[1] - rangeRequest[0] + 1,
|
|
515
|
-
});
|
|
516
|
-
body
|
|
517
|
-
.pipe(_streamSlice(rangeRequest[0], rangeRequest[1]))
|
|
518
|
-
.pipe(response);
|
|
519
|
-
}
|
|
520
|
-
} else {
|
|
521
|
-
// ...as a whole
|
|
522
|
-
body.pipe(response);
|
|
523
|
-
}
|
|
524
|
-
} else {
|
|
525
|
-
// The default
|
|
526
|
-
if ($context.response.headers.contentType === 'application/json') {
|
|
527
|
-
body += '';
|
|
528
|
-
}
|
|
529
|
-
response.end(body);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// ----------------
|
|
533
|
-
// PRE-RENDERING
|
|
534
|
-
// ----------------
|
|
535
|
-
if (!$context.response.meta.filename && $context.response.headers.contentType === 'text/html') {
|
|
536
|
-
var prerenderMatch = config.prerendering ? await !config.prerendering.match(serverNavigationEvent.url.pathname, flags, hostSetup.layout) : null;
|
|
537
|
-
if (prerenderMatch) {
|
|
538
|
-
router.putPreRendered(serverNavigationEvent.url.pathname, `<!-- PRE-RENDERED -->\r\n` + $context.response.original);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
} else if (!$context.response.headers.redirect) {
|
|
542
|
-
response.statusCode = $context.response.status !== 200 ? $context.response.status : 404;
|
|
543
|
-
response.end(`${serverNavigationEvent.request.url} not found!`);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
} catch(e) {
|
|
549
|
-
|
|
550
|
-
$context.fatal = e;
|
|
551
|
-
response.statusCode = 500;
|
|
552
|
-
response.end(`Internal server error: ${e.errorCode}`);
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// --------
|
|
559
|
-
// request log
|
|
560
|
-
// --------
|
|
561
|
-
|
|
562
|
-
if (flags.logs !== false) {
|
|
563
|
-
let errorCode = [ 404, 500 ].includes(response.statusCode) ? response.statusCode : 0;
|
|
564
|
-
let xRedirectCode = response.getHeader('X-Redirect-Code');
|
|
565
|
-
let redirectCode = xRedirectCode || ((response.statusCode + '').startsWith('3') ? response.statusCode : 0);
|
|
566
|
-
let statusCode = xRedirectCode || response.statusCode;
|
|
567
|
-
Ui.log(''
|
|
568
|
-
+ '[' + (hostSetup.vh ? Ui.style.keyword(hostSetup.vh.host) + '][' : '') + Ui.style.comment((new Date).toUTCString()) + '] '
|
|
569
|
-
+ Ui.style.keyword(protocol.toUpperCase() + ' ' + serverNavigationEvent.request.method) + ' '
|
|
570
|
-
+ Ui.style.url(serverNavigationEvent.request.url) + ($context.response && ($context.response.meta || {}).autoIndex ? Ui.style.comment((!serverNavigationEvent.request.url.endsWith('/') ? '/' : '') + $context.response.meta.autoIndex) : '') + ' '
|
|
571
|
-
+ (' (' + Ui.style.comment($context.response && ($context.response.headers || {}).contentType ? $context.response.headers.contentType : 'unknown') + ') ')
|
|
572
|
-
+ (
|
|
573
|
-
errorCode
|
|
574
|
-
? Ui.style.err(errorCode + ($context.fatal ? ` [ERROR]: ${$context.fatal.error || $context.fatal.toString()}` : ``))
|
|
575
|
-
: Ui.style.val(statusCode) + (
|
|
576
|
-
redirectCode
|
|
577
|
-
? ' - ' + Ui.style.val(response.getHeader('Location'))
|
|
578
|
-
: ' (' + Ui.style.keyword(response.getHeader('Content-Range') || response.statusMessage) + ')'
|
|
579
|
-
)
|
|
580
|
-
)
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if ($context.fatal) {
|
|
585
|
-
if (flags.env === 'dev') {
|
|
586
|
-
console.trace($context.fatal);
|
|
587
|
-
//Ui.error($context.fatal);
|
|
588
|
-
process.exit();
|
|
589
|
-
}
|
|
590
|
-
throw $context.fatal;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
};
|