@jsenv/server 15.5.3 → 16.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/README.md +79 -86
- package/dist/html/4xx.html +196 -0
- package/dist/html/500.html +17 -0
- package/dist/html/route_inspector.html +2435 -0
- package/dist/js/formidable.js +17 -0
- package/dist/js/vendors.js +2001 -0
- package/dist/js/ws.js +6863 -0
- package/dist/jsenv_server.js +8829 -0
- package/package.json +20 -9
- package/src/content_negotiation/pick_content_version.js +63 -0
- package/src/{fetch_filesystem.js → filesystem/fetch_filesystem.js} +57 -72
- package/src/{serve_directory.js → filesystem/serve_directory.js} +1 -2
- package/src/interfacing_with_node/from_node_request.js +455 -0
- package/src/interfacing_with_node/get_observable_value_type.js +43 -0
- package/src/interfacing_with_node/observable.js +91 -75
- package/src/interfacing_with_node/observable_from.js +34 -0
- package/src/interfacing_with_node/observable_from_file_handle.js +30 -0
- package/src/interfacing_with_node/observable_from_node_stream.js +44 -46
- package/src/interfacing_with_node/observable_from_node_web_readable_stream.js +32 -0
- package/src/interfacing_with_node/observable_from_promise.js +13 -0
- package/src/interfacing_with_node/write_node_response.js +80 -35
- package/src/internal/colorizeResponseStatus.js +2 -0
- package/src/internal/headers_composition.js +42 -15
- package/src/internal/multiple-header.js +27 -12
- package/src/internal/object_composition.js +2 -2
- package/src/internal/response_composition.js +16 -3
- package/src/main.js +15 -11
- package/src/progressive_response.js +38 -0
- package/src/replace_placeholder_in_html.js +12 -0
- package/src/request_body_handling.js +22 -0
- package/src/router/router.js +878 -0
- package/src/server_events.js +431 -0
- package/src/service_controller.js +79 -73
- package/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js +125 -0
- package/src/services/autoreload_on_server_restart/jsenv_service_autoreload_on_server_restart.js +45 -0
- package/src/services/cors/jsenv_service_cors.js +10 -18
- package/src/services/default_body_4xx_5xx/client/4xx.html +65 -0
- package/src/services/default_body_4xx_5xx/jsenv_service_default_body_4xx_5xx.js +82 -0
- package/src/services/error_handler/client/500.html +17 -0
- package/src/services/error_handler/jsenv_service_error_handler.js +47 -28
- package/src/services/internal_client_files/jsenv_service_internal_client_files.js +34 -0
- package/src/services/open_file/jsenv_service_open_file.js +37 -0
- package/src/services/route_inspector/client/code_block.js +215 -0
- package/src/services/route_inspector/client/route_inspector.html +1363 -0
- package/src/services/route_inspector/jsenv_service_route_inspector.js +39 -0
- package/src/start_server.js +575 -607
- package/src/web_socket_response.js +53 -0
- package/src/from_fetch_response.js +0 -15
- package/src/interfacing_with_node/body.js +0 -81
- package/src/interfacing_with_node/request_factory.js +0 -125
- package/src/readRequestBody.js +0 -32
- package/src/server_timing/timing_measure.js +0 -26
- package/src/service_composition/path_to_regexp.js +0 -436
- package/src/service_composition/routing.js +0 -31
- 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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
40
|
+
}
|
|
41
|
+
return currentValues.join(", ");
|
|
18
42
|
};
|
|
19
43
|
|
|
20
44
|
const HEADER_NAMES_COMPOSITION = {
|
|
21
|
-
"accept":
|
|
22
|
-
"accept-charset":
|
|
23
|
-
"accept-language":
|
|
24
|
-
"access-control-allow-headers":
|
|
25
|
-
"access-control-allow-methods":
|
|
26
|
-
"access-control-allow-origin":
|
|
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":
|
|
56
|
+
"server-timing": composeTwoCommaSeparatedValues,
|
|
30
57
|
// 'content-type', // https://github.com/ninenines/cowboy/issues/1230
|
|
31
|
-
"vary":
|
|
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(
|
|
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 =
|
|
39
|
-
|
|
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
|
-
|
|
62
|
+
continue;
|
|
45
63
|
}
|
|
46
|
-
|
|
47
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
+
};
|