@jsenv/server 15.5.3 → 16.0.1

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 (55) hide show
  1. package/README.md +79 -86
  2. package/dist/html/4xx.html +196 -0
  3. package/dist/html/500.html +17 -0
  4. package/dist/html/route_inspector.html +2435 -0
  5. package/dist/js/formidable.js +17 -0
  6. package/dist/js/vendors.js +2001 -0
  7. package/dist/js/ws.js +6863 -0
  8. package/dist/jsenv_server.js +8829 -0
  9. package/package.json +19 -9
  10. package/src/content_negotiation/pick_content_version.js +63 -0
  11. package/src/{fetch_filesystem.js → filesystem/fetch_filesystem.js} +57 -72
  12. package/src/{serve_directory.js → filesystem/serve_directory.js} +1 -2
  13. package/src/interfacing_with_node/from_node_request.js +455 -0
  14. package/src/interfacing_with_node/get_observable_value_type.js +43 -0
  15. package/src/interfacing_with_node/observable.js +91 -75
  16. package/src/interfacing_with_node/observable_from.js +34 -0
  17. package/src/interfacing_with_node/observable_from_file_handle.js +30 -0
  18. package/src/interfacing_with_node/observable_from_node_stream.js +44 -46
  19. package/src/interfacing_with_node/observable_from_node_web_readable_stream.js +32 -0
  20. package/src/interfacing_with_node/observable_from_promise.js +13 -0
  21. package/src/interfacing_with_node/write_node_response.js +80 -35
  22. package/src/internal/colorizeResponseStatus.js +2 -0
  23. package/src/internal/headers_composition.js +42 -15
  24. package/src/internal/multiple-header.js +27 -12
  25. package/src/internal/object_composition.js +2 -2
  26. package/src/internal/response_composition.js +16 -3
  27. package/src/main.js +15 -11
  28. package/src/progressive_response.js +38 -0
  29. package/src/replace_placeholder_in_html.js +12 -0
  30. package/src/request_body_handling.js +22 -0
  31. package/src/router/router.js +878 -0
  32. package/src/server_events.js +431 -0
  33. package/src/service_controller.js +79 -73
  34. package/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js +125 -0
  35. package/src/services/autoreload_on_server_restart/jsenv_service_autoreload_on_server_restart.js +45 -0
  36. package/src/services/cors/jsenv_service_cors.js +10 -18
  37. package/src/services/default_body_4xx_5xx/client/4xx.html +65 -0
  38. package/src/services/default_body_4xx_5xx/jsenv_service_default_body_4xx_5xx.js +82 -0
  39. package/src/services/error_handler/client/500.html +17 -0
  40. package/src/services/error_handler/jsenv_service_error_handler.js +47 -28
  41. package/src/services/internal_client_files/jsenv_service_internal_client_files.js +34 -0
  42. package/src/services/open_file/jsenv_service_open_file.js +37 -0
  43. package/src/services/route_inspector/client/code_block.js +215 -0
  44. package/src/services/route_inspector/client/route_inspector.html +1363 -0
  45. package/src/services/route_inspector/jsenv_service_route_inspector.js +39 -0
  46. package/src/start_server.js +575 -607
  47. package/src/web_socket_response.js +53 -0
  48. package/src/from_fetch_response.js +0 -15
  49. package/src/interfacing_with_node/body.js +0 -81
  50. package/src/interfacing_with_node/request_factory.js +0 -125
  51. package/src/readRequestBody.js +0 -32
  52. package/src/server_timing/timing_measure.js +0 -26
  53. package/src/service_composition/path_to_regexp.js +0 -436
  54. package/src/service_composition/routing.js +0 -31
  55. package/src/sse/sse_room.js +0 -313
@@ -1,32 +1,59 @@
1
1
  import { composeTwoObjects } from "./object_composition.js";
2
2
 
3
3
  export const composeTwoHeaders = (firstHeaders, secondHeaders) => {
4
+ if (firstHeaders && typeof firstHeaders.entries === "function") {
5
+ firstHeaders = Object.fromEntries(firstHeaders.entries());
6
+ }
7
+ if (secondHeaders && typeof secondHeaders.entries === "function") {
8
+ secondHeaders = Object.fromEntries(secondHeaders.entries());
9
+ }
4
10
  return composeTwoObjects(firstHeaders, secondHeaders, {
5
11
  keysComposition: HEADER_NAMES_COMPOSITION,
6
12
  forceLowerCase: true,
7
13
  });
8
14
  };
