@webqit/webflo 0.11.61-0 → 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.
- package/.gitignore +7 -7
- package/LICENSE +20 -20
- package/README.md +2079 -2074
- package/docker/Dockerfile +42 -42
- package/docker/README.md +91 -91
- package/docker/package.json +2 -2
- package/package.json +80 -81
- package/src/{Context.js → AbstractContext.js} +71 -79
- package/src/config-pi/deployment/Env.js +68 -68
- package/src/config-pi/deployment/Layout.js +63 -63
- package/src/config-pi/deployment/Origins.js +139 -139
- package/src/config-pi/deployment/Proxy.js +74 -74
- package/src/config-pi/deployment/index.js +17 -17
- package/src/config-pi/index.js +15 -15
- package/src/config-pi/runtime/Client.js +116 -98
- package/src/config-pi/runtime/Server.js +125 -125
- package/src/config-pi/runtime/client/Worker.js +109 -134
- package/src/config-pi/runtime/client/index.js +11 -11
- package/src/config-pi/runtime/index.js +17 -17
- package/src/config-pi/runtime/server/Headers.js +74 -74
- package/src/config-pi/runtime/server/Redirects.js +69 -69
- package/src/config-pi/runtime/server/index.js +13 -13
- package/src/config-pi/static/Manifest.js +319 -319
- package/src/config-pi/static/Ssg.js +49 -49
- package/src/config-pi/static/index.js +13 -13
- package/src/deployment-pi/index.js +10 -10
- package/src/deployment-pi/origins/index.js +216 -216
- package/src/index.js +11 -19
- package/src/runtime-pi/HttpEvent.js +126 -106
- package/src/runtime-pi/HttpUser.js +126 -0
- package/src/runtime-pi/MessagingOverBroadcast.js +9 -0
- package/src/runtime-pi/MessagingOverChannel.js +85 -0
- package/src/runtime-pi/MessagingOverSocket.js +106 -0
- package/src/runtime-pi/MultiportMessagingAPI.js +81 -0
- package/src/runtime-pi/WebfloCookieStorage.js +27 -0
- package/src/runtime-pi/WebfloEventTarget.js +39 -0
- package/src/runtime-pi/WebfloMessageEvent.js +58 -0
- package/src/runtime-pi/WebfloMessagingAPI.js +69 -0
- package/src/runtime-pi/{Router.js → WebfloRouter.js} +99 -130
- package/src/runtime-pi/WebfloRuntime.js +52 -0
- package/src/runtime-pi/WebfloStorage.js +109 -0
- package/src/runtime-pi/client/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/Context.js +3 -7
- package/src/runtime-pi/client/CookieStorage.js +17 -0
- package/src/runtime-pi/client/Router.js +38 -48
- package/src/runtime-pi/client/SessionStorage.js +33 -0
- package/src/runtime-pi/client/Url.js +156 -205
- package/src/runtime-pi/client/WebfloClient.js +544 -0
- package/src/runtime-pi/client/WebfloRootClient1.js +179 -0
- package/src/runtime-pi/client/WebfloRootClient2.js +109 -0
- package/src/runtime-pi/client/WebfloSubClient.js +165 -0
- package/src/runtime-pi/client/Workport.js +118 -178
- package/src/runtime-pi/client/generate.js +480 -471
- package/src/runtime-pi/client/index.js +16 -21
- package/src/runtime-pi/client/worker/ClientMessaging.js +5 -0
- package/src/runtime-pi/client/worker/Context.js +3 -7
- package/src/runtime-pi/client/worker/CookieStorage.js +17 -0
- package/src/runtime-pi/client/worker/SessionStorage.js +13 -0
- package/src/runtime-pi/client/worker/WebfloWorker.js +294 -0
- package/src/runtime-pi/client/worker/Workport.js +17 -85
- package/src/runtime-pi/client/worker/index.js +10 -21
- package/src/runtime-pi/index.js +6 -13
- package/src/runtime-pi/server/ClientMessaging.js +18 -0
- package/src/runtime-pi/server/ClientMessagingRegistry.js +57 -0
- package/src/runtime-pi/server/Context.js +11 -15
- package/src/runtime-pi/server/CookieStorage.js +17 -0
- package/src/runtime-pi/server/Router.js +93 -159
- package/src/runtime-pi/server/SessionStorage.js +53 -0
- package/src/runtime-pi/server/WebfloServer.js +755 -0
- package/src/runtime-pi/server/index.js +10 -21
- package/src/runtime-pi/util-http.js +322 -86
- package/src/runtime-pi/util-url.js +146 -146
- package/src/runtime-pi/xURL.js +108 -105
- package/src/runtime-pi/xfetch.js +22 -22
- package/src/services-pi/cert/http-auth-hook.js +22 -22
- package/src/services-pi/cert/http-cleanup-hook.js +22 -22
- package/src/services-pi/cert/index.js +79 -79
- package/src/services-pi/index.js +8 -8
- package/src/static-pi/index.js +10 -10
- package/src/webflo.js +30 -30
- package/test/index.test.js +26 -26
- package/test/site/package.json +9 -9
- package/test/site/public/bundle.html +5 -5
- package/test/site/public/bundle.html.json +3 -3
- package/test/site/public/bundle.js +2 -2
- package/test/site/public/bundle.webflo.js +15 -15
- package/test/site/public/index.html +29 -29
- package/test/site/public/index1.html +34 -34
- package/test/site/public/page-2/bundle.html +4 -4
- package/test/site/public/page-2/bundle.js +2 -2
- package/test/site/public/page-2/index.html +45 -45
- package/test/site/public/page-2/main.html +2 -2
- package/test/site/public/page-4/subpage/bundle.js +2 -2
- package/test/site/public/page-4/subpage/index.html +30 -30
- package/test/site/public/sparoots.json +4 -4
- package/test/site/public/worker.js +3 -3
- package/test/site/server/index.js +15 -15
- package/src/runtime-pi/Application.js +0 -29
- package/src/runtime-pi/Cookies.js +0 -82
- package/src/runtime-pi/Runtime.js +0 -21
- package/src/runtime-pi/client/Application.js +0 -100
- package/src/runtime-pi/client/Runtime.js +0 -332
- package/src/runtime-pi/client/createStorage.js +0 -57
- package/src/runtime-pi/client/oohtml/full.js +0 -7
- package/src/runtime-pi/client/oohtml/namespacing.js +0 -7
- package/src/runtime-pi/client/oohtml/scripting.js +0 -8
- package/src/runtime-pi/client/oohtml/templating.js +0 -8
- package/src/runtime-pi/client/worker/Application.js +0 -44
- package/src/runtime-pi/client/worker/Runtime.js +0 -269
- package/src/runtime-pi/server/Application.js +0 -116
- package/src/runtime-pi/server/Runtime.js +0 -557
- package/src/runtime-pi/xFormData.js +0 -24
- package/src/runtime-pi/xHeaders.js +0 -146
- package/src/runtime-pi/xRequest.js +0 -46
- package/src/runtime-pi/xRequestHeaders.js +0 -109
- package/src/runtime-pi/xResponse.js +0 -33
- package/src/runtime-pi/xResponseHeaders.js +0 -117
- package/src/runtime-pi/xxHttpMessage.js +0 -102
|
@@ -1,557 +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/oohtml-ssr/apis.js';
|
|
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(this.cx, 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
|
-
}
|
|
215
|
-
return [ fullUrl, requestInit ];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Performs a request.
|
|
220
|
-
*
|
|
221
|
-
* @param object|string url
|
|
222
|
-
* @param object|Request init
|
|
223
|
-
* @param object detail
|
|
224
|
-
*
|
|
225
|
-
* @return Response
|
|
226
|
-
*/
|
|
227
|
-
async go(url, init = {}, detail = {}) {
|
|
228
|
-
await this.ready;
|
|
229
|
-
|
|
230
|
-
// ------------
|
|
231
|
-
url = typeof url === 'string' ? new URL(url) : url;
|
|
232
|
-
if (!(init instanceof Request) && !init.referrer) {
|
|
233
|
-
init = { referrer: this.location.href, ...init };
|
|
234
|
-
}
|
|
235
|
-
// ------------
|
|
236
|
-
const hosts = [];
|
|
237
|
-
this.servers.forEach(server => hosts.push(...server.domains));
|
|
238
|
-
// ------------
|
|
239
|
-
for (const [ /*id*/, vhost ] of this.proxied) {
|
|
240
|
-
if (vhost.hostnames.includes(url.hostname) || (vhost.hostnames.includes('*') && !hosts.includes('*'))) {
|
|
241
|
-
return this.proxyGo(vhost, url, init);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
// ------------
|
|
245
|
-
let exit, exitMessage;
|
|
246
|
-
if (!hosts.includes(url.hostname) && !hosts.includes('*')) {
|
|
247
|
-
exit = { status: 500 }, exitMessage = 'Unrecognized host';
|
|
248
|
-
} else if (url.protocol === 'http:' && this.cx.server.https.force) {
|
|
249
|
-
exit = { status: 302, headers: { Location: ( url.protocol = 'https:', url.href ) } };
|
|
250
|
-
} else if (url.hostname.startsWith('www.') && this.cx.server.force_www === 'remove') {
|
|
251
|
-
exit = { status: 302, headers: { Location: ( url.hostname = url.hostname.substr(4), url.href ) } };
|
|
252
|
-
} else if (!url.hostname.startsWith('www.') && this.cx.server.force_www === 'add') {
|
|
253
|
-
exit = { status: 302, headers: { Location: ( url.hostname = `www.${ url.hostname }`, url.href ) } };
|
|
254
|
-
} else if (this.cx.config.runtime.server.Redirects) {
|
|
255
|
-
exit = ((await (new this.cx.config.runtime.server.Redirects(this.cx)).read()).entries || []).reduce((_rdr, entry) => {
|
|
256
|
-
return _rdr || ((_rdr = pattern(entry.from, url.origin).exec(url.href)) && { status: entry.code || 302, headers: { Location: _rdr.render(entry.to) } });
|
|
257
|
-
}, null);
|
|
258
|
-
}
|
|
259
|
-
if (exit) { return new xResponse(exitMessage, exit); }
|
|
260
|
-
// ------------
|
|
261
|
-
|
|
262
|
-
// ------------
|
|
263
|
-
Observer.set(this.location, url, { detail: { init, ...detail, } });
|
|
264
|
-
Observer.set(this.network, 'redirecting', null);
|
|
265
|
-
// ------------
|
|
266
|
-
|
|
267
|
-
// ------------
|
|
268
|
-
// Automatically-added headers
|
|
269
|
-
const autoHeaders = this.cx.config.runtime.server.Headers
|
|
270
|
-
? ((await (new this.cx.config.runtime.server.Headers(this.cx)).read()).entries || []).filter(entry => pattern(entry.url, url.origin).exec(url.href))
|
|
271
|
-
: [];
|
|
272
|
-
// The request object
|
|
273
|
-
const request = this.generateRequest(url.href, init, autoHeaders.filter(header => header.type === 'request'));
|
|
274
|
-
// The navigation event
|
|
275
|
-
const httpEvent = new HttpEvent(request, detail, (id = 'session', options = { duration: 60 * 60 * 24, activeDuration: 60 * 60 * 24 }, callback = null) => {
|
|
276
|
-
return this.getSession(this.cx, httpEvent, id, options, callback);
|
|
277
|
-
});
|
|
278
|
-
// Response
|
|
279
|
-
let response, finalResponse;
|
|
280
|
-
try {
|
|
281
|
-
response = await this.app.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
282
|
-
finalResponse = await this.handleResponse(this.cx, httpEvent, response, autoHeaders.filter(header => header.type === 'response'));
|
|
283
|
-
} catch(e) {
|
|
284
|
-
finalResponse = new xResponse(e.message, { status: 500 });
|
|
285
|
-
console.error(e);
|
|
286
|
-
}
|
|
287
|
-
// Logging
|
|
288
|
-
if (this.cx.logger) {
|
|
289
|
-
const log = this.generateLog(httpEvent.request, finalResponse);
|
|
290
|
-
this.cx.logger.log(log);
|
|
291
|
-
}
|
|
292
|
-
// ------------
|
|
293
|
-
// Return value
|
|
294
|
-
return finalResponse;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Fetch from proxied host
|
|
298
|
-
async proxyGo(vhost, url, init) {
|
|
299
|
-
// ---------
|
|
300
|
-
const url2 = new URL(url);
|
|
301
|
-
url2.port = vhost.port;
|
|
302
|
-
if (vhost.proto) { url2.protocol = vhost.proto; }
|
|
303
|
-
// ---------
|
|
304
|
-
let init2;
|
|
305
|
-
if (init instanceof Request) {
|
|
306
|
-
init2 = init.clone();
|
|
307
|
-
init.headers.set('Host', url2.host);
|
|
308
|
-
} else {
|
|
309
|
-
init2 = { ...init, decompress: false/* honoured in xfetch() */ };
|
|
310
|
-
if (!init2.headers) init2.headers = {};
|
|
311
|
-
init2.headers.host = url2.host;
|
|
312
|
-
delete init2.headers.connection;
|
|
313
|
-
}
|
|
314
|
-
// ---------
|
|
315
|
-
let response;
|
|
316
|
-
try {
|
|
317
|
-
response = await this.remoteFetch(url2, init2);
|
|
318
|
-
} catch(e) {
|
|
319
|
-
response = new xResponse(`Reverse Proxy Error: ${e.message}`, { status: 500 });
|
|
320
|
-
console.error(e);
|
|
321
|
-
}
|
|
322
|
-
if (this.cx.logger) {
|
|
323
|
-
const log = this.generateLog({ url: url2.href, ...init2 }, response, true);
|
|
324
|
-
this.cx.logger.log(log);
|
|
325
|
-
}
|
|
326
|
-
return response;
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// Generates request object
|
|
332
|
-
generateRequest(href, init = {}, autoHeaders = []) {
|
|
333
|
-
const request = new xRequest(href, init);
|
|
334
|
-
this._autoHeaders(request.headers, autoHeaders);
|
|
335
|
-
return request;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Generates session object
|
|
339
|
-
getSession(cx, e, id, options = {}, callback = null) {
|
|
340
|
-
let baseObject;
|
|
341
|
-
if (!(e.detail.request && e.detail.response)) {
|
|
342
|
-
baseObject = this.mockSessionStore;
|
|
343
|
-
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
|
|
344
|
-
if (!(this.mockSessionStore[id] && cookieAvailability)) {
|
|
345
|
-
let cookieObj = {};
|
|
346
|
-
Object.defineProperty(this.mockSessionStore, id, {
|
|
347
|
-
get: () => cookieObj,
|
|
348
|
-
set: value => (cookieObj = value),
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
} else {
|
|
352
|
-
Sessions({
|
|
353
|
-
duration: 0, // how long the session will stay valid in ms
|
|
354
|
-
activeDuration: 0, // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
|
|
355
|
-
...options,
|
|
356
|
-
cookieName: id, // cookie name dictates the key name added to the request object
|
|
357
|
-
secret: cx.env.SESSION_KEY, // should be a large unguessable string
|
|
358
|
-
})(e.detail.request, e.detail.response, e => {
|
|
359
|
-
if (e) {
|
|
360
|
-
if (!callback) throw e;
|
|
361
|
-
callback(e);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
baseObject = e.detail.request;
|
|
365
|
-
}
|
|
366
|
-
// Where theres no error, instance is available
|
|
367
|
-
let instance = Object.getOwnPropertyDescriptor(baseObject, id);
|
|
368
|
-
if (!callback) return instance;
|
|
369
|
-
if (instance) callback(null, instance);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Initiates remote fetch and sets the status
|
|
373
|
-
remoteFetch(request, ...args) {
|
|
374
|
-
let href = request;
|
|
375
|
-
if (request instanceof Request) {
|
|
376
|
-
href = request.url;
|
|
377
|
-
} else if (request instanceof URL) {
|
|
378
|
-
href = request.href;
|
|
379
|
-
}
|
|
380
|
-
Observer.set(this.network, 'remote', href);
|
|
381
|
-
const _response = xfetch(request, ...args);
|
|
382
|
-
// This catch() is NOT intended to handle failure of the fetch
|
|
383
|
-
_response.catch(e => Observer.set(this.network, 'error', e.message));
|
|
384
|
-
// Save a reference to this
|
|
385
|
-
return _response.then(async response => {
|
|
386
|
-
// Stop loading status
|
|
387
|
-
Observer.set(this.network, 'remote', false);
|
|
388
|
-
return xResponse.compat(response);
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Handles response object
|
|
393
|
-
async handleResponse(cx, e, response, autoHeaders = []) {
|
|
394
|
-
if (!(response instanceof xResponse)) { response = xResponse.compat(response); }
|
|
395
|
-
Observer.set(this.network, 'remote', false);
|
|
396
|
-
Observer.set(this.network, 'error', null);
|
|
397
|
-
|
|
398
|
-
// ----------------
|
|
399
|
-
// Mock-Cookies?
|
|
400
|
-
if (!(e.detail.request && e.detail.response)) {
|
|
401
|
-
for (let cookieName of Object.getOwnPropertyNames(this.mockSessionStore)) {
|
|
402
|
-
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
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// ----------------
|
|
407
|
-
// Auto-Headers
|
|
408
|
-
response.headers.set('Accept-Ranges', 'bytes');
|
|
409
|
-
this._autoHeaders(response.headers, autoHeaders);
|
|
410
|
-
|
|
411
|
-
// ----------------
|
|
412
|
-
// Redirects
|
|
413
|
-
if (response.headers.location) {
|
|
414
|
-
const xRedirectPolicy = e.request.headers.get('X-Redirect-Policy');
|
|
415
|
-
const xRedirectCode = e.request.headers.get('X-Redirect-Code') || 300;
|
|
416
|
-
const destinationUrl = new URL(response.headers.location, e.url.origin);
|
|
417
|
-
const isSameOriginRedirect = destinationUrl.origin === e.url.origin;
|
|
418
|
-
let isSameSpaRedirect, sparootsFile = Path.join(cx.CWD, cx.layout.PUBLIC_DIR, 'sparoots.json');
|
|
419
|
-
if (isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-spa' && Fs.existsSync(sparootsFile)) {
|
|
420
|
-
// Longest-first sorting
|
|
421
|
-
const sparoots = _arrFrom(JSON.parse(Fs.readFileSync(sparootsFile))).sort((a, b) => a.length > b.length ? -1 : 1);
|
|
422
|
-
const matchRoot = path => sparoots.reduce((prev, root) => prev || (`${path}/`.startsWith(`${root}/`) && root), null);
|
|
423
|
-
isSameSpaRedirect = matchRoot(destinationUrl.pathname) === matchRoot(e.url.pathname);
|
|
424
|
-
}
|
|
425
|
-
if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (!isSameSpaRedirect && xRedirectPolicy === 'manual-when-cross-spa')) {
|
|
426
|
-
response.headers.json({
|
|
427
|
-
'X-Redirect-Code': response.status,
|
|
428
|
-
'Access-Control-Allow-Origin': '*',
|
|
429
|
-
'Cache-Control': 'no-store',
|
|
430
|
-
});
|
|
431
|
-
response.attrs.status = xRedirectCode;
|
|
432
|
-
}
|
|
433
|
-
return response;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// ----------------
|
|
437
|
-
// 404
|
|
438
|
-
if (!response.meta.body && response.meta.body !== 0) {
|
|
439
|
-
if (response.status === 200 || response.status === 0) {
|
|
440
|
-
response = new xResponse(response.body, { status: 404, headers: response.headers });
|
|
441
|
-
}
|
|
442
|
-
return response;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// ----------------
|
|
446
|
-
// Not acceptable
|
|
447
|
-
if (e.request.headers.get('Accept') && !e.request.headers.accept.match(response.headers.contentType)) {
|
|
448
|
-
response = new xResponse(response.body, { status: 406, headers: response.headers });
|
|
449
|
-
return response;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// ----------------
|
|
453
|
-
// Important no-caching
|
|
454
|
-
// for non-"get" requests
|
|
455
|
-
if (e.request.method !== 'GET' && !response.headers.get('Cache-Control')) {
|
|
456
|
-
response.headers.set('Cache-Control', 'no-store');
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// ----------------
|
|
460
|
-
// Body
|
|
461
|
-
let rangeRequest, body = response.body;
|
|
462
|
-
if ((rangeRequest = e.request.headers.range) && !response.headers.get('Content-Range')
|
|
463
|
-
&& ((body instanceof ReadableStream) || (ArrayBuffer.isView(body) && (body = _ReadableStream.from(body))))) {
|
|
464
|
-
// ...in partials
|
|
465
|
-
const totalLength = response.headers.contentLength || 0;
|
|
466
|
-
const ranges = await rangeRequest.reduce(async (_ranges, range) => {
|
|
467
|
-
_ranges = await _ranges;
|
|
468
|
-
if (range[0] < 0 || (totalLength && range[0] > totalLength)
|
|
469
|
-
|| (range[1] > -1 && (range[1] <= range[0] || (totalLength && range[1] >= totalLength)))) {
|
|
470
|
-
// The range is beyond upper/lower limits
|
|
471
|
-
_ranges.error = true;
|
|
472
|
-
}
|
|
473
|
-
if (!totalLength && range[0] === undefined) {
|
|
474
|
-
// totalLength is unknown and we cant read the trailing size specified in range[1]
|
|
475
|
-
_ranges.error = true;
|
|
476
|
-
}
|
|
477
|
-
if (_ranges.error) return _ranges;
|
|
478
|
-
if (totalLength) { range.clamp(totalLength); }
|
|
479
|
-
const partLength = range[1] - range[0] + 1;
|
|
480
|
-
_ranges.parts.push({
|
|
481
|
-
body: body.pipe(_streamSlice(range[0], range[1] + 1)),
|
|
482
|
-
range: range = `bytes ${range[0]}-${range[1]}/${totalLength || '*'}`,
|
|
483
|
-
length: partLength,
|
|
484
|
-
});
|
|
485
|
-
_ranges.totalLength += partLength;
|
|
486
|
-
return _ranges;
|
|
487
|
-
}, { parts: [], totalLength: 0 });
|
|
488
|
-
if (ranges.error) {
|
|
489
|
-
response.attrs.status = 416;
|
|
490
|
-
response.headers.json({
|
|
491
|
-
'Content-Range': `bytes */${totalLength || '*'}`,
|
|
492
|
-
'Content-Length': 0,
|
|
493
|
-
});
|
|
494
|
-
} else {
|
|
495
|
-
// TODO: of ranges.parts is more than one, return multipart/byteranges
|
|
496
|
-
response = new xResponse(ranges.parts[0].body, {
|
|
497
|
-
status: 206,
|
|
498
|
-
statusText: response.statusText,
|
|
499
|
-
headers: response.headers,
|
|
500
|
-
});
|
|
501
|
-
response.headers.json({
|
|
502
|
-
'Content-Range': ranges.parts[0].range,
|
|
503
|
-
'Content-Length': ranges.totalLength,
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return response;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Generates log
|
|
512
|
-
generateLog(request, response, isproxy = false) {
|
|
513
|
-
let log = [];
|
|
514
|
-
// ---------------
|
|
515
|
-
const style = this.cx.logger.style || { keyword: str => str, comment: str => str, url: str => str, val: str => str, err: str => str, };
|
|
516
|
-
const errorCode = [ 404, 500 ].includes(response.status) ? response.status : 0;
|
|
517
|
-
const xRedirectCode = response.headers.get('X-Redirect-Code');
|
|
518
|
-
const redirectCode = xRedirectCode || ((response.status + '').startsWith('3') ? response.status : 0);
|
|
519
|
-
const statusCode = xRedirectCode || response.status;
|
|
520
|
-
// ---------------
|
|
521
|
-
log.push(`[${style.comment((new Date).toUTCString())}]`);
|
|
522
|
-
log.push(style.keyword(request.method));
|
|
523
|
-
if (isproxy) log.push(style.keyword('>>'));
|
|
524
|
-
log.push(style.url(request.url));
|
|
525
|
-
if (response.attrs.hint) log.push(`(${style.comment(response.attrs.hint)})`);
|
|
526
|
-
const contentInfo = [response.headers.contentType, response.headers.contentLength].filter(x => x);
|
|
527
|
-
if (contentInfo.length) log.push(`(${style.comment(contentInfo.join('; '))})`);
|
|
528
|
-
if (response.headers.get('Content-Encoding')) log.push(`(${style.comment(response.headers.get('Content-Encoding'))})`);
|
|
529
|
-
if (errorCode) log.push(style.err(`${errorCode} ${response.statusText}`));
|
|
530
|
-
else log.push(style.val(`${statusCode} ${response.statusText}`));
|
|
531
|
-
if (redirectCode) log.push(`- ${style.url(response.headers.location)}`);
|
|
532
|
-
|
|
533
|
-
return log.join(' ');
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Applies auto headers
|
|
537
|
-
_autoHeaders(headers, autoHeaders) {
|
|
538
|
-
autoHeaders.forEach(header => {
|
|
539
|
-
var headerName = header.name.toLowerCase(),
|
|
540
|
-
headerValue = header.value,
|
|
541
|
-
isAppend = headerName.startsWith('+') ? (headerName = headerName.substr(1), true) : false,
|
|
542
|
-
isPrepend = headerName.endsWith('+') ? (headerName = headerName.substr(0, headerName.length - 1), true) : false;
|
|
543
|
-
if (isAppend || isPrepend) {
|
|
544
|
-
headerValue = [ headers.get(headerName) || '' , headerValue].filter(v => v);
|
|
545
|
-
headerValue = isPrepend ? headerValue.reverse().join(',') : headerValue.join(',');
|
|
546
|
-
}
|
|
547
|
-
headers.set(headerName, headerValue);
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const _streamRead = stream => new Promise(res => {
|
|
554
|
-
let data = '';
|
|
555
|
-
stream.on('data', chunk => data += chunk);
|
|
556
|
-
stream.on('end', () => res(data));
|
|
557
|
-
});
|
|
@@ -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
|
-
}
|