@jsenv/server 15.0.2 → 15.0.3
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 +23 -23
- package/package.json +3 -3
- package/src/content_negotiation/pick_accepted_content.js +13 -13
- package/src/content_negotiation/pick_content_encoding.js +25 -25
- package/src/content_negotiation/pick_content_language.js +35 -34
- package/src/content_negotiation/pick_content_type.js +29 -29
- package/src/fetch_filesystem.js +110 -107
- package/src/from_fetch_response.js +6 -6
- package/src/interfacing_with_node/body.js +22 -22
- package/src/interfacing_with_node/observable.js +80 -80
- package/src/interfacing_with_node/observable_from_node_stream.js +35 -35
- package/src/interfacing_with_node/request_factory.js +26 -26
- package/src/interfacing_with_node/write_node_response.js +82 -82
- package/src/internal/colorizeResponseStatus.js +26 -26
- package/src/internal/convertFileSystemErrorToResponseProperties.js +10 -10
- package/src/internal/dns_resolution.js +7 -7
- package/src/internal/etag.js +11 -11
- package/src/internal/filesystem.js +19 -19
- package/src/internal/headersFromObject.js +8 -8
- package/src/internal/headersFromString.js +11 -11
- package/src/internal/headersToObject.js +5 -5
- package/src/internal/headersToString.js +7 -7
- package/src/internal/headers_composition.js +9 -9
- package/src/internal/hostname_parser.js +9 -9
- package/src/internal/listen.js +47 -47
- package/src/internal/listenClientError.js +10 -10
- package/src/internal/listenEvent.js +5 -5
- package/src/internal/listenRequest.js +8 -8
- package/src/internal/listenServerConnectionError.js +19 -19
- package/src/internal/multiple-header.js +50 -50
- package/src/internal/normalizeHeaderName.js +4 -4
- package/src/internal/normalizeHeaderValue.js +2 -2
- package/src/internal/object_composition.js +41 -39
- package/src/internal/response_composition.js +6 -6
- package/src/internal/server-polyglot.js +42 -42
- package/src/internal/server_ips.js +20 -20
- package/src/internal/trackServerPendingConnections.js +23 -23
- package/src/internal/trackServerPendingRequests.js +28 -28
- package/src/main.js +18 -18
- package/src/readRequestBody.js +15 -15
- package/src/serve_directory.js +16 -16
- package/src/server_timing/timing_header.js +24 -24
- package/src/server_timing/timing_measure.js +14 -14
- package/src/service_composition/path_to_regexp.js +173 -168
- package/src/service_composition/routing.js +12 -12
- package/src/service_controller.js +77 -77
- package/src/services/cors/jsenv_service_cors.js +29 -29
- package/src/services/error_handler/jsenv_service_error_handler.js +18 -18
- package/src/services/request_aliases/jsenv_service_request_aliases.js +20 -20
- package/src/services/response_acceptance_check/jsenv_service_response_acceptance_check.js +16 -16
- package/src/sse/sse_room.js +117 -115
- package/src/start_server.js +328 -328
- package/src/stopReasons.js +9 -9
package/src/sse/sse_room.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { createLogger } from "@jsenv/log"
|
|
1
|
+
import { createLogger } from "@jsenv/log";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
createObservable,
|
|
5
5
|
createCompositeProducer,
|
|
6
|
-
} from "@jsenv/server/src/interfacing_with_node/observable.js"
|
|
6
|
+
} from "@jsenv/server/src/interfacing_with_node/observable.js";
|
|
7
7
|
|
|
8
8
|
// https://www.html5rocks.com/en/tutorials/eventsource/basics/
|
|
9
9
|
export const createSSERoom = ({
|
|
@@ -19,36 +19,36 @@ export const createSSERoom = ({
|
|
|
19
19
|
welcomeEventEnabled = false,
|
|
20
20
|
welcomeEventPublic = false, // decides if welcome event are sent to other clients
|
|
21
21
|
} = {}) => {
|
|
22
|
-
const logger = createLogger({ logLevel })
|
|
22
|
+
const logger = createLogger({ logLevel });
|
|
23
23
|
|
|
24
|
-
const room = {}
|
|
25
|
-
const clients = new Set()
|
|
26
|
-
const eventHistory = createEventHistory(historyLength)
|
|
24
|
+
const room = {};
|
|
25
|
+
const clients = new Set();
|
|
26
|
+
const eventHistory = createEventHistory(historyLength);
|
|
27
27
|
// what about previousEventId that keeps growing ?
|
|
28
28
|
// we could add some limit
|
|
29
29
|
// one limit could be that an event older than 24h is deleted
|
|
30
|
-
let previousEventId = 0
|
|
31
|
-
let opened = false
|
|
32
|
-
let interval
|
|
33
|
-
let cleanupEffect = CLEANUP_NOOP
|
|
30
|
+
let previousEventId = 0;
|
|
31
|
+
let opened = false;
|
|
32
|
+
let interval;
|
|
33
|
+
let cleanupEffect = CLEANUP_NOOP;
|
|
34
34
|
|
|
35
35
|
const join = (request) => {
|
|
36
36
|
// should we ensure a given request can join a room only once?
|
|
37
37
|
|
|
38
38
|
const lastKnownId =
|
|
39
39
|
request.headers["last-event-id"] ||
|
|
40
|
-
new URL(request.url).searchParams.get("last-event-id")
|
|
40
|
+
new URL(request.url).searchParams.get("last-event-id");
|
|
41
41
|
|
|
42
42
|
if (clients.size >= maxClientAllowed) {
|
|
43
43
|
return {
|
|
44
44
|
status: 503,
|
|
45
|
-
}
|
|
45
|
+
};
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
if (!opened) {
|
|
49
49
|
return {
|
|
50
50
|
status: 204,
|
|
51
|
-
}
|
|
51
|
+
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const sseRoomObservable = createObservable(({ next, complete }) => {
|
|
@@ -56,30 +56,30 @@ export const createSSERoom = ({
|
|
|
56
56
|
next,
|
|
57
57
|
complete,
|
|
58
58
|
request,
|
|
59
|
-
}
|
|
59
|
+
};
|
|
60
60
|
if (clients.size === 0) {
|
|
61
|
-
const effectReturnValue = effect()
|
|
61
|
+
const effectReturnValue = effect();
|
|
62
62
|
if (typeof effectReturnValue === "function") {
|
|
63
|
-
cleanupEffect = effectReturnValue
|
|
63
|
+
cleanupEffect = effectReturnValue;
|
|
64
64
|
} else {
|
|
65
|
-
cleanupEffect = CLEANUP_NOOP
|
|
65
|
+
cleanupEffect = CLEANUP_NOOP;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
clients.add(client)
|
|
68
|
+
clients.add(client);
|
|
69
69
|
logger.debug(
|
|
70
70
|
`A client has joined. Number of client in room: ${clients.size}`,
|
|
71
|
-
)
|
|
71
|
+
);
|
|
72
72
|
|
|
73
73
|
if (lastKnownId !== undefined) {
|
|
74
|
-
const previousEvents = getAllEventSince(lastKnownId)
|
|
75
|
-
const eventMissedCount = previousEvents.length
|
|
74
|
+
const previousEvents = getAllEventSince(lastKnownId);
|
|
75
|
+
const eventMissedCount = previousEvents.length;
|
|
76
76
|
if (eventMissedCount > 0) {
|
|
77
77
|
logger.info(
|
|
78
78
|
`send ${eventMissedCount} event missed by client since event with id "${lastKnownId}"`,
|
|
79
|
-
)
|
|
79
|
+
);
|
|
80
80
|
previousEvents.forEach((previousEvent) => {
|
|
81
|
-
next(stringifySourceEvent(previousEvent))
|
|
82
|
-
})
|
|
81
|
+
next(stringifySourceEvent(previousEvent));
|
|
82
|
+
});
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -88,43 +88,45 @@ export const createSSERoom = ({
|
|
|
88
88
|
retry: retryDuration,
|
|
89
89
|
type: "welcome",
|
|
90
90
|
data: new Date().toLocaleTimeString(),
|
|
91
|
-
}
|
|
92
|
-
addEventToHistory(welcomeEvent)
|
|
91
|
+
};
|
|
92
|
+
addEventToHistory(welcomeEvent);
|
|
93
93
|
|
|
94
94
|
// send to everyone
|
|
95
95
|
if (welcomeEventPublic) {
|
|
96
96
|
sendEventToAllClients(welcomeEvent, {
|
|
97
97
|
history: false,
|
|
98
|
-
})
|
|
98
|
+
});
|
|
99
99
|
}
|
|
100
100
|
// send only to this client
|
|
101
101
|
else {
|
|
102
|
-
next(stringifySourceEvent(welcomeEvent))
|
|
102
|
+
next(stringifySourceEvent(welcomeEvent));
|
|
103
103
|
}
|
|
104
104
|
} else {
|
|
105
105
|
const firstEvent = {
|
|
106
106
|
retry: retryDuration,
|
|
107
107
|
type: "comment",
|
|
108
108
|
data: new Date().toLocaleTimeString(),
|
|
109
|
-
}
|
|
110
|
-
next(stringifySourceEvent(firstEvent))
|
|
109
|
+
};
|
|
110
|
+
next(stringifySourceEvent(firstEvent));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
return () => {
|
|
114
|
-
clients.delete(client)
|
|
114
|
+
clients.delete(client);
|
|
115
115
|
if (clients.size === 0) {
|
|
116
|
-
cleanupEffect()
|
|
117
|
-
cleanupEffect = CLEANUP_NOOP
|
|
116
|
+
cleanupEffect();
|
|
117
|
+
cleanupEffect = CLEANUP_NOOP;
|
|
118
118
|
}
|
|
119
|
-
logger.debug(
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
logger.debug(
|
|
120
|
+
`A client left. Number of client in room: ${clients.size}`,
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
});
|
|
122
124
|
|
|
123
125
|
const requestSSEObservable = connectRequestAndRoom(
|
|
124
126
|
request,
|
|
125
127
|
room,
|
|
126
128
|
sseRoomObservable,
|
|
127
|
-
)
|
|
129
|
+
);
|
|
128
130
|
|
|
129
131
|
return {
|
|
130
132
|
status: 200,
|
|
@@ -134,76 +136,76 @@ export const createSSERoom = ({
|
|
|
134
136
|
"connection": "keep-alive",
|
|
135
137
|
},
|
|
136
138
|
body: requestSSEObservable,
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
139
141
|
|
|
140
142
|
const leave = (request) => {
|
|
141
|
-
disconnectRequestFromRoom(request, room)
|
|
142
|
-
}
|
|
143
|
+
disconnectRequestFromRoom(request, room);
|
|
144
|
+
};
|
|
143
145
|
|
|
144
146
|
const addEventToHistory = (event) => {
|
|
145
147
|
if (typeof event.id === "undefined") {
|
|
146
|
-
event.id = computeEventId(event, previousEventId)
|
|
148
|
+
event.id = computeEventId(event, previousEventId);
|
|
147
149
|
}
|
|
148
|
-
previousEventId = event.id
|
|
149
|
-
eventHistory.add(event)
|
|
150
|
-
}
|
|
150
|
+
previousEventId = event.id;
|
|
151
|
+
eventHistory.add(event);
|
|
152
|
+
};
|
|
151
153
|
|
|
152
154
|
const sendEventToAllClients = (event, { history = true } = {}) => {
|
|
153
155
|
if (history) {
|
|
154
|
-
addEventToHistory(event)
|
|
156
|
+
addEventToHistory(event);
|
|
155
157
|
}
|
|
156
158
|
logger.debug(
|
|
157
159
|
`send "${event.type}" event to ${clients.size} client in the room`,
|
|
158
|
-
)
|
|
159
|
-
const eventString = stringifySourceEvent(event)
|
|
160
|
+
);
|
|
161
|
+
const eventString = stringifySourceEvent(event);
|
|
160
162
|
clients.forEach((client) => {
|
|
161
|
-
client.next(eventString)
|
|
162
|
-
})
|
|
163
|
-
}
|
|
163
|
+
client.next(eventString);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
164
166
|
|
|
165
167
|
const getAllEventSince = (id) => {
|
|
166
|
-
const events = eventHistory.since(id)
|
|
168
|
+
const events = eventHistory.since(id);
|
|
167
169
|
if (welcomeEventEnabled && !welcomeEventPublic) {
|
|
168
|
-
return events.filter((event) => event.type !== "welcome")
|
|
170
|
+
return events.filter((event) => event.type !== "welcome");
|
|
169
171
|
}
|
|
170
|
-
return events
|
|
171
|
-
}
|
|
172
|
+
return events;
|
|
173
|
+
};
|
|
172
174
|
|
|
173
175
|
const keepAlive = () => {
|
|
174
176
|
// maybe that, when an event occurs, we can delay the keep alive event
|
|
175
177
|
logger.debug(
|
|
176
178
|
`send keep alive event, number of client listening event source: ${clients.size}`,
|
|
177
|
-
)
|
|
179
|
+
);
|
|
178
180
|
sendEventToAllClients(
|
|
179
181
|
{
|
|
180
182
|
type: "comment",
|
|
181
183
|
data: new Date().toLocaleTimeString(),
|
|
182
184
|
},
|
|
183
185
|
{ history: false },
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
+
);
|
|
187
|
+
};
|
|
186
188
|
|
|
187
189
|
const open = () => {
|
|
188
|
-
if (opened) return
|
|
189
|
-
opened = true
|
|
190
|
-
interval = setInterval(keepAlive, keepaliveDuration)
|
|
190
|
+
if (opened) return;
|
|
191
|
+
opened = true;
|
|
192
|
+
interval = setInterval(keepAlive, keepaliveDuration);
|
|
191
193
|
if (!keepProcessAlive) {
|
|
192
|
-
interval.unref()
|
|
194
|
+
interval.unref();
|
|
193
195
|
}
|
|
194
|
-
}
|
|
196
|
+
};
|
|
195
197
|
|
|
196
198
|
const close = () => {
|
|
197
|
-
if (!opened) return
|
|
198
|
-
logger.debug(`closing room, number of client in the room: ${clients.size}`)
|
|
199
|
-
clients.forEach((client) => client.complete())
|
|
200
|
-
clients.clear()
|
|
201
|
-
clearInterval(interval)
|
|
202
|
-
eventHistory.reset()
|
|
203
|
-
opened = false
|
|
204
|
-
}
|
|
199
|
+
if (!opened) return;
|
|
200
|
+
logger.debug(`closing room, number of client in the room: ${clients.size}`);
|
|
201
|
+
clients.forEach((client) => client.complete());
|
|
202
|
+
clients.clear();
|
|
203
|
+
clearInterval(interval);
|
|
204
|
+
eventHistory.reset();
|
|
205
|
+
opened = false;
|
|
206
|
+
};
|
|
205
207
|
|
|
206
|
-
open()
|
|
208
|
+
open();
|
|
207
209
|
|
|
208
210
|
Object.assign(room, {
|
|
209
211
|
// main api:
|
|
@@ -221,91 +223,91 @@ export const createSSERoom = ({
|
|
|
221
223
|
// should rarely be used
|
|
222
224
|
close,
|
|
223
225
|
open,
|
|
224
|
-
})
|
|
225
|
-
return room
|
|
226
|
-
}
|
|
226
|
+
});
|
|
227
|
+
return room;
|
|
228
|
+
};
|
|
227
229
|
|
|
228
|
-
const CLEANUP_NOOP = () => {}
|
|
230
|
+
const CLEANUP_NOOP = () => {};
|
|
229
231
|
|
|
230
|
-
const requestMap = new Map()
|
|
232
|
+
const requestMap = new Map();
|
|
231
233
|
|
|
232
234
|
const connectRequestAndRoom = (request, room, roomObservable) => {
|
|
233
|
-
let sseProducer
|
|
234
|
-
let roomObservableMap
|
|
235
|
-
const requestInfo = requestMap.get(request)
|
|
235
|
+
let sseProducer;
|
|
236
|
+
let roomObservableMap;
|
|
237
|
+
const requestInfo = requestMap.get(request);
|
|
236
238
|
if (requestInfo) {
|
|
237
|
-
sseProducer = requestInfo.sseProducer
|
|
238
|
-
roomObservableMap = requestInfo.roomObservableMap
|
|
239
|
+
sseProducer = requestInfo.sseProducer;
|
|
240
|
+
roomObservableMap = requestInfo.roomObservableMap;
|
|
239
241
|
} else {
|
|
240
242
|
sseProducer = createCompositeProducer({
|
|
241
243
|
cleanup: () => {
|
|
242
|
-
requestMap.delete(request)
|
|
244
|
+
requestMap.delete(request);
|
|
243
245
|
},
|
|
244
|
-
})
|
|
245
|
-
roomObservableMap = new Map()
|
|
246
|
-
requestMap.set(request, { sseProducer, roomObservableMap })
|
|
246
|
+
});
|
|
247
|
+
roomObservableMap = new Map();
|
|
248
|
+
requestMap.set(request, { sseProducer, roomObservableMap });
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
roomObservableMap.set(room, roomObservable)
|
|
250
|
-
sseProducer.addObservable(roomObservable)
|
|
251
|
+
roomObservableMap.set(room, roomObservable);
|
|
252
|
+
sseProducer.addObservable(roomObservable);
|
|
251
253
|
|
|
252
|
-
return createObservable(sseProducer)
|
|
253
|
-
}
|
|
254
|
+
return createObservable(sseProducer);
|
|
255
|
+
};
|
|
254
256
|
|
|
255
257
|
const disconnectRequestFromRoom = (request, room) => {
|
|
256
|
-
const requestInfo = requestMap.get(request)
|
|
258
|
+
const requestInfo = requestMap.get(request);
|
|
257
259
|
if (!requestInfo) {
|
|
258
|
-
return
|
|
260
|
+
return;
|
|
259
261
|
}
|
|
260
|
-
const { sseProducer, roomObservableMap } = requestInfo
|
|
261
|
-
const roomObservable = roomObservableMap.get(room)
|
|
262
|
-
roomObservableMap.delete(room)
|
|
263
|
-
sseProducer.removeObservable(roomObservable)
|
|
264
|
-
}
|
|
262
|
+
const { sseProducer, roomObservableMap } = requestInfo;
|
|
263
|
+
const roomObservable = roomObservableMap.get(room);
|
|
264
|
+
roomObservableMap.delete(room);
|
|
265
|
+
sseProducer.removeObservable(roomObservable);
|
|
266
|
+
};
|
|
265
267
|
|
|
266
268
|
// https://github.com/dmail-old/project/commit/da7d2c88fc8273850812972885d030a22f9d7448
|
|
267
269
|
// https://github.com/dmail-old/project/commit/98b3ae6748d461ac4bd9c48944a551b1128f4459
|
|
268
270
|
// https://github.com/dmail-old/http-eventsource/blob/master/lib/event-source.js
|
|
269
271
|
// http://html5doctor.com/server-sent-events/
|
|
270
272
|
const stringifySourceEvent = ({ data, type = "message", id, retry }) => {
|
|
271
|
-
let string = ""
|
|
273
|
+
let string = "";
|
|
272
274
|
|
|
273
275
|
if (id !== undefined) {
|
|
274
|
-
string += `id:${id}\n
|
|
276
|
+
string += `id:${id}\n`;
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
if (retry) {
|
|
278
|
-
string += `retry:${retry}\n
|
|
280
|
+
string += `retry:${retry}\n`;
|
|
279
281
|
}
|
|
280
282
|
|
|
281
283
|
if (type !== "message") {
|
|
282
|
-
string += `event:${type}\n
|
|
284
|
+
string += `event:${type}\n`;
|
|
283
285
|
}
|
|
284
286
|
|
|
285
|
-
string += `data:${data}\n\n
|
|
287
|
+
string += `data:${data}\n\n`;
|
|
286
288
|
|
|
287
|
-
return string
|
|
288
|
-
}
|
|
289
|
+
return string;
|
|
290
|
+
};
|
|
289
291
|
|
|
290
292
|
const createEventHistory = (limit) => {
|
|
291
|
-
const events = []
|
|
293
|
+
const events = [];
|
|
292
294
|
|
|
293
295
|
const add = (data) => {
|
|
294
|
-
events.push(data)
|
|
296
|
+
events.push(data);
|
|
295
297
|
|
|
296
298
|
if (events.length >= limit) {
|
|
297
|
-
events.shift()
|
|
299
|
+
events.shift();
|
|
298
300
|
}
|
|
299
|
-
}
|
|
301
|
+
};
|
|
300
302
|
|
|
301
303
|
const since = (id) => {
|
|
302
|
-
const index = events.findIndex((event) => String(event.id) === id)
|
|
303
|
-
return index === -1 ? [] : events.slice(index + 1)
|
|
304
|
-
}
|
|
304
|
+
const index = events.findIndex((event) => String(event.id) === id);
|
|
305
|
+
return index === -1 ? [] : events.slice(index + 1);
|
|
306
|
+
};
|
|
305
307
|
|
|
306
308
|
const reset = () => {
|
|
307
|
-
events.length = 0
|
|
308
|
-
}
|
|
309
|
+
events.length = 0;
|
|
310
|
+
};
|
|
309
311
|
|
|
310
|
-
return { add, since, reset }
|
|
311
|
-
}
|
|
312
|
+
return { add, since, reset };
|
|
313
|
+
};
|