@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
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { Abort } from "@jsenv/abort";
|
|
2
|
+
import { createDetailedMessage } from "@jsenv/humanize";
|
|
3
|
+
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
4
|
+
import { parse } from "node:querystring";
|
|
5
|
+
import {
|
|
6
|
+
colorizeResponseStatus,
|
|
7
|
+
statusToType,
|
|
8
|
+
} from "../internal/colorizeResponseStatus.js";
|
|
9
|
+
import { headersFromObject } from "../internal/headersFromObject.js";
|
|
10
|
+
import { parseSingleHeaderWithAttributes } from "../internal/multiple-header.js";
|
|
11
|
+
import { observableFromNodeStream } from "./observable_from_node_stream.js";
|
|
12
|
+
|
|
13
|
+
export const fromNodeRequest = (
|
|
14
|
+
nodeRequest,
|
|
15
|
+
{ serverOrigin, signal, requestBodyLifetime, logger, nagle },
|
|
16
|
+
) => {
|
|
17
|
+
const requestLogger = createRequestLogger(nodeRequest, (type, value) => {
|
|
18
|
+
const logFunction = logger[type];
|
|
19
|
+
logFunction(value);
|
|
20
|
+
});
|
|
21
|
+
nodeRequest.on("error", (error) => {
|
|
22
|
+
if (error.message === "aborted") {
|
|
23
|
+
requestLogger.debug(
|
|
24
|
+
createDetailedMessage(`request aborted by client`, {
|
|
25
|
+
"error message": error.message,
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
} else {
|
|
29
|
+
// I'm not sure this can happen but it's here in case
|
|
30
|
+
requestLogger.error(
|
|
31
|
+
createDetailedMessage(`"error" event emitted on request`, {
|
|
32
|
+
"error stack": error.stack,
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const handleRequestOperation = Abort.startOperation();
|
|
39
|
+
if (signal) {
|
|
40
|
+
handleRequestOperation.addAbortSignal(signal);
|
|
41
|
+
}
|
|
42
|
+
handleRequestOperation.addAbortSource((abort) => {
|
|
43
|
+
nodeRequest.once("close", abort);
|
|
44
|
+
return () => {
|
|
45
|
+
nodeRequest.removeListener("close", abort);
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const headers = headersFromObject(nodeRequest.headers);
|
|
50
|
+
// pause the request body stream to let a chance for other parts of the code to subscribe to the stream
|
|
51
|
+
// Without this the request body readable stream
|
|
52
|
+
// might be closed when we'll try to attach "data" and "end" listeners to it
|
|
53
|
+
nodeRequest.pause();
|
|
54
|
+
if (!nagle) {
|
|
55
|
+
nodeRequest.connection.setNoDelay(true);
|
|
56
|
+
}
|
|
57
|
+
const body = observableFromNodeStream(nodeRequest, {
|
|
58
|
+
readableLifetime: requestBodyLifetime,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
let requestOrigin;
|
|
62
|
+
if (nodeRequest.upgrade) {
|
|
63
|
+
requestOrigin = serverOrigin;
|
|
64
|
+
} else if (nodeRequest.authority) {
|
|
65
|
+
requestOrigin = nodeRequest.connection.encrypted
|
|
66
|
+
? `https://${nodeRequest.authority}`
|
|
67
|
+
: `http://${nodeRequest.authority}`;
|
|
68
|
+
} else if (nodeRequest.headers.host) {
|
|
69
|
+
requestOrigin = nodeRequest.connection.encrypted
|
|
70
|
+
? `https://${nodeRequest.headers.host}`
|
|
71
|
+
: `http://${nodeRequest.headers.host}`;
|
|
72
|
+
} else {
|
|
73
|
+
requestOrigin = serverOrigin;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// check the following parsers if we want to support more request body content types
|
|
77
|
+
// https://github.com/node-formidable/formidable/tree/master/src/parsers
|
|
78
|
+
const buffer = async () => {
|
|
79
|
+
// here we don't really need to warn, one might want to read anything as binary
|
|
80
|
+
// const contentType = headers["content-type"];
|
|
81
|
+
// if (!CONTENT_TYPE.isBinary(contentType)) {
|
|
82
|
+
// console.warn(
|
|
83
|
+
// `buffer() called on a request with content-type: "${contentType}". A binary content-type was expected.`,
|
|
84
|
+
// );
|
|
85
|
+
// }
|
|
86
|
+
const requestBodyBuffer = await readBody(body, { as: "buffer" });
|
|
87
|
+
return requestBodyBuffer;
|
|
88
|
+
};
|
|
89
|
+
// maybe we could use https://github.com/form-data/form-data
|
|
90
|
+
// for now we'll just return { fields, files } it's good enough to work with
|
|
91
|
+
const formData = async () => {
|
|
92
|
+
const contentType = headers["content-type"];
|
|
93
|
+
if (contentType !== "multipart/form-data") {
|
|
94
|
+
console.warn(
|
|
95
|
+
`formData() called on a request with content-type: "${contentType}". multipart/form-data was expected.`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const { formidable } = await import("formidable");
|
|
99
|
+
const form = formidable({});
|
|
100
|
+
nodeRequest.resume(); // was paused in line #53
|
|
101
|
+
const [fields, files] = await form.parse(nodeRequest);
|
|
102
|
+
const requestBodyFormData = { fields, files };
|
|
103
|
+
return requestBodyFormData;
|
|
104
|
+
};
|
|
105
|
+
const text = async () => {
|
|
106
|
+
const contentType = headers["content-type"];
|
|
107
|
+
if (!CONTENT_TYPE.isTextual(contentType)) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`text() called on a request with content-type "${contentType}". A textual content-type was expected.`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const requestBodyString = await readBody(body, { as: "string" });
|
|
113
|
+
return requestBodyString;
|
|
114
|
+
};
|
|
115
|
+
const json = async () => {
|
|
116
|
+
const contentType = headers["content-type"];
|
|
117
|
+
if (!CONTENT_TYPE.isJson(contentType)) {
|
|
118
|
+
console.warn(
|
|
119
|
+
`json() called on a request with content-type "${contentType}". A json content-type was expected.`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const requestBodyString = await readBody(body, { as: "string" });
|
|
123
|
+
const requestBodyJSON = JSON.parse(requestBodyString);
|
|
124
|
+
return requestBodyJSON;
|
|
125
|
+
};
|
|
126
|
+
const queryString = async () => {
|
|
127
|
+
const contentType = headers["content-type"];
|
|
128
|
+
if (contentType !== "application/x-www-form-urlencoded") {
|
|
129
|
+
console.warn(
|
|
130
|
+
`queryString() called on a request with content-type "${contentType}". application/x-www-form-urlencoded was expected.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const requestBodyString = await readBody(body, { as: "string" });
|
|
134
|
+
const requestBodyQueryStringParsed = parse(requestBodyString);
|
|
135
|
+
return requestBodyQueryStringParsed;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// request.ip -> request ip as received by the server
|
|
139
|
+
// request.ipForwarded -> ip of the client before proxying, undefined when there is no proxy
|
|
140
|
+
// same applies on request.proto and request.host
|
|
141
|
+
let ip = nodeRequest.socket.remoteAddress;
|
|
142
|
+
let proto = requestOrigin.startsWith("http:") ? "http" : "https";
|
|
143
|
+
let host = headers["host"];
|
|
144
|
+
const forwarded = headers["forwarded"];
|
|
145
|
+
let hostForwarded;
|
|
146
|
+
let ipForwarded;
|
|
147
|
+
let protoForwarded;
|
|
148
|
+
if (forwarded) {
|
|
149
|
+
const forwardedParsed = parseSingleHeaderWithAttributes(forwarded);
|
|
150
|
+
ipForwarded = forwardedParsed.for;
|
|
151
|
+
protoForwarded = forwardedParsed.proto;
|
|
152
|
+
hostForwarded = forwardedParsed.host;
|
|
153
|
+
} else {
|
|
154
|
+
const forwardedFor = headers["x-forwarded-for"];
|
|
155
|
+
const forwardedProto = headers["x-forwarded-proto"];
|
|
156
|
+
const forwardedHost = headers["x-forwarded-host"];
|
|
157
|
+
if (forwardedFor) {
|
|
158
|
+
// format is <client-ip>, <proxy1>, <proxy2>
|
|
159
|
+
ipForwarded = forwardedFor.split(",")[0];
|
|
160
|
+
}
|
|
161
|
+
if (forwardedProto) {
|
|
162
|
+
protoForwarded = forwardedProto;
|
|
163
|
+
}
|
|
164
|
+
if (forwardedHost) {
|
|
165
|
+
hostForwarded = forwardedHost;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return Object.freeze({
|
|
170
|
+
logger: requestLogger,
|
|
171
|
+
ip,
|
|
172
|
+
ipForwarded,
|
|
173
|
+
proto,
|
|
174
|
+
protoForwarded,
|
|
175
|
+
host,
|
|
176
|
+
hostForwarded,
|
|
177
|
+
params: {},
|
|
178
|
+
signal: handleRequestOperation.signal,
|
|
179
|
+
http2: Boolean(nodeRequest.stream),
|
|
180
|
+
origin: requestOrigin,
|
|
181
|
+
...getPropertiesFromResource({
|
|
182
|
+
resource: nodeRequest.url,
|
|
183
|
+
baseUrl: requestOrigin,
|
|
184
|
+
}),
|
|
185
|
+
method: nodeRequest.method,
|
|
186
|
+
headers,
|
|
187
|
+
body,
|
|
188
|
+
buffer,
|
|
189
|
+
formData,
|
|
190
|
+
text,
|
|
191
|
+
json,
|
|
192
|
+
queryString,
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const createRequestLogger = (nodeRequest, write) => {
|
|
197
|
+
// Handling request is asynchronous, we buffer logs for that request
|
|
198
|
+
// until we know what happens with that request
|
|
199
|
+
// It delays logs until we know of the request will be handled
|
|
200
|
+
// but it's mandatory to make logs readable.
|
|
201
|
+
|
|
202
|
+
const logArray = [];
|
|
203
|
+
const childArray = [];
|
|
204
|
+
const add = ({ type, value }) => {
|
|
205
|
+
logArray.push({ type, value });
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const requestLogger = {
|
|
209
|
+
logArray,
|
|
210
|
+
childArray,
|
|
211
|
+
hasPushChild: false,
|
|
212
|
+
forPush: () => {
|
|
213
|
+
const childLogBuffer = createRequestLogger(nodeRequest, write);
|
|
214
|
+
childLogBuffer.isChild = true;
|
|
215
|
+
childArray.push(childLogBuffer);
|
|
216
|
+
requestLogger.hasPushChild = true;
|
|
217
|
+
return childLogBuffer;
|
|
218
|
+
},
|
|
219
|
+
debug: (value) => {
|
|
220
|
+
add({
|
|
221
|
+
type: "debug",
|
|
222
|
+
value,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
info: (value) => {
|
|
226
|
+
add({
|
|
227
|
+
type: "info",
|
|
228
|
+
value,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
warn: (value) => {
|
|
232
|
+
add({
|
|
233
|
+
type: "warn",
|
|
234
|
+
value,
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
error: (value) => {
|
|
238
|
+
add({
|
|
239
|
+
type: "error",
|
|
240
|
+
value,
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
onHeadersSent: ({ status, statusText }) => {
|
|
244
|
+
const statusType = statusToType(status);
|
|
245
|
+
let message = `${colorizeResponseStatus(status)}`;
|
|
246
|
+
if (statusText) {
|
|
247
|
+
message += ` ${statusText}`;
|
|
248
|
+
}
|
|
249
|
+
add({
|
|
250
|
+
type:
|
|
251
|
+
status === 404 && nodeRequest.path === "/favicon.ico"
|
|
252
|
+
? "debug"
|
|
253
|
+
: {
|
|
254
|
+
information: "info",
|
|
255
|
+
success: "info",
|
|
256
|
+
redirection: "info",
|
|
257
|
+
client_error: "warn",
|
|
258
|
+
server_error: "error",
|
|
259
|
+
}[statusType] || "error",
|
|
260
|
+
value: message,
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
ended: false,
|
|
264
|
+
end: () => {
|
|
265
|
+
if (requestLogger.ended) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
requestLogger.ended = true;
|
|
269
|
+
if (requestLogger.isChild) {
|
|
270
|
+
// keep buffering until root request write logs for everyone
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const prefixLines = (string, prefix) => {
|
|
274
|
+
return string.replace(/^(?!\s*$)/gm, prefix);
|
|
275
|
+
};
|
|
276
|
+
const writeLog = (
|
|
277
|
+
{ type, value },
|
|
278
|
+
{ someLogIsError, someLogIsWarn, depth },
|
|
279
|
+
) => {
|
|
280
|
+
if (depth > 0) {
|
|
281
|
+
value = prefixLines(value, " ".repeat(depth));
|
|
282
|
+
}
|
|
283
|
+
if (type === "info") {
|
|
284
|
+
if (someLogIsError) {
|
|
285
|
+
type = "error";
|
|
286
|
+
} else if (someLogIsWarn) {
|
|
287
|
+
type = "warn";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
write(type, value);
|
|
291
|
+
};
|
|
292
|
+
const writeLogs = (loggerToWrite, depth) => {
|
|
293
|
+
let someLogIsError = false;
|
|
294
|
+
let someLogIsWarn = false;
|
|
295
|
+
for (const log of loggerToWrite.logArray) {
|
|
296
|
+
if (log.type === "error") {
|
|
297
|
+
someLogIsError = true;
|
|
298
|
+
}
|
|
299
|
+
if (log.type === "warn") {
|
|
300
|
+
someLogIsWarn = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const firstLog = loggerToWrite.logArray.shift();
|
|
304
|
+
const lastLog = loggerToWrite.logArray.pop();
|
|
305
|
+
const middleLogs = loggerToWrite.logArray;
|
|
306
|
+
if (!firstLog) {
|
|
307
|
+
debugger;
|
|
308
|
+
}
|
|
309
|
+
writeLog(firstLog, {
|
|
310
|
+
someLogIsError,
|
|
311
|
+
someLogIsWarn,
|
|
312
|
+
depth,
|
|
313
|
+
});
|
|
314
|
+
for (const middleLog of middleLogs) {
|
|
315
|
+
writeLog(middleLog, {
|
|
316
|
+
someLogIsError,
|
|
317
|
+
someLogIsWarn,
|
|
318
|
+
depth,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
for (const childLoggerToWrite of loggerToWrite.childArray) {
|
|
322
|
+
writeLogs(childLoggerToWrite, depth + 1);
|
|
323
|
+
}
|
|
324
|
+
if (lastLog) {
|
|
325
|
+
writeLog(lastLog, {
|
|
326
|
+
someLogIsError,
|
|
327
|
+
someLogIsWarn,
|
|
328
|
+
depth: depth + 1,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
writeLogs(requestLogger, 0);
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return requestLogger;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const readBody = (body, { as }) => {
|
|
340
|
+
return new Promise((resolve, reject) => {
|
|
341
|
+
const bufferArray = [];
|
|
342
|
+
body.subscribe({
|
|
343
|
+
error: reject,
|
|
344
|
+
next: (buffer) => {
|
|
345
|
+
bufferArray.push(buffer);
|
|
346
|
+
},
|
|
347
|
+
complete: () => {
|
|
348
|
+
const bodyAsBuffer = Buffer.concat(bufferArray);
|
|
349
|
+
if (as === "buffer") {
|
|
350
|
+
resolve(bodyAsBuffer);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (as === "string") {
|
|
354
|
+
const bodyAsString = bodyAsBuffer.toString();
|
|
355
|
+
resolve(bodyAsString);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (as === "json") {
|
|
359
|
+
const bodyAsString = bodyAsBuffer.toString();
|
|
360
|
+
const bodyAsJSON = JSON.parse(bodyAsString);
|
|
361
|
+
resolve(bodyAsJSON);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
// exported for unit tests
|
|
369
|
+
export const readRequestBody = (request, { as }) => {
|
|
370
|
+
if (as === "string") {
|
|
371
|
+
return request.text();
|
|
372
|
+
}
|
|
373
|
+
if (as === "buffer") {
|
|
374
|
+
return request.buffer();
|
|
375
|
+
}
|
|
376
|
+
if (as === "json") {
|
|
377
|
+
return request.json();
|
|
378
|
+
}
|
|
379
|
+
throw new Error(`unsupported ${as}`);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
export const applyRedirectionToRequest = (
|
|
383
|
+
request,
|
|
384
|
+
{ resource, pathname, ...rest },
|
|
385
|
+
) => {
|
|
386
|
+
return {
|
|
387
|
+
...request,
|
|
388
|
+
...(resource
|
|
389
|
+
? getPropertiesFromResource({
|
|
390
|
+
resource,
|
|
391
|
+
baseUrl: request.url,
|
|
392
|
+
})
|
|
393
|
+
: pathname
|
|
394
|
+
? getPropertiesFromPathname({
|
|
395
|
+
pathname,
|
|
396
|
+
baseUrl: request.url,
|
|
397
|
+
})
|
|
398
|
+
: {}),
|
|
399
|
+
...rest,
|
|
400
|
+
};
|
|
401
|
+
};
|
|
402
|
+
const getPropertiesFromResource = ({ resource, baseUrl }) => {
|
|
403
|
+
const urlObject = new URL(resource, baseUrl);
|
|
404
|
+
let pathname = urlObject.pathname;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
url: String(urlObject),
|
|
408
|
+
searchParams: urlObject.searchParams,
|
|
409
|
+
pathname,
|
|
410
|
+
resource,
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
const getPropertiesFromPathname = ({ pathname, baseUrl }) => {
|
|
414
|
+
return getPropertiesFromResource({
|
|
415
|
+
resource: `${pathname}${new URL(baseUrl).search}`,
|
|
416
|
+
baseUrl,
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export const createPushRequest = (
|
|
421
|
+
request,
|
|
422
|
+
{ signal, pathname, method, logger },
|
|
423
|
+
) => {
|
|
424
|
+
const pushRequest = Object.freeze({
|
|
425
|
+
...request,
|
|
426
|
+
logger,
|
|
427
|
+
parent: request,
|
|
428
|
+
signal,
|
|
429
|
+
http2: true,
|
|
430
|
+
...(pathname
|
|
431
|
+
? getPropertiesFromPathname({
|
|
432
|
+
pathname,
|
|
433
|
+
baseUrl: request.url,
|
|
434
|
+
})
|
|
435
|
+
: {}),
|
|
436
|
+
method: method || request.method,
|
|
437
|
+
headers: getHeadersInheritedByPushRequest(request),
|
|
438
|
+
body: undefined,
|
|
439
|
+
});
|
|
440
|
+
return pushRequest;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const getHeadersInheritedByPushRequest = (request) => {
|
|
444
|
+
const headersInherited = { ...request.headers };
|
|
445
|
+
// mtime sent by the client in request headers concerns the main request
|
|
446
|
+
// Time remains valid for request to other resources so we keep it
|
|
447
|
+
// in child requests
|
|
448
|
+
// delete childHeaders["if-modified-since"]
|
|
449
|
+
|
|
450
|
+
// eTag sent by the client in request headers concerns the main request
|
|
451
|
+
// A request made to an other resource must not inherit the eTag
|
|
452
|
+
delete headersInherited["if-none-match"];
|
|
453
|
+
|
|
454
|
+
return headersInherited;
|
|
455
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Readable, Stream, Writable } from "node:stream";
|
|
2
|
+
import { isObservable } from "./observable.js";
|
|
3
|
+
import { isFileHandle } from "./observable_from_file_handle.js";
|
|
4
|
+
|
|
5
|
+
export const getObservableValueType = (value) => {
|
|
6
|
+
if (value && typeof value.then === "function") {
|
|
7
|
+
return "promise";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (isObservable(value)) {
|
|
11
|
+
return "observable";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (isFileHandle(value)) {
|
|
15
|
+
return "file_handle";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isNodeStream(value)) {
|
|
19
|
+
return "node_stream";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (value instanceof ReadableStream) {
|
|
23
|
+
return "node_web_stream";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return "js_value";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const isNodeStream = (value) => {
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
value instanceof Stream ||
|
|
36
|
+
value instanceof Writable ||
|
|
37
|
+
value instanceof Readable
|
|
38
|
+
) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false;
|
|
43
|
+
};
|