@webqit/webflo 0.11.61 → 1.0.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 (66) hide show
  1. package/package.json +1 -1
  2. package/src/{Context.js → AbstractContext.js} +1 -9
  3. package/src/deployment-pi/origins/index.js +1 -1
  4. package/src/index.js +1 -9
  5. package/src/runtime-pi/HttpEvent.js +101 -81
  6. package/src/runtime-pi/HttpUser.js +126 -0
  7. package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
  8. package/src/runtime-pi/MessagingOverChannel.js +85 -0
  9. package/src/runtime-pi/MessagingOverSocket.js +106 -0
  10. package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
  11. package/src/runtime-pi/WebfloCookieStorage.js +27 -0
  12. package/src/runtime-pi/WebfloEventTarget.js +39 -0
  13. package/src/runtime-pi/WebfloMessageEvent.js +58 -0
  14. package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
  15. package/src/runtime-pi/{Router.js → WebfloRouter.js} +3 -34
  16. package/src/runtime-pi/WebfloRuntime.js +52 -0
  17. package/src/runtime-pi/WebfloStorage.js +109 -0
  18. package/src/runtime-pi/client/ClientMessaging.js +5 -0
  19. package/src/runtime-pi/client/Context.js +2 -6
  20. package/src/runtime-pi/client/CookieStorage.js +17 -0
  21. package/src/runtime-pi/client/Router.js +3 -13
  22. package/src/runtime-pi/client/SessionStorage.js +33 -0
  23. package/src/runtime-pi/client/Url.js +24 -72
  24. package/src/runtime-pi/client/WebfloClient.js +544 -0
  25. package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
  26. package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
  27. package/src/runtime-pi/client/WebfloSubClient.js +165 -0
  28. package/src/runtime-pi/client/Workport.js +89 -161
  29. package/src/runtime-pi/client/generate.js +1 -1
  30. package/src/runtime-pi/client/index.js +13 -18
  31. package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
  32. package/src/runtime-pi/client/worker/Context.js +2 -6
  33. package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
  34. package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
  35. package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
  36. package/src/runtime-pi/client/worker/Workport.js +13 -73
  37. package/src/runtime-pi/client/worker/index.js +7 -18
  38. package/src/runtime-pi/index.js +1 -8
  39. package/src/runtime-pi/server/ClientMessaging.js +18 -0
  40. package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
  41. package/src/runtime-pi/server/Context.js +2 -6
  42. package/src/runtime-pi/server/CookieStorage.js +17 -0
  43. package/src/runtime-pi/server/Router.js +2 -68
  44. package/src/runtime-pi/server/SessionStorage.js +53 -0
  45. package/src/runtime-pi/server/WebfloServer.js +755 -0
  46. package/src/runtime-pi/server/index.js +7 -18
  47. package/src/runtime-pi/util-http.js +268 -32
  48. package/src/runtime-pi/xURL.js +25 -22
  49. package/src/runtime-pi/xfetch.js +2 -2
  50. package/src/runtime-pi/Application.js +0 -29
  51. package/src/runtime-pi/Cookies.js +0 -82
  52. package/src/runtime-pi/Runtime.js +0 -21
  53. package/src/runtime-pi/client/Application.js +0 -76
  54. package/src/runtime-pi/client/Runtime.js +0 -525
  55. package/src/runtime-pi/client/createStorage.js +0 -58
  56. package/src/runtime-pi/client/worker/Application.js +0 -44
  57. package/src/runtime-pi/client/worker/Runtime.js +0 -275
  58. package/src/runtime-pi/server/Application.js +0 -101
  59. package/src/runtime-pi/server/Runtime.js +0 -558
  60. package/src/runtime-pi/xFormData.js +0 -24
  61. package/src/runtime-pi/xHeaders.js +0 -146
  62. package/src/runtime-pi/xRequest.js +0 -46
  63. package/src/runtime-pi/xRequestHeaders.js +0 -109
  64. package/src/runtime-pi/xResponse.js +0 -33
  65. package/src/runtime-pi/xResponseHeaders.js +0 -117
  66. package/src/runtime-pi/xxHttpMessage.js +0 -102