9
15
 
10
- const composeHeaderValues = (value, nextValue) => {
11
- const headerValues = value.split(", ");
12
- nextValue.split(", ").forEach((value) => {
13
- if (!headerValues.includes(value)) {
14
- headerValues.push(value);
16
+ export const composeTwoHeaderValues = (name, leftValue, rightValue) => {
17
+ if (HEADER_NAMES_COMPOSITION[name]) {
18
+ return HEADER_NAMES_COMPOSITION[name](leftValue, rightValue);
19
+ }
20
+ return rightValue;
21
+ };
22
+
23
+ const composeTwoCommaSeparatedValues = (value, nextValue) => {
24
+ if (!value) {
25
+ return nextValue;
26
+ }
27
+ if (!nextValue) {
28
+ return value;
29
+ }
30
+ const currentValues = value
31
+ .split(", ")
32
+ .map((part) => part.trim().toLowerCase());
33
+ const nextValues = nextValue
34
+ .split(", ")
35
+ .map((part) => part.trim().toLowerCase());
36
+ for (const nextValue of nextValues) {
37
+ if (!currentValues.includes(nextValue)) {
38
+ currentValues.push(nextValue);
15
39
  }
16
- });
17
- return headerValues.join(", ");
40
+ }
41
+ return currentValues.join(", ");
18
42
  };
19
43
 
20
44
  const HEADER_NAMES_COMPOSITION = {
21
- "accept": composeHeaderValues,
22
- "accept-charset": composeHeaderValues,
23
- "accept-language": composeHeaderValues,
24
- "access-control-allow-headers": composeHeaderValues,
25
- "access-control-allow-methods": composeHeaderValues,
26
- "access-control-allow-origin": composeHeaderValues,
45
+ "accept": composeTwoCommaSeparatedValues,
46
+ "accept-charset": composeTwoCommaSeparatedValues,
47
+ "accept-language": composeTwoCommaSeparatedValues,
48
+ "access-control-allow-headers": composeTwoCommaSeparatedValues,
49
+ "access-control-allow-methods": composeTwoCommaSeparatedValues,
50
+ "access-control-allow-origin": composeTwoCommaSeparatedValues,
51
+ "accept-patch": composeTwoCommaSeparatedValues,
52
+ "accept-post": composeTwoCommaSeparatedValues,
53
+ "allow": composeTwoCommaSeparatedValues,
27
54
  // https://www.w3.org/TR/server-timing/
28
55
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing
29
- "server-timing": composeHeaderValues,
56
+ "server-timing": composeTwoCommaSeparatedValues,
30
57
  // 'content-type', // https://github.com/ninenines/cowboy/issues/1230
31
- "vary": composeHeaderValues,
58
+ "vary": composeTwoCommaSeparatedValues,
32
59
  };
@@ -25,29 +25,44 @@ export const parseMultipleHeader = (
25
25
  if (!nameValidation) {
26
26
  return;
27
27
  }
28
-
29
- const properties = parseHeaderProperties(valueParts.slice(1), {
30
- validateProperty,
31
- });
28
+ const afterName = valueParts.slice(1);
29
+ const properties = parseHeaderProperties(afterName, { validateProperty });
32
30
  multipleHeader[name] = properties;
33
31
  });
34
32
  return multipleHeader;
35
33
  };
36
34
 
