@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.
- 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 +19 -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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standard ways to create a Response
|
|
3
|
+
* - new Response(body, init)
|
|
4
|
+
* - Response.json(data, init)
|
|
5
|
+
* Here we need a way to tell: I want to handle websocket
|
|
6
|
+
* to align with the style of new Response and Response.json to make it look as follow:
|
|
7
|
+
* ```js
|
|
8
|
+
* import { WebSocketResponse } from "@jsenv/server"
|
|
9
|
+
* new WebSocketResponse((websocket) => {
|
|
10
|
+
* // do stuff with the websocket
|
|
11
|
+
* })
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* But we don't really need a class so we are just returning a regular object under the hood
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class WebSocketResponse {
|
|
18
|
+
constructor(
|
|
19
|
+
webSocketHandler,
|
|
20
|
+
{
|
|
21
|
+
status = 101,
|
|
22
|
+
statusText = status === 101 ? "Switching Protocols" : undefined,
|
|
23
|
+
headers,
|
|
24
|
+
} = {},
|
|
25
|
+
) {
|
|
26
|
+
const webSocketResponse = {
|
|
27
|
+
status,
|
|
28
|
+
statusText,
|
|
29
|
+
headers,
|
|
30
|
+
body: {
|
|
31
|
+
websocket: webSocketHandler,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
// eslint-disable-next-line no-constructor-return
|
|
35
|
+
return webSocketResponse;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const isWebSocketResponse = (responseProperties) => {
|
|
40
|
+
return (
|
|
41
|
+
responseProperties.body &&
|
|
42
|
+
typeof responseProperties.body.websocket === "function"
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const getWebSocketHandler = (responseProperties) => {
|
|
47
|
+
const responseBody = responseProperties.body;
|
|
48
|
+
if (!responseBody) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const webSocketHandler = responseBody.websocket;
|
|
52
|
+
return webSocketHandler;
|
|
53
|
+
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const fromFetchResponse = (fetchResponse) => {
|
|
2
|
-
const responseHeaders = {};
|
|
3
|
-
const headersToIgnore = ["connection"];
|
|
4
|
-
fetchResponse.headers.forEach((value, name) => {
|
|
5
|
-
if (!headersToIgnore.includes(name)) {
|
|
6
|
-
responseHeaders[name] = value;
|
|
7
|
-
}
|
|
8
|
-
});
|
|
9
|
-
return {
|
|
10
|
-
status: fetchResponse.status,
|
|
11
|
-
statusText: fetchResponse.statusText,
|
|
12
|
-
headers: responseHeaders,
|
|
13
|
-
body: fetchResponse.body, // node-fetch assumed
|
|
14
|
-
};
|
|
15
|
-
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { createReadStream } from "node:fs";
|
|
2
|
-
import { Readable, Stream, Writable } from "node:stream";
|
|
3
|
-
|
|
4
|
-
import { isObservable, observableFromValue } from "./observable.js";
|
|
5
|
-
import { observableFromNodeStream } from "./observable_from_node_stream.js";
|
|
6
|
-
|
|
7
|
-
export const normalizeBodyMethods = (body) => {
|
|
8
|
-
if (isObservable(body)) {
|
|
9
|
-
return {
|
|
10
|
-
asObservable: () => body,
|
|
11
|
-
destroy: () => {},
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (isFileHandle(body)) {
|
|
16
|
-
return {
|
|
17
|
-
asObservable: () => fileHandleToObservable(body),
|
|
18
|
-
destroy: () => {
|
|
19
|
-
body.close();
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (isNodeStream(body)) {
|
|
25
|
-
return {
|
|
26
|
-
asObservable: () => observableFromNodeStream(body),
|
|
27
|
-
destroy: () => {
|
|
28
|
-
body.destroy();
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
asObservable: () => observableFromValue(body),
|
|
35
|
-
destroy: () => {},
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const isFileHandle = (value) => {
|
|
40
|
-
return value && value.constructor && value.constructor.name === "FileHandle";
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export const fileHandleToReadableStream = (fileHandle) => {
|
|
44
|
-
const fileReadableStream =
|
|
45
|
-
typeof fileHandle.createReadStream === "function"
|
|
46
|
-
? fileHandle.createReadStream()
|
|
47
|
-
: createReadStream(
|
|
48
|
-
"/toto", // is it ok to pass a fake path like this?
|
|
49
|
-
{
|
|
50
|
-
fd: fileHandle.fd,
|
|
51
|
-
emitClose: true,
|
|
52
|
-
// autoClose: true
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
// I suppose it's required only when doing fs.createReadStream()
|
|
56
|
-
// and not fileHandle.createReadStream()
|
|
57
|
-
// fileReadableStream.on("end", () => {
|
|
58
|
-
// fileHandle.close()
|
|
59
|
-
// })
|
|
60
|
-
return fileReadableStream;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const fileHandleToObservable = (fileHandle) => {
|
|
64
|
-
return observableFromNodeStream(fileHandleToReadableStream(fileHandle));
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const isNodeStream = (value) => {
|
|
68
|
-
if (value === undefined) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (
|
|
73
|
-
value instanceof Stream ||
|
|
74
|
-
value instanceof Writable ||
|
|
75
|
-
value instanceof Readable
|
|
76
|
-
) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return false;
|
|
81
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { Abort } from "@jsenv/abort";
|
|
2
|
-
import { headersFromObject } from "../internal/headersFromObject.js";
|
|
3
|
-
import { observableFromNodeStream } from "./observable_from_node_stream.js";
|
|
4
|
-
|
|
5
|
-
export const fromNodeRequest = (
|
|
6
|
-
nodeRequest,
|
|
7
|
-
{ serverOrigin, signal, requestBodyLifetime },
|
|
8
|
-
) => {
|
|
9
|
-
const handleRequestOperation = Abort.startOperation();
|
|
10
|
-
if (signal) {
|
|
11
|
-
handleRequestOperation.addAbortSignal(signal);
|
|
12
|
-
}
|
|
13
|
-
handleRequestOperation.addAbortSource((abort) => {
|
|
14
|
-
nodeRequest.once("close", abort);
|
|
15
|
-
return () => {
|
|
16
|
-
nodeRequest.removeListener("close", abort);
|
|
17
|
-
};
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const headers = headersFromObject(nodeRequest.headers);
|
|
21
|
-
const body = observableFromNodeStream(nodeRequest, {
|
|
22
|
-
readableStreamLifetime: requestBodyLifetime,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
let requestOrigin;
|
|
26
|
-
if (nodeRequest.upgrade) {
|
|
27
|
-
requestOrigin = serverOrigin;
|
|
28
|
-
} else if (nodeRequest.authority) {
|
|
29
|
-
requestOrigin = nodeRequest.connection.encrypted
|
|
30
|
-
? `https://${nodeRequest.authority}`
|
|
31
|
-
: `http://${nodeRequest.authority}`;
|
|
32
|
-
} else if (nodeRequest.headers.host) {
|
|
33
|
-
requestOrigin = nodeRequest.connection.encrypted
|
|
34
|
-
? `https://${nodeRequest.headers.host}`
|
|
35
|
-
: `http://${nodeRequest.headers.host}`;
|
|
36
|
-
} else {
|
|
37
|
-
requestOrigin = serverOrigin;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return Object.freeze({
|
|
41
|
-
signal: handleRequestOperation.signal,
|
|
42
|
-
http2: Boolean(nodeRequest.stream),
|
|
43
|
-
origin: requestOrigin,
|
|
44
|
-
...getPropertiesFromResource({
|
|
45
|
-
resource: nodeRequest.url,
|
|
46
|
-
baseUrl: requestOrigin,
|
|
47
|
-
}),
|
|
48
|
-
method: nodeRequest.method,
|
|
49
|
-
headers,
|
|
50
|
-
body,
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const applyRedirectionToRequest = (
|
|
55
|
-
request,
|
|
56
|
-
{ resource, pathname, ...rest },
|
|
57
|
-
) => {
|
|
58
|
-
return {
|
|
59
|
-
...request,
|
|
60
|
-
...(resource
|
|
61
|
-
? getPropertiesFromResource({
|
|
62
|
-
resource,
|
|
63
|
-
baseUrl: request.url,
|
|
64
|
-
})
|
|
65
|
-
: pathname
|
|
66
|
-
? getPropertiesFromPathname({
|
|
67
|
-
pathname,
|
|
68
|
-
baseUrl: request.url,
|
|
69
|
-
})
|
|
70
|
-
: {}),
|
|
71
|
-
...rest,
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const getPropertiesFromResource = ({ resource, baseUrl }) => {
|
|
76
|
-
const urlObject = new URL(resource, baseUrl);
|
|
77
|
-
let pathname = urlObject.pathname;
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
url: String(urlObject),
|
|
81
|
-
searchParams: urlObject.searchParams,
|
|
82
|
-
pathname,
|
|
83
|
-
resource,
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const getPropertiesFromPathname = ({ pathname, baseUrl }) => {
|
|
88
|
-
return getPropertiesFromResource({
|
|
89
|
-
resource: `${pathname}${new URL(baseUrl).search}`,
|
|
90
|
-
baseUrl,
|
|
91
|
-
});
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export const createPushRequest = (request, { signal, pathname, method }) => {
|
|
95
|
-
const pushRequest = Object.freeze({
|
|
96
|
-
...request,
|
|
97
|
-
parent: request,
|
|
98
|
-
signal,
|
|
99
|
-
http2: true,
|
|
100
|
-
...(pathname
|
|
101
|
-
? getPropertiesFromPathname({
|
|
102
|
-
pathname,
|
|
103
|
-
baseUrl: request.url,
|
|
104
|
-
})
|
|
105
|
-
: {}),
|
|
106
|
-
method: method || request.method,
|
|
107
|
-
headers: getHeadersInheritedByPushRequest(request),
|
|
108
|
-
body: undefined,
|
|
109
|
-
});
|
|
110
|
-
return pushRequest;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const getHeadersInheritedByPushRequest = (request) => {
|
|
114
|
-
const headersInherited = { ...request.headers };
|
|
115
|
-
// mtime sent by the client in request headers concerns the main request
|
|
116
|
-
// Time remains valid for request to other resources so we keep it
|
|
117
|
-
// in child requests
|
|
118
|
-
// delete childHeaders["if-modified-since"]
|
|
119
|
-
|
|
120
|
-
// eTag sent by the client in request headers concerns the main request
|
|
121
|
-
// A request made to an other resource must not inherit the eTag
|
|
122
|
-
delete headersInherited["if-none-match"];
|
|
123
|
-
|
|
124
|
-
return headersInherited;
|
|
125
|
-
};
|
package/src/readRequestBody.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export const readRequestBody = (request, { as = "string" } = {}) => {
|
|
2
|
-
return new Promise((resolve, reject) => {
|
|
3
|
-
const bufferArray = [];
|
|
4
|
-
request.body.subscribe({
|
|
5
|
-
error: reject,
|
|
6
|
-
next: (buffer) => {
|
|
7
|
-
bufferArray.push(buffer);
|
|
8
|
-
},
|
|
9
|
-
complete: () => {
|
|
10
|
-
const bodyAsBuffer = Buffer.concat(bufferArray);
|
|
11
|
-
|
|
12
|
-
if (as === "buffer") {
|
|
13
|
-
resolve(bodyAsBuffer);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (as === "string") {
|
|
18
|
-
const bodyAsString = bodyAsBuffer.toString();
|
|
19
|
-
resolve(bodyAsString);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (as === "json") {
|
|
24
|
-
const bodyAsString = bodyAsBuffer.toString();
|
|
25
|
-
const bodyAsJSON = JSON.parse(bodyAsString);
|
|
26
|
-
resolve(bodyAsJSON);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { performance } from "node:perf_hooks";
|
|
2
|
-
|
|
3
|
-
export const timeStart = (name) => {
|
|
4
|
-
// as specified in https://w3c.github.io/server-timing/#the-performanceservertiming-interface
|
|
5
|
-
// duration is a https://www.w3.org/TR/hr-time-2/#sec-domhighrestimestamp
|
|
6
|
-
const startTimestamp = performance.now();
|
|
7
|
-
const timeEnd = () => {
|
|
8
|
-
const endTimestamp = performance.now();
|
|
9
|
-
const timing = {
|
|
10
|
-
[name]: endTimestamp - startTimestamp,
|
|
11
|
-
};
|
|
12
|
-
return timing;
|
|
13
|
-
};
|
|
14
|
-
return timeEnd;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const timeFunction = (name, fn) => {
|
|
18
|
-
const timeEnd = timeStart(name);
|
|
19
|
-
const returnValue = fn();
|
|
20
|
-
if (returnValue && typeof returnValue.then === "function") {
|
|
21
|
-
return returnValue.then((value) => {
|
|
22
|
-
return [timeEnd(), value];
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
return [timeEnd(), returnValue];
|
|
26
|
-
};
|