@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.
Files changed (97) hide show
  1. package/package.json +5 -12
  2. package/src/Cli.js +131 -0
  3. package/src/Configurator.js +97 -0
  4. package/src/Context.js +76 -0
  5. package/src/config-pi/deployment/Env.js +69 -0
  6. package/src/config-pi/deployment/Layout.js +65 -0
  7. package/src/config-pi/deployment/Origins.js +133 -0
  8. package/src/config-pi/deployment/Virtualization.js +65 -0
  9. package/src/config-pi/deployment/index.js +18 -0
  10. package/src/config-pi/index.js +16 -0
  11. package/src/config-pi/runtime/Client.js +59 -0
  12. package/src/config-pi/runtime/Server.js +174 -0
  13. package/src/config-pi/runtime/client/Worker.js +117 -0
  14. package/src/config-pi/runtime/client/index.js +12 -0
  15. package/src/config-pi/runtime/index.js +18 -0
  16. package/src/config-pi/runtime/server/Headers.js +90 -0
  17. package/src/config-pi/runtime/server/Redirects.js +108 -0
  18. package/src/config-pi/runtime/server/index.js +14 -0
  19. package/src/config-pi/static/Manifest.js +321 -0
  20. package/src/config-pi/static/Ssg.js +72 -0
  21. package/src/config-pi/static/index.js +14 -0
  22. package/src/deployment-pi/index.js +10 -0
  23. package/src/{services → deployment-pi}/origins/index.js +88 -58
  24. package/src/index.js +14 -147
  25. package/src/{runtime → runtime-pi}/Router.js +19 -19
  26. package/src/runtime-pi/client/Context.js +7 -0
  27. package/src/{runtime → runtime-pi}/client/Router.js +2 -2
  28. package/src/{runtime/client/Navigator.js → runtime-pi/client/Runtime.js} +143 -102
  29. package/src/runtime-pi/client/RuntimeClient.js +114 -0
  30. package/src/{runtime → runtime-pi}/client/Storage.js +8 -7
  31. package/src/{runtime → runtime-pi}/client/Url.js +2 -6
  32. package/src/{runtime/client/WorkerClient.js → runtime-pi/client/WorkerComm.js} +2 -2
  33. package/src/runtime-pi/client/generate.js +242 -0
  34. package/src/runtime-pi/client/generate.oohtml.js +7 -0
  35. package/src/runtime-pi/client/index.js +18 -0
  36. package/src/runtime-pi/client/whatwag.js +27 -0
  37. package/src/runtime-pi/client/worker/Context.js +7 -0
  38. package/src/runtime-pi/client/worker/Worker.js +243 -0
  39. package/src/runtime-pi/client/worker/WorkerClient.js +46 -0
  40. package/src/runtime-pi/client/worker/index.js +18 -0
  41. package/src/runtime-pi/index.js +14 -0
  42. package/src/runtime-pi/server/Context.js +16 -0
  43. package/src/{runtime → runtime-pi}/server/Router.js +6 -6
  44. package/src/runtime-pi/server/Runtime.js +531 -0
  45. package/src/runtime-pi/server/RuntimeClient.js +102 -0
  46. package/src/runtime-pi/server/index.js +41 -0
  47. package/src/runtime-pi/server/whatwag.js +35 -0
  48. package/src/{runtime → runtime-pi}/util.js +0 -0
  49. package/src/{runtime/_FormData.js → runtime-pi/xFormData.js} +2 -2
  50. package/src/{runtime/_Headers.js → runtime-pi/xHeaders.js} +4 -4
  51. package/src/runtime-pi/xHttpEvent.js +93 -0
  52. package/src/runtime-pi/xHttpMessage.js +179 -0
  53. package/src/runtime-pi/xRequest.js +67 -0
  54. package/src/runtime-pi/xRequestHeaders.js +95 -0
  55. package/src/runtime-pi/xResponse.js +62 -0
  56. package/src/{runtime/_ResponseHeaders.js → runtime-pi/xResponseHeaders.js} +38 -18
  57. package/src/{runtime/_URL.js → runtime-pi/xURL.js} +4 -4
  58. package/src/runtime-pi/xfetch.js +7 -0
  59. package/src/{services → services-pi}/certbot/http-auth-hook.js +0 -0
  60. package/src/{services → services-pi}/certbot/http-cleanup-hook.js +0 -0
  61. package/src/{services → services-pi}/certbot/index.js +21 -15
  62. package/src/services-pi/index.js +9 -0
  63. package/src/static-pi/index.js +11 -0
  64. package/src/webflo.js +33 -0
  65. package/test/index.test.js +26 -0
  66. package/src/build/client/index.js +0 -261
  67. package/src/build/index.js +0 -5
  68. package/src/config/client.js +0 -191
  69. package/src/config/headers.js +0 -121
  70. package/src/config/index.js +0 -14
  71. package/src/config/layout.js +0 -83
  72. package/src/config/manifest.js +0 -341
  73. package/src/config/origins.js +0 -165
  74. package/src/config/prerendering.js +0 -100
  75. package/src/config/redirects.js +0 -137
  76. package/src/config/server.js +0 -201
  77. package/src/config/variables.js +0 -102
  78. package/src/config/vhosts.js +0 -93
  79. package/src/runtime/_MessageStream.js +0 -195
  80. package/src/runtime/_NavigationEvent.js +0 -91
  81. package/src/runtime/_Request.js +0 -61
  82. package/src/runtime/_RequestHeaders.js +0 -72
  83. package/src/runtime/_Response.js +0 -56
  84. package/src/runtime/client/Cache.js +0 -38
  85. package/src/runtime/client/Http.js +0 -225
  86. package/src/runtime/client/NavigationEvent.js +0 -21
  87. package/src/runtime/client/Runtime.js +0 -126
  88. package/src/runtime/client/StdRequest.js +0 -74
  89. package/src/runtime/client/Worker.js +0 -312
  90. package/src/runtime/client/WorkerComm.js +0 -183
  91. package/src/runtime/client/effects/sounds.js +0 -64
  92. package/src/runtime/index.js +0 -5
  93. package/src/runtime/server/NavigationEvent.js +0 -39
  94. package/src/runtime/server/Runtime.js +0 -593
  95. package/src/runtime/server/index.js +0 -183
  96. package/src/runtime/server/index.mjs +0 -10
  97. 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
- };