35
+ export const parseSingleHeaderWithAttributes = (
36
+ string,
37
+ { validateAttribute = () => true } = {},
38
+ ) => {
39
+ const props = {};
40
+ const attributes = string.split(";");
41
+ for (const attr of attributes) {
42
+ let [name, value] = attr.split("=");
43
+ name = name.trim();
44
+ value = value.trim();
45
+ if (validateAttribute({ name, value })) {
46
+ props[name] = value;
47
+ }
48
+ }
49
+ return props;
50
+ };
51
+
37
52
  const parseHeaderProperties = (headerProperties, { validateProperty }) => {
38
- const properties = headerProperties.reduce((previous, valuePart) => {
39
- const [propertyName, propertyValueString] = valuePart.split("=");
53
+ const properties = {};
54
+ for (const propertySource of headerProperties) {
55
+ const [propertyName, propertyValueString] = propertySource
56
+ .trim()
57
+ .split("=");
40
58
  const propertyValue = parseHeaderPropertyValue(propertyValueString);
41
59
  const property = { name: propertyName, value: propertyValue };
42
60
  const propertyValidation = validateProperty(property);
43
61
  if (!propertyValidation) {
44
- return previous;
62
+ continue;
45
63
  }
46
- return {
47
- ...previous,
48
- [property.name]: property.value,
49
- };
50
- }, {});
64
+ properties[propertyName] = propertyValue;
65
+ }
51
66
  return properties;
52
67
  };
53
68
 
