@chidchanun/bcp 0.1.8 → 0.1.9
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/CHANGELOG.md +41 -0
- package/README.md +82 -4
- package/docs/application-modules.md +63 -3
- package/docs/server-data-loaders.md +240 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production.ts +25 -0
- package/packages/client/src/index.tsx +4 -0
- package/packages/client/src/loader-data.tsx +99 -0
- package/packages/client/src/router-v2.tsx +254 -29
- package/packages/server/src/dev-navigation-target.ts +188 -0
- package/packages/server/src/index.ts +209 -119
- package/packages/server/src/navigation-payload.ts +24 -1
- package/packages/server/src/navigation-response.ts +284 -0
- package/packages/server/src/page-loader.ts +346 -0
- package/packages/server/src/standalone-production-runtime-v2-navigation.ts +785 -0
- package/packages/server/src/standalone-production-runtime-v2.ts +131 -19
- package/packages/server/src/standalone-production-runtime-v3.ts +1 -1
- package/packages/server/src/standalone-production-runtime-v4.ts +42 -2
- package/packages/server/src/static-dev-server.ts +21 -17
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createElement,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
renderToStaticMarkup,
|
|
10
|
+
} from "react-dom/server";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
isNotFoundError,
|
|
14
|
+
} from "../../client/src/not-found.js";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
matchRoute,
|
|
18
|
+
type Route,
|
|
19
|
+
} from "../../router/src/index.js";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
resolveRouteMetadata,
|
|
23
|
+
} from "./metadata.js";
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
attachLoaderDataToParams,
|
|
27
|
+
executePageLoaderFunction,
|
|
28
|
+
type PageLoaderExecution,
|
|
29
|
+
} from "./page-loader.js";
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
createNavigationJsonResponse,
|
|
33
|
+
createNavigationRedirectResponse,
|
|
34
|
+
sendNavigationWebResponse,
|
|
35
|
+
} from "./navigation-response.js";
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
applyResponseCookies,
|
|
39
|
+
runWithRequestContext,
|
|
40
|
+
} from "./request-context.js";
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
createStandaloneProductionServer as createBaseStandaloneProductionServer,
|
|
44
|
+
type StandaloneApiRoute,
|
|
45
|
+
type StandalonePageRoute,
|
|
46
|
+
type StandaloneProductionServerOptions as BaseStandaloneProductionServerOptions,
|
|
47
|
+
} from "./standalone-production-runtime-v2.js";
|
|
48
|
+
|
|
49
|
+
export type {
|
|
50
|
+
StandaloneApiRoute,
|
|
51
|
+
StandalonePageRoute,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface StandaloneProductionServerOptions
|
|
55
|
+
extends BaseStandaloneProductionServerOptions {}
|
|
56
|
+
|
|
57
|
+
export function createStandaloneProductionServer(
|
|
58
|
+
options: StandaloneProductionServerOptions
|
|
59
|
+
) {
|
|
60
|
+
const hasLoaderRoutes =
|
|
61
|
+
options.routes.some(
|
|
62
|
+
(route) =>
|
|
63
|
+
route.loader !==
|
|
64
|
+
null
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (!hasLoaderRoutes) {
|
|
68
|
+
return createBaseStandaloneProductionServer(
|
|
69
|
+
options
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const routeByPathname =
|
|
74
|
+
new Map(
|
|
75
|
+
options.routes.map(
|
|
76
|
+
(route) => [
|
|
77
|
+
route.pathname,
|
|
78
|
+
route,
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const routePatterns:
|
|
84
|
+
Route[] =
|
|
85
|
+
options.routes.map(
|
|
86
|
+
(route) => ({
|
|
87
|
+
pathname:
|
|
88
|
+
route.pathname,
|
|
89
|
+
segments:
|
|
90
|
+
route.segments,
|
|
91
|
+
filePath:
|
|
92
|
+
route.pathname,
|
|
93
|
+
layouts: [],
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
let baseServer:
|
|
98
|
+
ReturnType<
|
|
99
|
+
typeof createBaseStandaloneProductionServer
|
|
100
|
+
> | null =
|
|
101
|
+
null;
|
|
102
|
+
|
|
103
|
+
let gateway:
|
|
104
|
+
http.Server | null =
|
|
105
|
+
null;
|
|
106
|
+
|
|
107
|
+
let started =
|
|
108
|
+
false;
|
|
109
|
+
|
|
110
|
+
async function start(): Promise<void> {
|
|
111
|
+
if (started) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const internalPort =
|
|
116
|
+
await findFreePort();
|
|
117
|
+
|
|
118
|
+
baseServer =
|
|
119
|
+
createBaseStandaloneProductionServer({
|
|
120
|
+
...options,
|
|
121
|
+
port:
|
|
122
|
+
internalPort,
|
|
123
|
+
hostname:
|
|
124
|
+
"127.0.0.1",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await baseServer.start();
|
|
128
|
+
|
|
129
|
+
gateway =
|
|
130
|
+
http.createServer(
|
|
131
|
+
async (
|
|
132
|
+
req,
|
|
133
|
+
res
|
|
134
|
+
) => {
|
|
135
|
+
try {
|
|
136
|
+
const requestUrl =
|
|
137
|
+
new URL(
|
|
138
|
+
req.url ??
|
|
139
|
+
"/",
|
|
140
|
+
`http://${
|
|
141
|
+
req.headers.host ??
|
|
142
|
+
`${options.hostname}:${options.port}`
|
|
143
|
+
}`
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (
|
|
147
|
+
requestUrl.pathname !==
|
|
148
|
+
"/_bcp/navigation"
|
|
149
|
+
) {
|
|
150
|
+
proxyRequest(
|
|
151
|
+
req,
|
|
152
|
+
res,
|
|
153
|
+
internalPort
|
|
154
|
+
);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await handleNavigation(
|
|
159
|
+
req,
|
|
160
|
+
res,
|
|
161
|
+
requestUrl,
|
|
162
|
+
routePatterns,
|
|
163
|
+
routeByPathname,
|
|
164
|
+
options.version
|
|
165
|
+
);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(
|
|
168
|
+
"[BCP Loader Navigation Error]",
|
|
169
|
+
error
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
res.headersSent
|
|
174
|
+
) {
|
|
175
|
+
res.end();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await sendNavigationWebResponse(
|
|
180
|
+
req,
|
|
181
|
+
res,
|
|
182
|
+
createNavigationJsonResponse(
|
|
183
|
+
{
|
|
184
|
+
error:
|
|
185
|
+
"Internal Server Error",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
status:
|
|
189
|
+
500,
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
gateway.keepAliveTimeout =
|
|
198
|
+
65_000;
|
|
199
|
+
gateway.headersTimeout =
|
|
200
|
+
66_000;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await listen(
|
|
204
|
+
gateway,
|
|
205
|
+
options.port,
|
|
206
|
+
options.hostname
|
|
207
|
+
);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
await baseServer.stop();
|
|
210
|
+
baseServer =
|
|
211
|
+
null;
|
|
212
|
+
gateway =
|
|
213
|
+
null;
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
started =
|
|
218
|
+
true;
|
|
219
|
+
|
|
220
|
+
console.log(
|
|
221
|
+
`[BCP Loader] SPA navigation gateway: http://${options.hostname}:${options.port}`
|
|
222
|
+
);
|
|
223
|
+
console.log("");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function stop(): Promise<void> {
|
|
227
|
+
if (gateway) {
|
|
228
|
+
await closeServer(
|
|
229
|
+
gateway
|
|
230
|
+
);
|
|
231
|
+
gateway =
|
|
232
|
+
null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (baseServer) {
|
|
236
|
+
await baseServer.stop();
|
|
237
|
+
baseServer =
|
|
238
|
+
null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
started =
|
|
242
|
+
false;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
start,
|
|
247
|
+
stop,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function handleNavigation(
|
|
252
|
+
req: http.IncomingMessage,
|
|
253
|
+
res: http.ServerResponse,
|
|
254
|
+
requestUrl: URL,
|
|
255
|
+
routes: Route[],
|
|
256
|
+
routeByPathname: Map<
|
|
257
|
+
string,
|
|
258
|
+
StandalonePageRoute
|
|
259
|
+
>,
|
|
260
|
+
version: number
|
|
261
|
+
): Promise<void> {
|
|
262
|
+
if (
|
|
263
|
+
(
|
|
264
|
+
req.method ??
|
|
265
|
+
"GET"
|
|
266
|
+
).toUpperCase() !==
|
|
267
|
+
"GET"
|
|
268
|
+
) {
|
|
269
|
+
await sendNavigationWebResponse(
|
|
270
|
+
req,
|
|
271
|
+
res,
|
|
272
|
+
createNavigationJsonResponse(
|
|
273
|
+
{
|
|
274
|
+
error:
|
|
275
|
+
"Method Not Allowed",
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
status:
|
|
279
|
+
405,
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const targetPath =
|
|
287
|
+
requestUrl.searchParams.get(
|
|
288
|
+
"url"
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (!targetPath) {
|
|
292
|
+
await sendNavigationWebResponse(
|
|
293
|
+
req,
|
|
294
|
+
res,
|
|
295
|
+
createNavigationJsonResponse(
|
|
296
|
+
{
|
|
297
|
+
error:
|
|
298
|
+
"Missing navigation target.",
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
status:
|
|
302
|
+
400,
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let target: URL;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
target =
|
|
313
|
+
new URL(
|
|
314
|
+
targetPath,
|
|
315
|
+
requestUrl.origin
|
|
316
|
+
);
|
|
317
|
+
} catch {
|
|
318
|
+
await sendNavigationWebResponse(
|
|
319
|
+
req,
|
|
320
|
+
res,
|
|
321
|
+
createNavigationJsonResponse(
|
|
322
|
+
{
|
|
323
|
+
error:
|
|
324
|
+
"Invalid navigation target.",
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
status:
|
|
328
|
+
400,
|
|
329
|
+
}
|
|
330
|
+
)
|
|
331
|
+
);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (
|
|
336
|
+
target.origin !==
|
|
337
|
+
requestUrl.origin
|
|
338
|
+
) {
|
|
339
|
+
await sendNavigationWebResponse(
|
|
340
|
+
req,
|
|
341
|
+
res,
|
|
342
|
+
createNavigationJsonResponse(
|
|
343
|
+
{
|
|
344
|
+
error:
|
|
345
|
+
"Cross-origin navigation is not allowed.",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
status:
|
|
349
|
+
400,
|
|
350
|
+
}
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const match =
|
|
357
|
+
matchRoute(
|
|
358
|
+
routes,
|
|
359
|
+
normalizePathname(
|
|
360
|
+
target.pathname
|
|
361
|
+
)
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
if (!match) {
|
|
365
|
+
await sendNavigationWebResponse(
|
|
366
|
+
req,
|
|
367
|
+
res,
|
|
368
|
+
createNavigationJsonResponse(
|
|
369
|
+
{
|
|
370
|
+
error:
|
|
371
|
+
"Route not found.",
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
status:
|
|
375
|
+
404,
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const definition =
|
|
383
|
+
routeByPathname.get(
|
|
384
|
+
match.route.pathname
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
if (!definition) {
|
|
388
|
+
await sendNavigationWebResponse(
|
|
389
|
+
req,
|
|
390
|
+
res,
|
|
391
|
+
createNavigationJsonResponse(
|
|
392
|
+
{
|
|
393
|
+
error:
|
|
394
|
+
"Route not found.",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
status:
|
|
398
|
+
404,
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const request =
|
|
406
|
+
createNavigationRequest(
|
|
407
|
+
req,
|
|
408
|
+
target
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
let response: Response;
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
response =
|
|
415
|
+
await runWithRequestContext(
|
|
416
|
+
request,
|
|
417
|
+
async () => {
|
|
418
|
+
const loaderExecution:
|
|
419
|
+
PageLoaderExecution =
|
|
420
|
+
definition.loader
|
|
421
|
+
? await executePageLoaderFunction(
|
|
422
|
+
definition.loader,
|
|
423
|
+
match.params,
|
|
424
|
+
target,
|
|
425
|
+
definition.pathname
|
|
426
|
+
)
|
|
427
|
+
: {
|
|
428
|
+
hasLoader:
|
|
429
|
+
false,
|
|
430
|
+
data:
|
|
431
|
+
undefined,
|
|
432
|
+
response:
|
|
433
|
+
null,
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
if (
|
|
437
|
+
loaderExecution.response
|
|
438
|
+
) {
|
|
439
|
+
const loaderResponse =
|
|
440
|
+
applyResponseCookies(
|
|
441
|
+
loaderExecution.response
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
createNavigationRedirectResponse(
|
|
446
|
+
loaderResponse
|
|
447
|
+
) ??
|
|
448
|
+
loaderResponse
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const metadata =
|
|
453
|
+
await resolveRouteMetadata(
|
|
454
|
+
definition.metadataSources,
|
|
455
|
+
match.params
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const loadingHtml =
|
|
459
|
+
definition.loading
|
|
460
|
+
? renderToStaticMarkup(
|
|
461
|
+
createElement(
|
|
462
|
+
definition.loading,
|
|
463
|
+
{
|
|
464
|
+
params:
|
|
465
|
+
match.params,
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
)
|
|
469
|
+
: null;
|
|
470
|
+
|
|
471
|
+
const params =
|
|
472
|
+
attachLoaderDataToParams(
|
|
473
|
+
match.params,
|
|
474
|
+
loaderExecution
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
return applyResponseCookies(
|
|
478
|
+
createNavigationJsonResponse(
|
|
479
|
+
{
|
|
480
|
+
route:
|
|
481
|
+
definition.pathname,
|
|
482
|
+
params,
|
|
483
|
+
clientScript:
|
|
484
|
+
definition.client.entry,
|
|
485
|
+
clientImports:
|
|
486
|
+
definition.client.imports,
|
|
487
|
+
title:
|
|
488
|
+
metadata.title,
|
|
489
|
+
metadata,
|
|
490
|
+
version,
|
|
491
|
+
loadingHtml,
|
|
492
|
+
}
|
|
493
|
+
)
|
|
494
|
+
);
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
remoteAddress:
|
|
498
|
+
req.socket
|
|
499
|
+
.remoteAddress ??
|
|
500
|
+
null,
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
if (
|
|
505
|
+
isNotFoundError(
|
|
506
|
+
error
|
|
507
|
+
)
|
|
508
|
+
) {
|
|
509
|
+
response =
|
|
510
|
+
createNavigationJsonResponse(
|
|
511
|
+
{
|
|
512
|
+
error:
|
|
513
|
+
"Route not found.",
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
status:
|
|
517
|
+
404,
|
|
518
|
+
}
|
|
519
|
+
);
|
|
520
|
+
} else {
|
|
521
|
+
console.error(
|
|
522
|
+
`[BCP Loader Navigation Error] ${definition.pathname}:`,
|
|
523
|
+
error
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
response =
|
|
527
|
+
createNavigationJsonResponse(
|
|
528
|
+
{
|
|
529
|
+
error:
|
|
530
|
+
"Internal Server Error",
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
status:
|
|
534
|
+
500,
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
await sendNavigationWebResponse(
|
|
541
|
+
req,
|
|
542
|
+
res,
|
|
543
|
+
response
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function createNavigationRequest(
|
|
548
|
+
req: http.IncomingMessage,
|
|
549
|
+
target: URL
|
|
550
|
+
): Request {
|
|
551
|
+
const headers =
|
|
552
|
+
new Headers();
|
|
553
|
+
|
|
554
|
+
for (
|
|
555
|
+
const [key, value]
|
|
556
|
+
of Object.entries(
|
|
557
|
+
req.headers
|
|
558
|
+
)
|
|
559
|
+
) {
|
|
560
|
+
if (value === undefined) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (Array.isArray(value)) {
|
|
565
|
+
for (const item of value) {
|
|
566
|
+
headers.append(
|
|
567
|
+
key,
|
|
568
|
+
item
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
headers.set(
|
|
573
|
+
key,
|
|
574
|
+
value
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return new Request(
|
|
580
|
+
target,
|
|
581
|
+
{
|
|
582
|
+
method:
|
|
583
|
+
"GET",
|
|
584
|
+
headers,
|
|
585
|
+
}
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function proxyRequest(
|
|
590
|
+
req: http.IncomingMessage,
|
|
591
|
+
res: http.ServerResponse,
|
|
592
|
+
port: number
|
|
593
|
+
): void {
|
|
594
|
+
const upstream =
|
|
595
|
+
http.request({
|
|
596
|
+
hostname:
|
|
597
|
+
"127.0.0.1",
|
|
598
|
+
port,
|
|
599
|
+
method:
|
|
600
|
+
req.method ??
|
|
601
|
+
"GET",
|
|
602
|
+
path:
|
|
603
|
+
req.url ??
|
|
604
|
+
"/",
|
|
605
|
+
headers: {
|
|
606
|
+
...req.headers,
|
|
607
|
+
host:
|
|
608
|
+
req.headers.host,
|
|
609
|
+
},
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
upstream.on(
|
|
613
|
+
"response",
|
|
614
|
+
(upstreamResponse) => {
|
|
615
|
+
res.writeHead(
|
|
616
|
+
upstreamResponse
|
|
617
|
+
.statusCode ??
|
|
618
|
+
502,
|
|
619
|
+
upstreamResponse
|
|
620
|
+
.statusMessage ??
|
|
621
|
+
"",
|
|
622
|
+
upstreamResponse.headers
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
upstreamResponse.pipe(
|
|
626
|
+
res
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
upstream.on(
|
|
632
|
+
"error",
|
|
633
|
+
(error) => {
|
|
634
|
+
if (
|
|
635
|
+
res.headersSent
|
|
636
|
+
) {
|
|
637
|
+
res.destroy(
|
|
638
|
+
error
|
|
639
|
+
);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
res.writeHead(
|
|
644
|
+
502,
|
|
645
|
+
{
|
|
646
|
+
"Content-Type":
|
|
647
|
+
"text/plain; charset=utf-8",
|
|
648
|
+
"Cache-Control":
|
|
649
|
+
"no-store",
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
res.end(
|
|
653
|
+
"Bad Gateway"
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
req.pipe(
|
|
659
|
+
upstream
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function normalizePathname(
|
|
664
|
+
pathname: string
|
|
665
|
+
): string {
|
|
666
|
+
if (
|
|
667
|
+
pathname.length > 1 &&
|
|
668
|
+
pathname.endsWith(
|
|
669
|
+
"/"
|
|
670
|
+
)
|
|
671
|
+
) {
|
|
672
|
+
return pathname.slice(
|
|
673
|
+
0,
|
|
674
|
+
-1
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return pathname || "/";
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async function findFreePort(): Promise<number> {
|
|
682
|
+
const server =
|
|
683
|
+
net.createServer();
|
|
684
|
+
|
|
685
|
+
try {
|
|
686
|
+
await listen(
|
|
687
|
+
server,
|
|
688
|
+
0,
|
|
689
|
+
"127.0.0.1"
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
const address =
|
|
693
|
+
server.address();
|
|
694
|
+
|
|
695
|
+
if (
|
|
696
|
+
!address ||
|
|
697
|
+
typeof address ===
|
|
698
|
+
"string"
|
|
699
|
+
) {
|
|
700
|
+
throw new Error(
|
|
701
|
+
"BCP Framework: could not allocate loader navigation production port."
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return address.port;
|
|
706
|
+
} finally {
|
|
707
|
+
await closeServer(
|
|
708
|
+
server
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function listen(
|
|
714
|
+
server: net.Server | http.Server,
|
|
715
|
+
port: number,
|
|
716
|
+
hostname: string
|
|
717
|
+
): Promise<void> {
|
|
718
|
+
return new Promise(
|
|
719
|
+
(
|
|
720
|
+
resolve,
|
|
721
|
+
reject
|
|
722
|
+
) => {
|
|
723
|
+
const onError =
|
|
724
|
+
(error: Error) => {
|
|
725
|
+
server.off(
|
|
726
|
+
"listening",
|
|
727
|
+
onListening
|
|
728
|
+
);
|
|
729
|
+
reject(
|
|
730
|
+
error
|
|
731
|
+
);
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const onListening =
|
|
735
|
+
() => {
|
|
736
|
+
server.off(
|
|
737
|
+
"error",
|
|
738
|
+
onError
|
|
739
|
+
);
|
|
740
|
+
resolve();
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
server.once(
|
|
744
|
+
"error",
|
|
745
|
+
onError
|
|
746
|
+
);
|
|
747
|
+
server.once(
|
|
748
|
+
"listening",
|
|
749
|
+
onListening
|
|
750
|
+
);
|
|
751
|
+
server.listen(
|
|
752
|
+
port,
|
|
753
|
+
hostname
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function closeServer(
|
|
760
|
+
server: net.Server | http.Server
|
|
761
|
+
): Promise<void> {
|
|
762
|
+
if (!server.listening) {
|
|
763
|
+
return Promise.resolve();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return new Promise(
|
|
767
|
+
(
|
|
768
|
+
resolve,
|
|
769
|
+
reject
|
|
770
|
+
) => {
|
|
771
|
+
server.close(
|
|
772
|
+
(error) => {
|
|
773
|
+
if (error) {
|
|
774
|
+
reject(
|
|
775
|
+
error
|
|
776
|
+
);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
resolve();
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
}
|