@@ -1,558 +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 Sessions from 'client-sessions';
10
- import Observer from '@webqit/observer';
11
- import { _each } from '@webqit/util/obj/index.js';
12
- import { _isEmpty } from '@webqit/util/js/index.js';
13
- import { _from as _arrFrom, _any } from '@webqit/util/arr/index.js';
14
- import { slice as _streamSlice } from 'stream-slice';
15
- import { Readable as _ReadableStream } from 'stream';
16
- import { pattern } from '../util-url.js';
17
- import xRequest from "../xRequest.js";
18
- import xResponse from "../xResponse.js";
19
- import xfetch from '../xfetch.js';
20
- import HttpEvent from '../HttpEvent.js';
21
- import _Runtime from '../Runtime.js';
22
-
23
- export {
24
- //fetch,
25
- HttpEvent,
26
- Observer,
27
- }
28
-
29
- export default class Runtime extends _Runtime {
30
-
31
- /**
32
- * Runtime
33
- *
34
- * @param Object cx
35
- * @param Function applicationInstance
36
- *
37
- * @return void
38
- */
39
- constructor(cx, applicationInstance) {
40
- super(cx, applicationInstance);
41
- // ---------------
42
- this.ready = (async () => {
43
- // ---------------
44
- const resolveContextObj = async (cx, force = false) => {
45
- if (_isEmpty(cx.layout) || force) { cx.layout = await (new cx.config.deployment.Layout(cx)).read(); }
46
- if (_isEmpty(cx.server) || force) { cx.server = await (new cx.config.runtime.Server(cx)).read(); }
47
- if (_isEmpty(cx.env) || force) { cx.env = await (new cx.config.deployment.Env(cx)).read(); }
48
- };
49
- await resolveContextObj(this.cx);
50
- if (this.cx.env.autoload !== false) {
51
- Object.keys(this.cx.env.entries).forEach(key => {
52
- if (!(key in process.env)) {
53
- process.env[key] = this.cx.env.entries[key];
54
- }
55
- });
56
- }
57
- // ---------------
58
- const parseDomains = domains => _arrFrom(domains).reduce((arr, str) => arr.concat(str.split(',')), []).map(str => str.trim()).filter(str => str);
59
- const selectDomains = (serverDefs, matchingPort = null) => serverDefs.reduce((doms, def) => doms.length ? doms : (((!matchingPort || def.port === matchingPort) && parseDomains(def.domains || def.hostnames)) || []), []);
60
- // ---------------
61
- this.proxied = new Map;
62
- if (this.cx.config.deployment.Proxy) {
63
- const proxied = await (new this.cx.config.deployment.Proxy(this.cx)).read();
64
- await Promise.all((proxied.entries || []).map(async vhost => {
65
- let cx, hostnames = parseDomains(vhost.hostnames), port = vhost.port, proto = vhost.proto;
66
- if (vhost.path) {
67
- cx = this.cx.constructor.create(this.cx, Path.join(this.cx.CWD, vhost.path));
68
- await resolveContextObj(cx, true);
69
- cx.dict.key = true;
70
- // From the server that's most likely to be active
71
- port || (port = cx.server.https.port || cx.server.port);
72
- // The domain list that corresponds to the specified resolved port
73
- hostnames.length || (hostnames = selectDomains([cx.server.https, cx.server], port));
74
- // Or anyone available... hoping that the remote configs can eventually be in sync
75
- hostnames.length || (hostnames = selectDomains([cx.server.https, cx.server]));
76
- // The corresponding proto
77
- proto || (proto = port === cx.server.https.port ? 'https' : 'http');
78
- }
79
- hostnames.length || (hostnames = ['*']);
80
- this.proxied.set(hostnames.sort().join('|'), { cx, hostnames, port, proto });
81
- }));
82
- }
83
- // ---------------
84
- this.servers = new Map;
85
- // ---------------
86
- if (!this.cx.flags['test-only'] && !this.cx.flags['https-only'] && this.cx.server.port) {
87
- const httpServer = Http.createServer((request, response) => handleRequest('http', request, response));
88
- httpServer.listen(this.cx.server.port);
89
- // -------
90
- let domains = parseDomains(this.cx.server.domains);
91
- if (!domains.length) { domains = ['*']; }
92
- this.servers.set('http', {
93
- instance: httpServer,
94
- port: this.cx.server.port,
95
- domains,
96
- });
97
- }
98
- // ---------------
99
- if (!this.cx.flags['test-only'] && !this.cx.flags['http-only'] && this.cx.server.https.port) {
100
- const httpsServer = Https.createServer((request, response) => handleRequest('https', request, response));
101
- httpsServer.listen(this.cx.server.https.port);
102
- // -------
103
- const addSSLContext = (serverConfig, domains) => {
104
- if (!Fs.existsSync(serverConfig.https.keyfile)) return;
105
- const cert = {
106
- key: Fs.readFileSync(serverConfig.https.keyfile),
107
- cert: Fs.readFileSync(serverConfig.https.certfile),
108
- };
109
- domains.forEach(domain => { httpsServer.addContext(domain, cert); });
110
- }
111
- // -------
112
- let domains = parseDomains(this.cx.server.https.domains);
113
- if (!domains.length) { domains = ['*']; }
114
- this.servers.set('https', {
115
- instance: httpsServer,
116
- port: this.cx.server.https.port,
117
- domains,
118
- });
119
- // -------
120
- addSSLContext(this.cx.server, domains);
121
- for (const [ /*id*/, vhost ] of this.proxied) {
122
- vhost.cx && addSSLContext(vhost.cx.server, vhost.hostnames);
123
- }
124
- }
125
- // ---------------
126
- const handleRequest = async (proto, request, response) => {
127
- request[Symbol.toStringTag] = 'ReadableStream';
128
- const [ fullUrl, requestInit ] = await this.parseNodeRequest(proto, request);
129
- let clientResponse = await this.go(fullUrl, requestInit, { request, response });
130
- if (response.headersSent) return;
131
- // --------
132
- _each(clientResponse.headers.json(), (name, value) => {
133
- response.setHeader(name, value);
134
- });
135
- // --------
136
- response.statusCode = clientResponse.status;
137
- response.statusMessage = clientResponse.statusText;
138
- if (clientResponse.headers.location) {
139
- return response.end();
140
- }
141
- if ((clientResponse.body instanceof _ReadableStream)) {
142
- return clientResponse.body.pipe(response);
143
- }
144
- if ((clientResponse.body instanceof ReadableStream)) {
145
- return _ReadableStream.from(clientResponse.body).pipe(response);
146
- }
147
- let body = clientResponse.body;
148
- if (clientResponse.headers.contentType === 'application/json') {
149
- body += '';
150
- }
151
- return response.end(body);
152
- };
153
- })();
154
-
155
- // ---------------
156
- Observer.set(this, 'location', {});
157
- Observer.set(this, 'network', {});
158
- // ---------------
159
-
160
- // -------------
161
- // Initialize
162
- (async () => {
163
- await this.ready;
164
- if (this.cx.logger) {
165
- if (this.servers.size) {
166
- this.cx.logger.info(`> Server running! (${this.cx.app.title || ''})`);
167
- for (let [ proto, def ] of this.servers) {
168
- this.cx.logger.info(`> ${ proto.toUpperCase() } / ${ def.domains.concat('').join(`:${ def.port } / `)}`);
169
- }
170
- } else {
171
- this.cx.logger.info(`> Server not running! No port specified.`);
172
- }
173
- if (this.proxied.size) {
174
- this.cx.logger.info(`> Reverse proxy active.`);
175
- for (let [ id, def ] of this.proxied) {
176
- this.cx.logger.info(`> ${ id } >>> ${ def.port }`);
177
- }
178
- }
179
- this.cx.logger.info(``);
180
- }
181
- if (this.app && this.app.init) {
182
- const request = this.generateRequest('http://localhost/');
183
- const httpEvent = new HttpEvent(request, { srcType: 'initialization' }, (id = 'session', options = { duration: 60 * 60 * 24, activeDuration: 60 * 60 * 24 }, callback = null) => {
184
- return this.getSession(httpEvent, id, options, callback);
185
- });
186
- await this.app.init(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
187
- }
188
- })();
189
- // ---------------
190
-
191
- // ---------------
192
- this.mockSessionStore = {};
193
- // ---------------
194
-
195
- }
196
-
197
- /**
198
- * Parse Nodejs's IncomingMessage to WHATWAG request params.
199
- *
200
- * @param String proto
201
- * @param Http.IncomingMessage request
202
- *
203
- * @return Array
204
- */
205
- async parseNodeRequest(proto, request) {
206
- // Detected when using manual proxy setting in a browser
207
- if (request.url.startsWith(`http://${ request.headers.host }`) || request.url.startsWith(`https://${ request.headers.host }`)) {
208
- request.url = request.url.split(request.headers.host)[1];
209
- }
210
- const fullUrl = proto + '://' + request.headers.host + request.url;
211
- const requestInit = { method: request.method, headers: request.headers };
212
- if (!['GET', 'HEAD'].includes(request.method)) {
213
- requestInit.body = request;
214
- requestInit.duplex = 'half'; // See https://github.com/nodejs/node/issues/46221
215
- }
216
- return [ fullUrl, requestInit ];
217
- }
218
-
219
- /**
220
- * Performs a request.
221
- *
222
- * @param object|string url
223
- * @param object|Request init
224
- * @param object detail
225
- *
226
- * @return Response
227
- */
228
- async go(url, init = {}, detail = {}) {
229
- await this.ready;
230
-
231
- // ------------
232
- url = typeof url === 'string' ? new URL(url) : url;
233
- if (!(init instanceof Request) && !init.referrer) {
234
- init = { referrer: this.location.href, ...init };
235
- }
236
- // ------------
237
- const hosts = [];
238
- this.servers.forEach(server => hosts.push(...server.domains));
239
- // ------------
240
- for (const [ /*id*/, vhost ] of this.proxied) {
241
- if (vhost.hostnames.includes(url.hostname) || (vhost.hostnames.includes('*') && !hosts.includes('*'))) {
242
- return this.proxyGo(vhost, url, init);
243
- }
244
- }
245
- // ------------
246
- let exit, exitMessage;
247
- if (!hosts.includes(url.hostname) && !hosts.includes('*')) {
248
- exit = { status: 500 }, exitMessage = 'Unrecognized host';
249
- } else if (url.protocol === 'http:' && this.cx.server.https.force) {
250
- exit = { status: 302, headers: { Location: ( url.protocol = 'https:', url.href ) } };
251
- } else if (url.hostname.startsWith('www.') && this.cx.server.force_www === 'remove') {
252
- exit = { status: 302, headers: { Location: ( url.hostname = url.hostname.substr(4), url.href ) } };
253
- } else if (!url.hostname.startsWith('www.') && this.cx.server.force_www === 'add') {
254
- exit = { status: 302, headers: { Location: ( url.hostname = `www.${ url.hostname }`, url.href ) } };
255
- } else if (this.cx.config.runtime.server.Redirects) {
256
- exit = ((await (new this.cx.config.runtime.server.Redirects(this.cx)).read()).entries || []).reduce((_rdr, entry) => {
257
- return _rdr || ((_rdr = pattern(entry.from, url.origin).exec(url.href)) && { status: entry.code || 302, headers: { Location: _rdr.render(entry.to) } });
258
- }, null);
259
- }
260
- if (exit) { return new xResponse(exitMessage, exit); }
261
- // ------------
262
-
263
- // ------------
264
- Observer.set(this.location, url, { detail: { init, ...detail, } });
265
- Observer.set(this.network, 'error', null, { diff: true });
266
- // ------------
267
-
268
- // ------------
269
- // Automatically-added headers
270
- const autoHeaders = this.cx.config.runtime.server.Headers
271
- ? ((await (new this.cx.config.runtime.server.Headers(this.cx)).read()).entries || []).filter(entry => pattern(entry.url, url.origin).exec(url.href))
272
- : [];
273
- // The request object
274
- const request = this.generateRequest(url.href, init, autoHeaders.filter(header => header.type === 'request'));
275
- // The navigation event
276
- const httpEvent = new HttpEvent(request, detail, (id = 'session', options = { duration: 60 * 60 * 24, activeDuration: 60 * 60 * 24 }, callback = null) => {
277
- return this.getSession(httpEvent, id, options, callback);
278
- });
279
- // Response
280
- let response;
281
- try {
282
- response = await this.app.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
283
- if (!response && response !== 0) { response = new xResponse(null, { status: 404 }); }
284
- else if (!(response instanceof xResponse)) { response = xResponse.compat(response); }
285
- if (!(httpEvent.detail.request && httpEvent.detail.response)) {
286
- for (let cookieName of Object.getOwnPropertyNames(this.mockSessionStore)) {
287
- response.headers.append('Set-Cookie', `${cookieName}=1`); // We just want to know availability... not validity, as this is understood to be for testing purposes only
288
- }
289
- }
290
- response = await this.encodeRedirect(httpEvent, response, async () => {
291
- if (httpEvent.request.headers.accept.match('text/html') && this.app.render && !response.attrs.static) {
292
- let rendering;
293
- if (response.ok && response.meta.type === 'json' && typeof response.meta.body === 'object' && response.meta.body) {
294
- rendering = await this.app.render(httpEvent, response);
295
- } else if (!response.ok) {
296
- if ([404, 500].includes(response.status)) {
297
- Observer.set(this.network, 'error', new Error(response.statusText, { cause: response.status }));
298
- }
299
- rendering = await this.app.render(httpEvent, response);
300
- }
301
- if (typeof rendering !== 'string' && !(typeof rendering === 'object' && rendering && typeof rendering.toString === 'function')) {
302
- throw new Error('render() must return a string response or an object that implements toString()..');
303
- }
304
- rendering = rendering.toString();
305
- response = new httpEvent.Response(rendering, {
306
- headers: { ...response.headers.json(), contentType: 'text/html', contentLength: (new Blob([rendering]).size) },
307
- status: response.status,
308
- });
309
- }
310
- return this.handleResponse2(httpEvent, autoHeaders.filter(header => header.type === 'response'), response);
311
- });
312
- } catch(e) {
313
- response = new xResponse(e.message, { status: 500 });
314
- console.error(e);
315
- }
316
- // Logging
317
- if (this.cx.logger) {
318
- const log = this.generateLog(httpEvent.request, response);
319
- this.cx.logger.log(log);
320
- }
321
- // ------------
322
- // Return value
323
- return response;
324
- }
325
-
326
- // Fetch from proxied host
327
- async proxyGo(vhost, url, init) {
328
- // ---------
329
- const url2 = new URL(url);
330
- url2.port = vhost.port;
331
- if (vhost.proto) { url2.protocol = vhost.proto; }
332
- // ---------
333
- let init2;
334
- if (init instanceof Request) {
335
- init2 = init.clone();
336
- init.headers.set('Host', url2.host);
337
- } else {
338
- init2 = { ...init, decompress: false/* honoured in xfetch() */ };
339
- if (!init2.headers) init2.headers = {};
340
- init2.headers.host = url2.host;
341
- delete init2.headers.connection;
342
- }
343
- // ---------
344
- let response;
345
- try {
346
- response = await this.remoteFetch(url2, init2);
347
- } catch(e) {
348
- response = new xResponse(`Reverse Proxy Error: ${e.message}`, { status: 500 });
349
- console.error(e);
350
- }
351
- if (this.cx.logger) {
352
- const log = this.generateLog({ url: url2.href, ...init2 }, response, true);
353
- this.cx.logger.log(log);
354
- }
355
- return response;
356
-
357
- }
358
-
359
-
360
- // Generates request object
361
- generateRequest(href, init = {}, autoHeaders = []) {
362
- const request = new xRequest(href, init);
363
- this._autoHeaders(request.headers, autoHeaders);
364
- return request;
365
- }
366
-
367
- // Generates session object
368
- getSession(e, id, options = {}, callback = null) {
369
- let baseObject;
370
- if (!(e.detail.request && e.detail.response)) {
371
- baseObject = this.mockSessionStore;
372
- let cookieAvailability = e.request.headers.cookies.get(id); // We just want to know availability... not validity, as this is understood to be for testing purposes only
373
- if (!(this.mockSessionStore[id] && cookieAvailability)) {
374
- let cookieObj = {};
375
- Object.defineProperty(this.mockSessionStore, id, {
376
- get: () => cookieObj,
377
- set: value => (cookieObj = value),
378
- });
379
- }
380
- } else {
381
- Sessions({
382
- duration: 0, // how long the session will stay valid in ms
383
- activeDuration: 0, // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
384
- ...options,
385
- cookieName: id, // cookie name dictates the key name added to the request object
386
- secret: this.cx.env.SESSION_KEY, // should be a large unguessable string
387
- })(e.detail.request, e.detail.response, e => {
388
- if (e) {
389
- if (!callback) throw e;
390
- callback(e);
391
- }
392
- });
393
- baseObject = e.detail.request;
394
- }
395
- // Where theres no error, instance is available
396
- let instance = Object.getOwnPropertyDescriptor(baseObject, id);
397
- if (!callback) return instance;
398
- if (instance) callback(null, instance);
399
- }
400
-
401
- // Initiates remote fetch and sets the status
402
- remoteFetch(request, ...args) {
403
- let href = request;
404
- if (request instanceof Request) {
405
- href = request.url;
406
- } else if (request instanceof URL) {
407
- href = request.href;
408
- }
409
- Observer.set(this.network, 'remote', href, {diff: true});
410
- const _response = xfetch(request, ...args);
411
- // Save a reference to this
412
- return _response.then(async response => {
413
- // Stop loading status
414
- Observer.set(this.network, 'remote', false, {diff: true});
415
- return xResponse.compat(response);
416
- });
417
- }
418
-
419
- // Handles response object
420
- encodeRedirect(httpEvent, response, callback) {
421
- // Redirects
422
- if (response.headers.location) {
423
- const xRedirectPolicy = httpEvent.request.headers.get('X-Redirect-Policy');
424
- const xRedirectCode = httpEvent.request.headers.get('X-Redirect-Code') || 300;
425
- const destinationUrl = new URL(response.headers.location, httpEvent.url.origin);
426
- const isSameOriginRedirect = destinationUrl.origin === httpEvent.url.origin;
427
- let isSameSpaRedirect, sparootsFile = Path.join(this.cx.CWD, this.cx.layout.PUBLIC_DIR, 'sparoots.json');
428
- if (isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-spa' && Fs.existsSync(sparootsFile)) {
429
- // Longest-first sorting
430
- const sparoots = _arrFrom(JSON.parse(Fs.readFileSync(sparootsFile))).sort((a, b) => a.length > b.length ? -1 : 1);
431
- const matchRoot = path => sparoots.reduce((prev, root) => prev || (`${path}/`.startsWith(`${root}/`) && root), null);
432
- isSameSpaRedirect = matchRoot(destinationUrl.pathname) === matchRoot(httpEvent.url.pathname);
433
- }
434
- if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (!isSameSpaRedirect && xRedirectPolicy === 'manual-when-cross-spa')) {
435
- response.headers.json({
436
- 'X-Redirect-Code': response.status,
437
- 'Access-Control-Allow-Origin': '*',
438
- 'Cache-Control': 'no-store',
439
- });
440
- response.attrs.status = xRedirectCode;
441
- }
442
- return response;
443
- }
444
- return callback();
445
- }
446
-
447
- // Handles response object
448
- async handleResponse2(httpEvent, autoHeaders, response) {
449
- // Not acceptable
450
- if (response.headers.contentType && httpEvent.request.headers.get('Accept') && !httpEvent.request.headers.accept.match(response.headers.contentType)) {
451
- return new xResponse(response.body, { status: 406, headers: response.headers });
452
- }
453
- // Auto-Headers
454
- this._autoHeaders(response.headers, autoHeaders);
455
- // Important no-caching
456
- // for non-"get" requests
457
- if (httpEvent.request.method !== 'GET' && !response.headers.get('Cache-Control')) {
458
- response.headers.set('Cache-Control', 'no-store');
459
- }
460
- // Body
461
- response.headers.set('Accept-Ranges', 'bytes');
462
- let rangeRequest, body = response.body;
463
- if ((rangeRequest = httpEvent.request.headers.range) && !response.headers.get('Content-Range')
464
- && ((body instanceof ReadableStream) || (ArrayBuffer.isView(body) && (body = _ReadableStream.from(body))))) {
465
- // ...in partials
466
- const totalLength = response.headers.contentLength || 0;
467
- const ranges = await rangeRequest.reduce(async (_ranges, range) => {
468
- _ranges = await _ranges;
469
- if (range[0] < 0 || (totalLength && range[0] > totalLength)
470
- || (range[1] > -1 && (range[1] <= range[0] || (totalLength && range[1] >= totalLength)))) {
471
- // The range is beyond upper/lower limits
472
- _ranges.error = true;
473
- }
474
- if (!totalLength && range[0] === undefined) {
475
- // totalLength is unknown and we cant read the trailing size specified in range[1]
476
- _ranges.error = true;
477
- }
478
- if (_ranges.error) return _ranges;
479
- if (totalLength) { range.clamp(totalLength); }
480
- const partLength = range[1] - range[0] + 1;
481
- _ranges.parts.push({
482
- body: body.pipe(_streamSlice(range[0], range[1] + 1)),
483
- range: range = `bytes ${range[0]}-${range[1]}/${totalLength || '*'}`,
484
- length: partLength,
485
- });
486
- _ranges.totalLength += partLength;
487
- return _ranges;
488
- }, { parts: [], totalLength: 0 });
489
- if (ranges.error) {
490
- response.attrs.status = 416;
491
- response.headers.json({
492
- 'Content-Range': `bytes */${totalLength || '*'}`,
493
- 'Content-Length': 0,
494
- });
495
- } else {
496
- // TODO: of ranges.parts is more than one, return multipart/byteranges
497
- response = new xResponse(ranges.parts[0].body, {
498
- status: 206,
499
- statusText: response.statusText,
500
- headers: response.headers,
501
- });
502
- response.headers.json({
503
- 'Content-Range': ranges.parts[0].range,
504
- 'Content-Length': ranges.totalLength,
505
- });
506
- }
507
- }
508
-
509
- return response;
510
- }
511
-
512
- // Generates log
513
- generateLog(request, response, isproxy = false) {
514
- let log = [];
515
- // ---------------
516
- const style = this.cx.logger.style || { keyword: str => str, comment: str => str, url: str => str, val: str => str, err: str => str, };
517
- const errorCode = [ 404, 500 ].includes(response.status) ? response.status : 0;
518
- const xRedirectCode = response.headers.get('X-Redirect-Code');
519
- const redirectCode = xRedirectCode || ((response.status + '').startsWith('3') ? response.status : 0);
520
- const statusCode = xRedirectCode || response.status;
521
- // ---------------
522
- log.push(`[${style.comment((new Date).toUTCString())}]`);
523
- log.push(style.keyword(request.method));
524
- if (isproxy) log.push(style.keyword('>>'));
525
- log.push(style.url(request.url));
526
- if (response.attrs.hint) log.push(`(${style.comment(response.attrs.hint)})`);
527
- const contentInfo = [response.headers.contentType, response.headers.contentLength].filter(x => x);
528
- if (contentInfo.length) log.push(`(${style.comment(contentInfo.join('; '))})`);
529
- if (response.headers.get('Content-Encoding')) log.push(`(${style.comment(response.headers.get('Content-Encoding'))})`);
530
- if (errorCode) log.push(style.err(`${errorCode} ${response.statusText}`));
531
- else log.push(style.val(`${statusCode} ${response.statusText}`));
532
- if (redirectCode) log.push(`- ${style.url(response.headers.location)}`);
533
-
534
- return log.join(' ');
535
- }
536
-
537
- // Applies auto headers
538
- _autoHeaders(headers, autoHeaders) {
539
- autoHeaders.forEach(header => {
540
- var headerName = header.name.toLowerCase(),
541
- headerValue = header.value,
542
- isAppend = headerName.startsWith('+') ? (headerName = headerName.substr(1), true) : false,
543
- isPrepend = headerName.endsWith('+') ? (headerName = headerName.substr(0, headerName.length - 1), true) : false;
544
- if (isAppend || isPrepend) {
545
- headerValue = [ headers.get(headerName) || '' , headerValue].filter(v => v);
546
- headerValue = isPrepend ? headerValue.reverse().join(',') : headerValue.join(',');
547
- }
548
- headers.set(headerName, headerValue);
549
- });
550
- }
551
-
552
- }
553
-
554
- const _streamRead = stream => new Promise(res => {
555
- let data = '';
556
- stream.on('data', chunk => data += chunk);
557
- stream.on('end', () => res(data));
558
- });
@@ -1,24 +0,0 @@
1
-
2
- /**
3
- * @imports
4
- */
5
- import { jsonfyFormData } from './util-http.js';
6
-
7
- /**
8
- * The _Headers Mixin
9
- */
10
- export default class xFormData extends FormData {
11
-
12
- async json(data = {}) {
13
- const result = await jsonfyFormData(this, ...arguments);
14
- return result[0];
15
- }
16
-
17
- static compat(formData) {
18
- if (formData instanceof this) return formData;
19
- if (formData instanceof FormData) {
20
- return Object.setPrototypeOf(formData, new this);
21
- }
22
- }
23
-
24
- }