@jsenv/core 29.1.5 → 29.1.6

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 (2) hide show
  1. package/dist/main.js +112 -81
  2. package/package.json +2 -2
package/dist/main.js CHANGED
@@ -4450,8 +4450,10 @@ const observableFromValue = value => {
4450
4450
  };
4451
4451
 
4452
4452
  // https://github.com/jamestalmage/stream-to-observable/blob/master/index.js
4453
- const readableStreamLifetimeInSeconds = 120;
4454
- const nodeStreamToObservable = nodeStream => {
4453
+ const observableFromNodeStream = (nodeStream, {
4454
+ readableStreamLifetime = 120_000 // 2s
4455
+
4456
+ } = {}) => {
4455
4457
  const observable = createObservable(({
4456
4458
  next,
4457
4459
  error,
@@ -4490,13 +4492,13 @@ const nodeStreamToObservable = nodeStream => {
4490
4492
  // safe measure, ensure the readable stream gets
4491
4493
  // used in the next ${readableStreamLifetimeInSeconds} otherwise destroys it
4492
4494
  const timeout = setTimeout(() => {
4493
- process.emitWarning(`Readable stream not used after ${readableStreamLifetimeInSeconds} seconds. It will be destroyed to release resources`, {
4495
+ process.emitWarning(`Readable stream not used after ${readableStreamLifetime / 1000} seconds. It will be destroyed to release resources`, {
4494
4496
  CODE: "READABLE_STREAM_TIMEOUT",
4495
4497
  // url is for http client request
4496
4498
  detail: `path: ${nodeStream.path}, fd: ${nodeStream.fd}, url: ${nodeStream.url}`
4497
4499
  });
4498
4500
  nodeStream.destroy();
4499
- }, readableStreamLifetimeInSeconds * 1000);
4501
+ }, readableStreamLifetime);
4500
4502
  observable.timeout = timeout;
4501
4503
  onceReadableStreamUsedOrClosed(nodeStream, () => {
4502
4504
  clearTimeout(timeout);
@@ -4550,10 +4552,13 @@ const headersFromObject = headersObject => {
4550
4552
 
4551
4553
  const fromNodeRequest = (nodeRequest, {
4552
4554
  serverOrigin,
4553
- signal
4555
+ signal,
4556
+ requestBodyLifetime
4554
4557
  }) => {
4555
4558
  const headers = headersFromObject(nodeRequest.headers);
4556
- const body = nodeStreamToObservable(nodeRequest);
4559
+ const body = observableFromNodeStream(nodeRequest, {
4560
+ readableStreamLifetime: requestBodyLifetime
4561
+ });
4557
4562
  let requestOrigin;
4558
4563
 
4559
4564
  if (nodeRequest.upgrade) {
@@ -4671,7 +4676,7 @@ const normalizeBodyMethods = body => {
4671
4676
 
4672
4677
  if (isNodeStream(body)) {
4673
4678
  return {
4674
- asObservable: () => nodeStreamToObservable(body),
4679
+ asObservable: () => observableFromNodeStream(body),
4675
4680
  destroy: () => {
4676
4681
  body.destroy();
4677
4682
  }
@@ -4702,7 +4707,7 @@ const fileHandleToReadableStream = fileHandle => {
4702
4707
  };
4703
4708
 
4704
4709
  const fileHandleToObservable = fileHandle => {
4705
- return nodeStreamToObservable(fileHandleToReadableStream(fileHandle));
4710
+ return observableFromNodeStream(fileHandleToReadableStream(fileHandle));
4706
4711
  };
4707
4712
 
4708
4713
  const isNodeStream = value => {
@@ -4717,7 +4722,7 @@ const isNodeStream = value => {
4717
4722
  return false;
4718
4723
  };
4719
4724
 
4720
- const populateNodeResponse = async (responseStream, {
4725
+ const writeNodeResponse = async (responseStream, {
4721
4726
  status,
4722
4727
  statusText,
4723
4728
  headers,
@@ -5487,7 +5492,16 @@ const startServer = async ({
5487
5492
  "request url": request.url,
5488
5493
  "request headers": JSON.stringify(request.headers, null, " ")
5489
5494
  }));
5490
- }
5495
+ },
5496
+ // timeAllocated to start responding to a request
5497
+ // after this delay the server will respond with 504
5498
+ responseTimeout = 60_000 * 10,
5499
+ // 10s
5500
+ // time allocated to server code to start reading the request body
5501
+ // after this delay the underlying stream is destroyed, attempting to read it would throw
5502
+ // if used the stream stays opened, it's only if the stream is not read at all that it gets destroyed
5503
+ requestBodyLifetime = 60_000 * 2 // 2s
5504
+
5491
5505
  } = {}) => {
5492
5506
  const logger = createLogger({
5493
5507
  logLevel
@@ -5972,7 +5986,7 @@ const startServer = async ({
5972
5986
  abortController.abort();
5973
5987
  } else if (!http2Stream.pushAllowed) {
5974
5988
  abortController.abort();
5975
- } else if (responseProperties !== ABORTED_RESPONSE_PROPERTIES) {
5989
+ } else if (responseProperties.requestAborted) {} else {
5976
5990
  const responseLength = responseProperties.headers["content-length"] || 0;
5977
5991
  const {
5978
5992
  effectiveRecvDataLength,
@@ -6047,87 +6061,104 @@ const startServer = async ({
6047
6061
  let handleRequestReturnValue;
6048
6062
  let errorWhileHandlingRequest = null;
6049
6063
  let handleRequestTimings = serverTiming ? {} : null;
6064
+ let timeout;
6065
+ const timeoutPromise = new Promise(resolve => {
6066
+ timeout = setTimeout(() => {
6067
+ resolve({
6068
+ // the correct status code should be 500 because it's
6069
+ // we don't really know what takes time
6070
+ // in practice it's often because server is trying to reach an other server
6071
+ // that is not responding so 504 is more correct
6072
+ status: 504,
6073
+ statusText: `server timeout after ${responseTimeout / 1000}s waiting to handle request`
6074
+ });
6075
+ }, responseTimeout);
6076
+ });
6077
+ const handleRequestPromise = serviceController.callAsyncHooksUntil("handleRequest", request, {
6078
+ timing: handleRequestTimings,
6079
+ warn,
6080
+ pushResponse: async ({
6081
+ path,
6082
+ method
6083
+ }) => {
6084
+ if (typeof path !== "string" || path[0] !== "/") {
6085
+ addRequestLog(requestNode, {
6086
+ type: "warn",
6087
+ value: `response push ignored because path is invalid (must be a string starting with "/", found ${path})`
6088
+ });
6089
+ return;
6090
+ }
6050
6091
 
6051
- try {
6052
- handleRequestReturnValue = await serviceController.callAsyncHooksUntil("handleRequest", request, {
6053
- timing: handleRequestTimings,
6054
- warn,
6055
- pushResponse: async ({
6056
- path,
6057
- method
6058
- }) => {
6059
- if (typeof path !== "string" || path[0] !== "/") {
6060
- addRequestLog(requestNode, {
6061
- type: "warn",
6062
- value: `response push ignored because path is invalid (must be a string starting with "/", found ${path})`
6063
- });
6064
- return;
6065
- }
6066
-
6067
- if (!request.http2) {
6068
- addRequestLog(requestNode, {
6069
- type: "warn",
6070
- value: `response push ignored because request is not http2`
6071
- });
6072
- return;
6073
- }
6074
-
6075
- const canPushStream = testCanPushStream(nodeResponse.stream);
6092
+ if (!request.http2) {
6093
+ addRequestLog(requestNode, {
6094
+ type: "warn",
6095
+ value: `response push ignored because request is not http2`
6096
+ });
6097
+ return;
6098
+ }
6076
6099
 
6077
- if (!canPushStream.can) {
6078
- addRequestLog(requestNode, {
6079
- type: "debug",
6080
- value: `response push ignored because ${canPushStream.reason}`
6081
- });
6082
- return;
6083
- }
6100
+ const canPushStream = testCanPushStream(nodeResponse.stream);
6084
6101
 
6085
- let preventedByService = null;
6102
+ if (!canPushStream.can) {
6103
+ addRequestLog(requestNode, {
6104
+ type: "debug",
6105
+ value: `response push ignored because ${canPushStream.reason}`
6106
+ });
6107
+ return;
6108
+ }
6086
6109
 
6087
- const prevent = () => {
6088
- preventedByService = serviceController.getCurrentService();
6089
- };
6110
+ let preventedByService = null;
6090
6111
 
6091
- serviceController.callHooksUntil("onResponsePush", {
6092
- path,
6093
- method
6094
- }, {
6095
- request,
6096
- warn,
6097
- prevent
6098
- }, () => preventedByService);
6099
-
6100
- if (preventedByService) {
6101
- addRequestLog(requestNode, {
6102
- type: "debug",
6103
- value: `response push prevented by "${preventedByService.name}" service`
6104
- });
6105
- return;
6106
- }
6112
+ const prevent = () => {
6113
+ preventedByService = serviceController.getCurrentService();
6114
+ };
6107
6115
 
6108
- const requestChildNode = {
6109
- logs: [],
6110
- children: []
6111
- };
6112
- requestNode.children.push(requestChildNode);
6113
- await pushResponse({
6114
- path,
6115
- method
6116
- }, {
6117
- requestNode: requestChildNode,
6118
- parentHttp2Stream: nodeResponse.stream
6116
+ serviceController.callHooksUntil("onResponsePush", {
6117
+ path,
6118
+ method
6119
+ }, {
6120
+ request,
6121
+ warn,
6122
+ prevent
6123
+ }, () => preventedByService);
6124
+
6125
+ if (preventedByService) {
6126
+ addRequestLog(requestNode, {
6127
+ type: "debug",
6128
+ value: `response push prevented by "${preventedByService.name}" service`
6119
6129
  });
6130
+ return;
6120
6131
  }
6121
- });
6122
- } catch (error) {
6123
- errorWhileHandlingRequest = error;
6132
+
6133
+ const requestChildNode = {
6134
+ logs: [],
6135
+ children: []
6136
+ };
6137
+ requestNode.children.push(requestChildNode);
6138
+ await pushResponse({
6139
+ path,
6140
+ method
6141
+ }, {
6142
+ requestNode: requestChildNode,
6143
+ parentHttp2Stream: nodeResponse.stream
6144
+ });
6145
+ }
6146
+ });
6147
+
6148
+ try {
6149
+ handleRequestReturnValue = await Promise.race([timeoutPromise, handleRequestPromise]);
6150
+ } catch (e) {
6151
+ errorWhileHandlingRequest = e;
6124
6152
  }
6125
6153
 
6154
+ clearTimeout(timeout);
6126
6155
  let responseProperties;
6127
6156
 
6128
6157
  if (errorWhileHandlingRequest) {
6129
6158
  if (errorWhileHandlingRequest.name === "AbortError" && request.signal.aborted) {
6130
- responseProperties = ABORTED_RESPONSE_PROPERTIES;
6159
+ responseProperties = {
6160
+ requestAborted: true
6161
+ };
6131
6162
  } else {
6132
6163
  // internal error, create 500 response
6133
6164
  if ( // stopOnInternalError stops server only if requestToResponse generated
@@ -6237,7 +6268,7 @@ const startServer = async ({
6237
6268
  await new Promise(resolve => setTimeout(resolve));
6238
6269
  }
6239
6270
 
6240
- await populateNodeResponse(responseStream, responseProperties, {
6271
+ await writeNodeResponse(responseStream, responseProperties, {
6241
6272
  signal,
6242
6273
  ignoreBody,
6243
6274
  onAbort: () => {
@@ -6348,7 +6379,8 @@ const startServer = async ({
6348
6379
  });
6349
6380
  const request = fromNodeRequest(nodeRequest, {
6350
6381
  serverOrigin: websocketOrigin,
6351
- signal: new AbortController().signal
6382
+ signal: new AbortController().signal,
6383
+ requestBodyLifetime
6352
6384
  });
6353
6385
  serviceController.callAsyncHooksUntil("handleWebsocket", websocket, {
6354
6386
  request
@@ -6456,7 +6488,6 @@ const testCanPushStream = http2Stream => {
6456
6488
  };
6457
6489
  };
6458
6490
 
6459
- const ABORTED_RESPONSE_PROPERTIES = {};
6460
6491
  const PROCESS_TEARDOWN_EVENTS_MAP = {
6461
6492
  SIGHUP: STOP_REASON_PROCESS_SIGHUP,
6462
6493
  SIGTERM: STOP_REASON_PROCESS_SIGTERM,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "29.1.5",
3
+ "version": "29.1.6",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -73,7 +73,7 @@
73
73
  "@jsenv/integrity": "0.0.1",
74
74
  "@jsenv/log": "3.3.0",
75
75
  "@jsenv/node-esm-resolution": "0.1.0",
76
- "@jsenv/server": "14.1.4",
76
+ "@jsenv/server": "14.1.5",
77
77
  "@jsenv/sourcemap": "1.0.5",
78
78
  "@jsenv/uneval": "1.6.0",
79
79
  "@jsenv/url-meta": "7.0.0",