@bjoernboss/mws 1.0.0 → 1.1.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/dist/client.d.ts CHANGED
@@ -2,18 +2,19 @@ import * as libLog from "./log.js";
2
2
  import * as libBuilder from "./builder.js";
3
3
  import * as libCache from "./cache.js";
4
4
  import * as libBase from "./base.js";
5
+ import * as libServer from "./server.js";
5
6
  import * as libStream from "stream";
6
7
  import * as libUrl from "url";
7
8
  import * as libWs from "ws";
8
9
  import * as libHttp from "http";
9
10
  declare class ClientContext {
10
- dropLogTag: () => void;
11
11
  path: string;
12
+ identity: string;
12
13
  translationCount: number;
13
14
  busyCount: number;
14
15
  headerPatchCount: number;
15
16
  htmlPatchCount: number;
16
- constructor(path: string, translationCount: number, busyCount: number, headerPatchCount: number, htmlPatchCount: number);
17
+ constructor(path: string, identity: string, translationCount: number, busyCount: number, headerPatchCount: number, htmlPatchCount: number);
17
18
  }
18
19
  declare class ClientBase extends libLog.Logger {
19
20
  private _config;
@@ -21,15 +22,62 @@ declare class ClientBase extends libLog.Logger {
21
22
  protected _translation: Record<string, string | null>[];
22
23
  protected constructor(url: libUrl.URL, kind: string, config: BurntClientConfig);
23
24
  protected constructor(client: ClientBase, kind: string, config: BurntClientConfig);
25
+ /** raw request origin (no host will result in '_'; host will be lower-case) */
24
26
  readonly url: libUrl.URL;
27
+ /** path relative to current module */
25
28
  get path(): string;
29
+ /** configuration used by this client */
26
30
  get config(): BurntClientConfig;
31
+ /** check if the path relative to the current module is a sub path or the same of the given test base path (can be /base or /base/...) */
27
32
  isSubPathOf(base: string): boolean;
33
+ /** check if the path relative to the current module is inside of the given test base path (must be truly inside; /base/...) */
28
34
  isInsideOf(base: string): boolean;
35
+ /** create a path relative from the current module into the clients traversed server space */
29
36
  makePath(path: string): string;
30
37
  }
38
+ type ClientSocketEvents = {
39
+ 'data': (data: Buffer) => void;
40
+ 'close': () => void;
41
+ };
42
+ /** look at the state and modify the headers accordingly (only add or remove headers, must not try to alter the response) */
31
43
  export type HeaderPatch = (status: libBase.StatusType, headers: Record<string, string>) => void;
44
+ /** look at the page and modify it or the headers accordingly (can be interrupted by returning an alternate response) */
32
45
  export type HtmlPatch = (page: libBuilder.HtmlPage, status: libBase.StatusType, headers: Record<string, string>) => Promise<void>;
46
+ /** type to invoke to update the logging tag (empty string will hide the tag entry;
47
+ * null will completely remove the tag; other values will update the tag) */
48
+ export type SocketLogTag = (value?: string) => void;
49
+ /**
50
+ * Does not throw any exceptions, unless explicitly stated.
51
+ * Http HEAD aware (will silently drain any data sent from a HEAD request).
52
+ *
53
+ * Request is considered acknowledged, as soon as a response has been triggered or a preparation started.
54
+ * Path remains URI encoded, as it was received, and path building will use the same encoded paths.
55
+ * Repeated request responding may override any ongoing responses and may terminate the connection; depending on the prior state.
56
+ * Not responded to requests will result in [not-found].
57
+ *
58
+ * Receiving data: Will automatically decode the stream and ensure a given maximum is not passed
59
+ * => Any errors while receiving will either auto-respond or send the connection into the broken state, and fail the receive reader (stream user does not need to respond).
60
+ * => Will terminate a connection, if the upload is not consumed or the client errors.
61
+ * => Premature destroying of receive reader will result in the connection being gracefully terminated.
62
+ * => All data must have been received before the response is completed.
63
+ * Responding data: Will automatically encode the stream and send the header accordingly
64
+ * => Will automatically determine if encoding is to be used
65
+ * => Checks if promised number of bytes is provided
66
+ * => Will automatically error, if the broken state is detected, and will auto-respond or send the connection into the broken state (stream user does not need to respond).
67
+ *
68
+ * A response sent while another is being prepared (acknowledged) will override it and close the connection.
69
+ * A response sent while data is already being streamed (header sent) will break the connection.
70
+ * Normal responses automatically add ClientConfig.responseCacheControl, if no other cache control is specified.
71
+ * File responses will automatically add ClientConfig.fileCacheControl/ClientConfig.immutableCacheControl, if no other cache control is specified.
72
+ * Responses will either use the dedicated responder interface and its highWaterMark, or a responder interface, which caches up to socket.highWaterMark.
73
+ *
74
+ * Upgrade requests, which were not accepted, will be closed after responding.
75
+ * An accept attempt must be fully awaited before completing the handling procedure.
76
+ *
77
+ * Defaults [Accept-Ranges] normally to 'none' or to 'bytes' for files
78
+ * Defaults [Vary] to 'Accept-Encoding'.
79
+ * Defaults [Connection] to 'close' for upgrade requests and for some error responses.
80
+ */
33
81
  export declare class ClientRequest extends ClientBase {
34
82
  private _headerPatcher;
35
83
  private _htmlPatcher;
@@ -37,7 +85,7 @@ export declare class ClientRequest extends ClientBase {
37
85
  private _throughput;
38
86
  private _native;
39
87
  private _request;
40
- private _cache;
88
+ private _server;
41
89
  private constructor();
42
90
  private constructQuickResponse;
43
91
  private failThroughput;
@@ -52,109 +100,151 @@ export declare class ClientRequest extends ClientBase {
52
100
  private sendClientWrite;
53
101
  private sendClientData;
54
102
  private receiveClientData;
55
- _pushTranslation(map: Record<string, string | null>, identity: string): ClientContext | null;
103
+ _pushTranslation(map: Record<string, string | null> | null, identity: string): ClientContext | null;
56
104
  _restoreSnapshot(snapshot: ClientContext): void;
57
- static fromRequest(protocol: string, request: libHttp.IncomingMessage, response: libHttp.ServerResponse, options?: {
58
- config?: BurntClientConfig | ClientConfig;
59
- cache?: libCache.CacheHost | libCache.CacheConfig | libCache.BurntCacheConfig;
60
- }): ClientRequest;
61
- static fromUpgrade(protocol: string, request: libHttp.IncomingMessage, socket: libStream.Duplex, head: Buffer, options?: {
62
- config?: BurntClientConfig | ClientConfig;
63
- cache?: libCache.CacheHost | libCache.CacheConfig | libCache.BurntCacheConfig;
64
- wss?: libWs.WebSocketServer;
65
- }): ClientRequest;
66
- finalizeConnection(): Promise<void>;
105
+ static _fromRequest(protocol: string, request: libHttp.IncomingMessage, response: libHttp.ServerResponse, config: BurntClientConfig, server: libServer.Server): ClientRequest;
106
+ static _fromUpgrade(protocol: string, request: libHttp.IncomingMessage, socket: libStream.Duplex, head: Buffer, config: BurntClientConfig, server: libServer.Server, wss: libWs.WebSocketServer): ClientRequest;
107
+ _finalizeConnection(): Promise<void>;
108
+ /** respond with an internal error and kill the connection */
67
109
  killConnection(reason: string): void;
110
+ /** server the client originates from */
111
+ get server(): libServer.Server;
112
+ /** cache host used by this server and client */
68
113
  get cache(): libCache.CacheHost;
114
+ /** request has not yet been acknowledged in any way */
69
115
  get unhandled(): boolean;
116
+ /** request has been acknowledged or already processed */
70
117
  get claimed(): boolean;
118
+ /** resolves whenever the response has been determined (is broken or a response header has been sent) */
71
119
  get responded(): Promise<void>;
120
+ /** resolves whenever the request has been fully processed */
72
121
  get completed(): Promise<void>;
122
+ /** http request headers */
73
123
  get headers(): libHttp.IncomingHttpHeaders;
124
+ /** http request method */
74
125
  get method(): string;
126
+ /** was the http request a head request */
75
127
  get isHead(): boolean;
128
+ /** return the string formatted media-type (or empty string for no media type) */
76
129
  getMediaType(): string;
130
+ /** check the content-type for a media-type and otherwise return the default type */
77
131
  getMediaTypeCharset(defEncoding: string): string;
132
+ /** ensure the media-type is one of the list and otherwise return null and auto-respond with [unsupported-media-type] (defaults to first type, if [noneIsFirst]) */
78
133
  requireMediaType(types: libBase.MediaType[] | libBase.MediaType, options?: {
79
134
  noneIsFirst?: boolean;
80
135
  headers?: Record<string, string>;
81
136
  }): libBase.MediaType | null;
137
+ /** ensure the method is one of the list and otherwise return null and auto-respond with [method-not-allowed]
138
+ * if [headExplicit] is false, method will substitute HEAD for GET, framework will consume the remaining body */
82
139
  requireMethod(methods: string[] | string, options?: {
83
140
  headExplicit?: boolean;
84
141
  headers?: Record<string, string>;
85
142
  }): string | null;
143
+ /** register a callback to check if the request is still being processed (delays throughput
144
+ * termintion and resets connection timeout; will only be considered within this handler context) */
86
145
  busyCheck(cb: () => boolean): void;
146
+ /** register a callback to be invoked once the response is sent, to adjust the
147
+ * headers to be sent (will only be considered within this handler context) */
87
148
  patchHeaders(cb: HeaderPatch): void;
149
+ /** register a callback to be invoked if html is built, to adjust the headers or
150
+ * the content to be sent (will only be considered within this handler context) */
88
151
  patchHtmlPage(cb: HtmlPatch): void;
152
+ /** respond with [internal-error] and a default text response (always considered an error; reason is logged server-side only) */
89
153
  respondInternalError(reason: string, options?: {
90
154
  headers?: Record<string, string>;
91
155
  }): void;
156
+ /** respond with [forbidden] and a default text response (reason is logged server-side only) */
92
157
  respondForbidden(reason: string, options?: {
93
158
  headers?: Record<string, string>;
94
159
  }): void;
160
+ /** respond with a any response of the given configuration (defaults to media-type: text/unknown/-, status: ok);
161
+ * if [lightResponse], the content length is suppressed for head responses (to accomodate short-circuiting responding) */
95
162
  respond(content: string | Buffer | null, options?: {
96
163
  media?: libBase.MediaType;
97
164
  status?: libBase.StatusType;
98
165
  headers?: Record<string, string>;
99
166
  lightResponse?: boolean;
100
167
  }): void;
168
+ /** respond with [ok] and either a message or a default response */
101
169
  respondOk(options?: {
102
170
  message?: string;
103
171
  headers?: Record<string, string>;
104
172
  }): void;
173
+ /** respond with [created] and either a message or a default response (ensure target is properly URI encoded) */
105
174
  respondCreated(target: string, options?: {
106
175
  headers?: Record<string, string>;
107
176
  }): void;
177
+ /** respond with [not-modified] and no body (ensure the etag and/or last-modified is set) */
108
178
  respondNotModified(options?: {
109
179
  etag?: string;
110
180
  lastModified?: string;
111
181
  headers?: Record<string, string>;
112
182
  }): void;
183
+ /** respond with [precondition-failed] and a default text response (ensure the etag and/or last-modified is set) */
113
184
  respondPreconditionFailed(reason: string, options?: {
114
185
  etag?: string;
115
186
  lastModified?: string;
116
187
  headers?: Record<string, string>;
117
188
  }): void;
189
+ /** respond with [bad-request] and a default text response */
118
190
  respondBadRequest(reason: string, options?: {
119
191
  headers?: Record<string, string>;
120
192
  }): void;
193
+ /** respond with [range-not-satisfiable] and a default text response */
121
194
  respondRangeIssue(range: string, size: number, options?: {
122
195
  headers?: Record<string, string>;
123
196
  }): void;
197
+ /** respond with [conflict] and a default text response */
124
198
  respondConflict(conflict: string, options?: {
125
199
  headers?: Record<string, string>;
126
200
  }): void;
201
+ /** respond with [not-found] and a default text response */
127
202
  respondNotFound(options?: {
128
203
  headers?: Record<string, string>;
129
204
  }): void;
205
+ /** respond with [unsupported-media-type] and a default text response */
130
206
  respondUnsupported(used: string, allowed: string, options?: {
131
207
  headers?: Record<string, string>;
132
208
  }): void;
209
+ /** respond with [method-not-allowed] and a default text response */
133
210
  respondMethodNotAllowed(method: string, allowed: string, options?: {
134
211
  headers?: Record<string, string>;
135
212
  }): void;
213
+ /** respond with [request-timeout] and a default text response */
136
214
  respondRequestTimeout(reason: string, options?: {
137
215
  headers?: Record<string, string>;
138
216
  }): void;
217
+ /** respond with [content-too-large] and a default text response */
139
218
  respondContentTooLarge(allowed: number, atLeastProvided: number, options?: {
140
219
  headers?: Record<string, string>;
141
220
  }): void;
221
+ /** respond with [update-required] and a default text response */
142
222
  respondUpdateRequired(upgrade: string, options?: {
143
223
  headers?: Record<string, string>;
144
224
  }): void;
225
+ /** respond with [see-other] to the given target and a default text response (forces method GET; ensure target is properly URI encoded) */
145
226
  respondSeeOther(target: string, options?: {
146
227
  headers?: Record<string, string>;
147
228
  }): void;
229
+ /** respond with [temporary-redirect] to the given target and a default text response (preserves method; ensure target is properly URI encoded) */
148
230
  respondTemporaryRedirect(target: string, options?: {
149
231
  headers?: Record<string, string>;
150
232
  }): void;
233
+ /** respond with [permanent-redirect] to the given target and a default text response (preserves method; ensure target is properly URI encoded) */
151
234
  respondPermanentRedirect(target: string, options?: {
152
235
  headers?: Record<string, string>;
153
236
  }): void;
237
+ /** respond with html, can be built on by parent modules, sent once the request has been fully processed
238
+ * (default status is ok; for HEAD builds, no actual content will be constructed or estimated in size)
239
+ * automatically adds ClientConfig.responseCacheControl, if no other cache control is specified */
154
240
  respondHtml(page: libBuilder.HtmlPage, options?: {
155
241
  status?: libBase.StatusType;
156
242
  headers?: Record<string, string>;
157
243
  }): Promise<void>;
244
+ /** [no-throw but errors] send data with [media type] and [status] and return a writable stream (default: status is ok, media is unknown, dynamicEncode is true);
245
+ * if a content size is provided, stream expects exactly this amount of bytes; if [dynamicEncode], the encoder will be dynamically negotiated
246
+ * based on the content; for a HEAD request, no encoding will be negotiated, no lengths verified, and the written data will just be drained
247
+ * (can immediately be ended using '.end()'); automatically adds ClientConfig.responseCacheControl, if no other cache control is specified */
158
248
  respondData(options?: {
159
249
  status?: libBase.StatusType;
160
250
  media?: libBase.MediaType;
@@ -162,51 +252,86 @@ export declare class ClientRequest extends ClientBase {
162
252
  dynamicEncode?: boolean;
163
253
  headers?: Record<string, string>;
164
254
  }): libStream.Writable;
255
+ /** try to respond with the given file, return false, if the file does not exist (range aware, HEAD aware); specify [checkFreshness] to
256
+ * re-validate the file stats on disk before serving from cache; the media type can be overwritten (defaults to extracting media-type
257
+ * from the file-path); [encoding] describes the encoding of a pre-encoded file (warning: no checks against accepted encodings
258
+ * performed!); status will be [Ok], [partial-content], [not-modified] or according errors cache aware and etag/last-modified aware;
259
+ * automatically adds ClientConfig.fileCacheControl/ClientConfig.immutableCacheControl, if no other cache control is specified */
165
260
  tryRespondFile(filePath: string, options?: {
166
261
  encoded?: string;
167
262
  media?: libBase.MediaType;
168
263
  headers?: Record<string, string>;
169
264
  checkFreshness?: boolean;
170
265
  }): Promise<boolean>;
266
+ /** [throws] receive the payload of given max length and write it directly to a file; will fail
267
+ * if the file already exists and delete the file if it could not be received in full
268
+ * automatically responds with given exceptions if the payload cannot be received properly or file operations fail */
171
269
  receiveToFile(path: string, maxLength: number | null): Promise<void>;
270
+ /** [no-throw but errors] receive the payload of given max length as a readable stream
271
+ * automatically responds with given exceptions if the payload cannot be received properly
272
+ * automatically drained if the readable stream is destroyed before reading all data */
172
273
  receiveData(maxLength: number | null): libStream.Readable;
274
+ /** [throws] receive the payload of given max length as a single complete buffer
275
+ * automatically responds with given exceptions if the payload cannot be received properly */
173
276
  receiveAllBuffer(maxLength: number | null): Promise<Buffer>;
277
+ /** [throws] receive the payload of given max length as a single complete decoded string
278
+ * automatically responds with given exceptions if the payload cannot be received properly */
174
279
  receiveAllText(encoding: string, maxLength: number | null): Promise<string>;
280
+ /** marks the object as having been handled and returns a web socket or
281
+ * automatically responds with a corresponding error and returns null */
175
282
  acceptWebSocket(): Promise<ClientSocket | null>;
176
283
  }
284
+ /**
285
+ * WebSocket with integrated alive checks.
286
+ * Structured WebSocket, which takes care of error handling.
287
+ * The 'close' event is guaranteed to fire exactly once and no 'data' events will follow.
288
+ * Takes ownership of the socket.
289
+ */
177
290
  export declare class ClientSocket extends ClientBase {
178
291
  private _ws;
179
292
  private _alive;
180
293
  private _closing;
181
294
  private _emitter;
182
- private _extension;
295
+ private _log;
183
296
  private constructor();
184
297
  private checkIsAlive;
185
298
  private selfIsAlive;
186
299
  private handleClosing;
300
+ private updateLogIdentity;
187
301
  static _fromRequest(ws: libWs.WebSocket, source: ClientRequest): ClientSocket;
302
+ /** send data to the remote (ignored if connection is being closed) */
188
303
  send(data: string | Buffer): void;
304
+ /** close the web socket (promise resolved once the close callback has been fully invoked) */
189
305
  close(): Promise<void>;
306
+ /** tag the logging with the given identifier and return a callback to update the tag */
307
+ tagLog(identifier: string): SocketLogTag;
190
308
  on<K extends keyof ClientSocketEvents>(event: K, listener: ClientSocketEvents[K]): ClientSocket;
191
309
  off<K extends keyof ClientSocketEvents>(event: K, listener: ClientSocketEvents[K]): ClientSocket;
192
310
  once<K extends keyof ClientSocketEvents>(event: K, listener: ClientSocketEvents[K]): ClientSocket;
193
311
  private emitEventSync;
194
312
  }
195
- type ClientSocketEvents = {
196
- 'data': (data: Buffer) => void;
197
- 'close': () => void;
198
- };
199
313
  export interface ClientConfig {
314
+ /** default server name to be used in the http:server header [empty value prevents server header; Default: 'Modular Web Server'] */
200
315
  serverName?: string;
316
+ /** default header values to be added to every http response [Default: { 'X-Content-Type-Options': 'nosniff' }] */
201
317
  commonHeaders?: Record<string, string>;
318
+ /** default web-socket timeout before performing a ping to determine liveness [0 disables the timeout; in milliseconds; Default: 180_000] */
202
319
  webSocketTimeout?: number;
320
+ /** default web-socket timeout to respond to a liveness ping before closing the connection [0 kills the connection without ping test; in milliseconds; Default: 2_000] */
203
321
  webSocketAliveTimeout?: number;
322
+ /** time for a broken connection or socket to receive the response before force-closing it [0 results in immediate close; in milliseconds; Default: 1_000] */
204
323
  killGraceTimeout?: number;
324
+ /** default cache-control value for normal cache reads [empty string does not set any cache-control; Default: 'public, max-age=600, must-revalidate' (10 minutes)] */
205
325
  fileCacheControl?: string;
326
+ /** default cache-control value for immutable cache reads [empty string does not set any cache-control; Default: 'public, max-age=2592000, immutable' (30 days)] */
206
327
  immutableCacheControl?: string;
328
+ /** default cache-control value for any basic responses [empty string does not set any cache-control; Default: 'private, no-cache'] */
207
329
  responseCacheControl?: string;
330
+ /** grace period before the throughput is started to be measured or for busy connections [in milliseconds; Default: 10_000] */
208
331
  throughputGrace?: number;
332
+ /** throughput required for combined sending and receiving bodies of requests [0 disables the throughput check, in bytes/second; Default: 1_000] */
209
333
  throughputThreshold?: number;
334
+ /** length of sliding time window for which the throughput must be above the threshold [in milliseconds; Default: 30_000] */
210
335
  throughputWindow?: number;
211
336
  }
212
337
  export declare class BurntClientConfig {