@@ -23,7 +23,7 @@ const applyCaseSensitiveComposition = (
23
23
  ) => {
24
24
  if (strict) {
25
25
  const composed = {};
26
- Object.keys(keysComposition).forEach((key) => {
26
+ for (const key of Object.keys(keysComposition)) {
27
27
  composed[key] = composeValueAtKey({
28
28
  firstObject,
29
29
  secondObject,
@@ -32,7 +32,7 @@ const applyCaseSensitiveComposition = (
32
32
  firstKey: keyExistsIn(key, firstObject) ? key : null,
33
33
  secondKey: keyExistsIn(key, secondObject) ? key : null,
34
34
  });
35
- });
35
+ }
36
36
  return composed;
37
37
  }
38
38
 
@@ -1,7 +1,23 @@
1
1
  import { composeTwoHeaders } from "./headers_composition.js";
2
2
  import { composeTwoObjects } from "./object_composition.js";
3
3
 
4
+ const asResponseProperties = (value) => {
5
+ if (value && value instanceof Response) {
6
+ return {
7
+ status: value.status,
8
+ statusText: value.statusText,
9
+ headers: Object.fromEntries(value.headers),
10
+ body: value.body,
11
+ bodyEncoding: value.bodyEncoding,
12
+ };
13
+ }
14
+ return value;
15
+ };
16
+
4
17
  export const composeTwoResponses = (firstResponse, secondResponse) => {
18
+ firstResponse = asResponseProperties(firstResponse);
19
+ secondResponse = asResponseProperties(secondResponse);
20
+
5
21
  return composeTwoObjects(firstResponse, secondResponse, {
6
22
  keysComposition: RESPONSE_KEYS_COMPOSITION,
7
23
  strict: true,
@@ -15,7 +31,4 @@ const RESPONSE_KEYS_COMPOSITION = {
15
31
  headers: composeTwoHeaders,
16
32
  body: (prevBody, body) => body,
17
33
  bodyEncoding: (prevEncoding, encoding) => encoding,
18
- timing: (prevTiming, timing) => {
19
- return { ...prevTiming, ...timing };
20
- },
21
34
  };
package/src/main.js CHANGED
@@ -7,9 +7,7 @@
7
7
  // tslint:disable:ordered-imports
8
8
 
9
9
  export { startServer } from "./start_server.js";
10
- export { setupRoutes } from "./service_composition/routing.js";
11
- export { readRequestBody } from "./readRequestBody.js";
12
- export { fetchFileSystem } from "./fetch_filesystem.js";
10
+
13
11
  export {
14
12
  STOP_REASON_INTERNAL_ERROR,
15
13
  STOP_REASON_PROCESS_SIGHUP,
@@ -21,6 +19,19 @@ export {
21
19
  } from "./stopReasons.js";
22
20
  export { jsenvServiceErrorHandler } from "./services/error_handler/jsenv_service_error_handler.js";
23
21
 
22
+ // filesystem
23
+ export {
24
+ createFileSystemFetch,
25
+ fetchFileSystem,
26
+ } from "./filesystem/fetch_filesystem.js";
27
+
28
+ // webSocket
29
+ export { WebSocketResponse } from "./web_socket_response.js";
30
+ // long polling
31
+ export { ProgressiveResponse } from "./progressive_response.js";
32
+ // Server event (can be used to push events to clients that can be connected with WebSocket or EventSource)
33
+ export { ServerEvents, LazyServerEvents } from "./server_events.js";
34
+
24
35
  // CORS
25
36
  export {
26
37
  jsenvServiceCORS,
@@ -28,12 +39,6 @@ export {
28
39
  jsenvAccessControlAllowedMethods,
29
40
  } from "./services/cors/jsenv_service_cors.js";
30
41
 
31
- // server timing
32
- export { timeFunction, timeStart } from "./server_timing/timing_measure.js";
33
-
34
- // SSE
35
- export { createSSERoom } from "./sse/sse_room.js";
36
-
37
42
  // content-negotiation
38
43
  export { pickContentType } from "./content_negotiation/pick_content_type.js";
39
44
  export { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js";
@@ -41,8 +46,7 @@ export { pickContentLanguage } from "./content_negotiation/pick_content_language
41
46
  export { jsenvServiceResponseAcceptanceCheck } from "./services/response_acceptance_check/jsenv_service_response_acceptance_check.js";
42
47
 
43
48
  // others
44
- export { serveDirectory } from "./serve_directory.js";
45
- export { fromFetchResponse } from "./from_fetch_response.js";
49
+ export { serveDirectory } from "./filesystem/serve_directory.js";
46
50
  export { composeTwoResponses } from "./internal/response_composition.js";
47
51
  export { jsenvServiceRequestAliases } from "./services/request_aliases/jsenv_service_request_aliases.js";
48
52
  export { findFreePort } from "./internal/listen.js";
@@ -0,0 +1,38 @@
1
+ import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
2
+ import { createObservable } from "./interfacing_with_node/observable.js";
3
+
4
+ export class ProgressiveResponse {
5
+ constructor(responseBodyHandler, { status = 200, statusText, headers } = {}) {
6
+ const contentType = headers ? headers["content-type"] : "text/plain";
7
+ const progressiveResponse = {
8
+ status,
9
+ statusText,
10
+ headers,
11
+ body: createObservable(({ next, complete, addTeardown }) => {
12
+ // we must write something for fetch promise to resolve
13
+ // this is conform to HTTP spec where client expect body to starts writing
14
+ // before resolving response promise client side
15
+ if (CONTENT_TYPE.isTextual(contentType)) {
16
+ next("");
17
+ } else {
18
+ next(new Uint8Array());
19
+ }
20
+ const returnValue = responseBodyHandler({
21
+ write: (data) => {
22
+ next(data);
23
+ },
24
+ end: () => {
25
+ complete();
26
+ },
27
+ });
28
+ if (typeof returnValue === "function") {
29
+ addTeardown(() => {
30
+ returnValue();
31
+ });
32
+ }
33
+ }),
34
+ };
35
+ // eslint-disable-next-line no-constructor-return
36
+ return progressiveResponse;
37
+ }
38
+ }
@@ -0,0 +1,12 @@
1
+ export const replacePlaceholdersInHtml = (html, replacers) => {
2
+ return html.replace(/\$\{(\w+)\}/g, (match, name) => {
3
+ const replacer = replacers[name];
4
+ if (replacer === undefined) {
5
+ return match;
6
+ }
7
+ if (typeof replacer === "function") {
8
+ return replacer();
9
+ }
10
+ return replacer;
11
+ });
12
+ };
@@ -0,0 +1,22 @@
1
+ export const pickRequestContentType = (request, acceptedContentTypeArray) => {
2
+ const requestBodyContentType = request.headers["content-type"];
3
+ if (!requestBodyContentType) {
4
+ return null;
5
+ }
6
+ for (const acceptedContentType of acceptedContentTypeArray) {
7
+ if (requestBodyContentType.includes(acceptedContentType)) {
8
+ return acceptedContentType;
9
+ }
10
+ }
11
+ return null;
12
+ };
13
+
14
+ export const createUnsupportedMediaTypeResponse = (
15
+ request,
16
+ acceptedContentTypeArray = [],
17
+ ) => {
18
+ return {
19
+ status: 415,
20
+ [`accept-${request.method}`]: acceptedContentTypeArray.join(", "),
21
+ };
22
+ };