sprucehttp_sjs 2.1.0 → 2.2.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 +70 -12
- package/index.js +99 -7
- package/package.json +1 -1
- package/types.ts +156 -0
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ A module for responding to requests within SpruceHTTP in an SJS file.
|
|
|
10
10
|
- [writeData(data)](#writedatadata)
|
|
11
11
|
- [writeDataAsync(data)](#writedataasyncdata)
|
|
12
12
|
- [clearResponse()](#clearresponse)
|
|
13
|
+
- [end()](#end)
|
|
13
14
|
- [siteConfig](#siteconfig)
|
|
14
15
|
- [requestIP](#requestip)
|
|
15
16
|
- [method](#method)
|
|
@@ -23,6 +24,9 @@ A module for responding to requests within SpruceHTTP in an SJS file.
|
|
|
23
24
|
- [body](#body)
|
|
24
25
|
- [bodyStream([options])](#bodystreamoptions)
|
|
25
26
|
- [mTlsPeerCertificate](#mtlspeercertificate)
|
|
27
|
+
- [requestObject](#requestobject)
|
|
28
|
+
- [updateConfig(newConfig)](#updateconfignewconfig)
|
|
29
|
+
- [modifyRequest(newRequest)](#modifyrequestnewrequest)
|
|
26
30
|
|
|
27
31
|
## Documentation:
|
|
28
32
|
Importing the module:
|
|
@@ -38,7 +42,7 @@ Stream mode means that responses are not buffered by the webserver, and thus are
|
|
|
38
42
|
Returns a Promise that resolves when the server acknowledges the switch to stream mode.
|
|
39
43
|
|
|
40
44
|
```js
|
|
41
|
-
sjs.streamMode(); // Set the response to stream mode.
|
|
45
|
+
await sjs.streamMode(); // Set the response to stream mode.
|
|
42
46
|
```
|
|
43
47
|
|
|
44
48
|
### ``writeStatusLine(statusCode[, reasonPhrase])``:
|
|
@@ -51,12 +55,12 @@ Returns a Promise that resolves when the server acknowledges the status line.
|
|
|
51
55
|
|
|
52
56
|
To send a 200 "OK" response, without needing to specify the reason phrase.
|
|
53
57
|
```js
|
|
54
|
-
sjs.writeStatusLine(200);
|
|
58
|
+
await sjs.writeStatusLine(200);
|
|
55
59
|
```
|
|
56
60
|
|
|
57
61
|
To send a 418 "I'm a teapot" response, with a specified reason phrase.
|
|
58
62
|
```js
|
|
59
|
-
sjs.writeStatusLine(418, "I'm a teapot");
|
|
63
|
+
await sjs.writeStatusLine(418, "I'm a teapot");
|
|
60
64
|
```
|
|
61
65
|
|
|
62
66
|
### ``writeHeader(name, value)``:
|
|
@@ -69,7 +73,7 @@ Returns a Promise that resolves when the server acknowledges the header.
|
|
|
69
73
|
|
|
70
74
|
To write the "Content-Type: text/html" header.
|
|
71
75
|
```js
|
|
72
|
-
sjs.writeHeader("Content-Type", "text/html");
|
|
76
|
+
await sjs.writeHeader("Content-Type", "text/html");
|
|
73
77
|
```
|
|
74
78
|
|
|
75
79
|
### ``writeData(data)``:
|
|
@@ -117,15 +121,28 @@ Returns a Promise that resolves when the response has been cleared.
|
|
|
117
121
|
This function does not work in stream mode.
|
|
118
122
|
|
|
119
123
|
```js
|
|
120
|
-
sjs.writeStatusLine(418, "I'm a teapot");
|
|
121
|
-
sjs.writeHeader("Content-Type", "text/html");
|
|
122
|
-
sjs.writeData("<h1>I'm a teapot</h1>");
|
|
124
|
+
await sjs.writeStatusLine(418, "I'm a teapot");
|
|
125
|
+
await sjs.writeHeader("Content-Type", "text/html");
|
|
126
|
+
await sjs.writeData("<h1>I'm a teapot</h1>");
|
|
123
127
|
// Be serious
|
|
124
|
-
sjs.clearResponse();
|
|
128
|
+
await sjs.clearResponse();
|
|
125
129
|
|
|
126
|
-
sjs.writeStatusLine(200);
|
|
127
|
-
sjs.writeHeader("Content-Type", "text/html");
|
|
128
|
-
sjs.writeData("<h1>I'm <i>not</i> a teapot</h1>");
|
|
130
|
+
await sjs.writeStatusLine(200);
|
|
131
|
+
await sjs.writeHeader("Content-Type", "text/html");
|
|
132
|
+
await sjs.writeData("<h1>I'm <i>not</i> a teapot</h1>");
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### ``end()``:
|
|
136
|
+
Ends the response. Responses are automatically ended when the SJS script exits, but this function allows you to end the response early. This is useful if you want to stop sending data before the script has finished executing.
|
|
137
|
+
|
|
138
|
+
Returns a Promise that resolves when the response has been ended.
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
await sjs.writeStatusLine(200);
|
|
142
|
+
await sjs.writeHeader("Content-Type", "text/html");
|
|
143
|
+
await sjs.writeData("<h1>Hello, world!</h1>");
|
|
144
|
+
await sjs.end(); // End the response early
|
|
145
|
+
// Any further writes will be ignored
|
|
129
146
|
```
|
|
130
147
|
|
|
131
148
|
### ``siteConfig``:
|
|
@@ -160,7 +177,7 @@ Returns the requestor's IP address.
|
|
|
160
177
|
|
|
161
178
|
```js
|
|
162
179
|
console.log(sjs.requestIP);
|
|
163
|
-
// ::ffff:
|
|
180
|
+
// ::ffff:198.51.100.163
|
|
164
181
|
```
|
|
165
182
|
|
|
166
183
|
### ``method``:
|
|
@@ -269,4 +286,45 @@ Returns details about the mTLS peer's certificate, if present.
|
|
|
269
286
|
```js
|
|
270
287
|
console.log(sjs.mTlsPeerCertificate.subject.CN);
|
|
271
288
|
// sprucehttp.com
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### ``requestObject``:
|
|
292
|
+
Returns the full request object.
|
|
293
|
+
|
|
294
|
+
```js
|
|
295
|
+
console.log(sjs.requestObject);
|
|
296
|
+
// {
|
|
297
|
+
// "version": "HTTP/1.1",
|
|
298
|
+
// ... rest of request object ...
|
|
299
|
+
// }
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### ``updateConfig(newConfig)``:
|
|
303
|
+
- ``newConfig:`` object: The new site configuration to set.
|
|
304
|
+
|
|
305
|
+
Updates the site-specific configuration for the request. You must provide a complete configuration object.
|
|
306
|
+
|
|
307
|
+
This is only effective during a pre-request hook.
|
|
308
|
+
|
|
309
|
+
Returns a Promise that resolves when the configuration has been updated.
|
|
310
|
+
|
|
311
|
+
```js
|
|
312
|
+
sjs.updateConfig({
|
|
313
|
+
// New configuration object
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### ``modifyRequest(newRequest)``:
|
|
318
|
+
- ``newRequest:`` object: The new request object to set.
|
|
319
|
+
|
|
320
|
+
Modifies the request object for the request. You must provide a complete request object.
|
|
321
|
+
|
|
322
|
+
This is only effective during a pre-request hook.
|
|
323
|
+
|
|
324
|
+
Returns a Promise that resolves when the request has been modified.
|
|
325
|
+
|
|
326
|
+
```js
|
|
327
|
+
sjs.modifyRequest({
|
|
328
|
+
// New request object
|
|
329
|
+
});
|
|
272
330
|
```
|
package/index.js
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
3
3
|
const fs = require("node:fs");
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Wait until the server tells us that it has processed the last sent message.
|
|
7
|
+
*
|
|
8
|
+
* @returns {Promise<void>} A Promise that resolves when the server has processed the last message.
|
|
9
|
+
*/
|
|
10
|
+
function waitUntilProcessed() {
|
|
11
|
+
return new Promise(function (resolve) {
|
|
12
|
+
process.once("message", function (message) {
|
|
13
|
+
if (message === "ready") {
|
|
14
|
+
resolve();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
module.exports = {
|
|
6
21
|
/**
|
|
7
22
|
* Sets the response into stream mode.
|
|
@@ -13,7 +28,10 @@ module.exports = {
|
|
|
13
28
|
return new Promise(function (resolve) {
|
|
14
29
|
process.send({
|
|
15
30
|
type: "streamMode"
|
|
16
|
-
},
|
|
31
|
+
}, async () => {
|
|
32
|
+
await waitUntilProcessed();
|
|
33
|
+
resolve();
|
|
34
|
+
});
|
|
17
35
|
});
|
|
18
36
|
},
|
|
19
37
|
|
|
@@ -29,7 +47,10 @@ module.exports = {
|
|
|
29
47
|
type: "status",
|
|
30
48
|
statusCode,
|
|
31
49
|
reasonPhrase
|
|
32
|
-
},
|
|
50
|
+
}, async () => {
|
|
51
|
+
await waitUntilProcessed();
|
|
52
|
+
resolve();
|
|
53
|
+
});
|
|
33
54
|
});
|
|
34
55
|
},
|
|
35
56
|
|
|
@@ -45,7 +66,10 @@ module.exports = {
|
|
|
45
66
|
type: "header",
|
|
46
67
|
name,
|
|
47
68
|
value
|
|
48
|
-
},
|
|
69
|
+
}, async () => {
|
|
70
|
+
await waitUntilProcessed();
|
|
71
|
+
resolve();
|
|
72
|
+
});
|
|
49
73
|
});
|
|
50
74
|
},
|
|
51
75
|
|
|
@@ -87,7 +111,10 @@ module.exports = {
|
|
|
87
111
|
process.send({
|
|
88
112
|
type: "data",
|
|
89
113
|
data: chunk
|
|
90
|
-
},
|
|
114
|
+
}, async () => {
|
|
115
|
+
await waitUntilProcessed();
|
|
116
|
+
resolve2();
|
|
117
|
+
});
|
|
91
118
|
});
|
|
92
119
|
}
|
|
93
120
|
resolve();
|
|
@@ -105,14 +132,32 @@ module.exports = {
|
|
|
105
132
|
return new Promise(function (resolve) {
|
|
106
133
|
process.send({
|
|
107
134
|
type: "clear"
|
|
108
|
-
},
|
|
135
|
+
}, async () => {
|
|
136
|
+
await waitUntilProcessed();
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Ends the response. This will not terminate the SJS script; it will continue to run until completion.
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
146
|
+
end() {
|
|
147
|
+
return new Promise(function (resolve) {
|
|
148
|
+
process.send({
|
|
149
|
+
type: "end"
|
|
150
|
+
}, async () => {
|
|
151
|
+
await waitUntilProcessed();
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
109
154
|
});
|
|
110
155
|
},
|
|
111
156
|
|
|
112
157
|
/**
|
|
113
158
|
* Returns the site-specific configuration.
|
|
114
159
|
*
|
|
115
|
-
* @returns {
|
|
160
|
+
* @returns {import("./types").SiteConfig} The site-specific configuration.
|
|
116
161
|
*/
|
|
117
162
|
get siteConfig() {
|
|
118
163
|
return JSON.parse(process.env.siteConfig);
|
|
@@ -217,7 +262,7 @@ module.exports = {
|
|
|
217
262
|
/**
|
|
218
263
|
* Returns the body of the request as an fs.ReadStream.
|
|
219
264
|
*
|
|
220
|
-
* @param {BufferEncoding | ReadStreamOptions} [options] The options to pass to fs.createReadStream.
|
|
265
|
+
* @param {BufferEncoding | fs.ReadStreamOptions} [options] The options to pass to fs.createReadStream.
|
|
221
266
|
* @returns {fs.ReadStream} A ReadStream of the body of the request.
|
|
222
267
|
*/
|
|
223
268
|
bodyStream(options) {
|
|
@@ -241,5 +286,52 @@ module.exports = {
|
|
|
241
286
|
if (retVal.pubkey) retVal.pubkey = Buffer.from(retVal.pubkey, "base64");
|
|
242
287
|
}
|
|
243
288
|
return retVal;
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Returns the full request object.
|
|
293
|
+
*
|
|
294
|
+
* @returns {import("./types").HTTPRequest} The full request object.
|
|
295
|
+
*/
|
|
296
|
+
get requestObject() {
|
|
297
|
+
return JSON.parse(process.env.requestObject);
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Updates the site-specific configuration for the request. You must provide a complete configuration object.
|
|
302
|
+
*
|
|
303
|
+
* This is only effective during a pre-request hook.
|
|
304
|
+
* @param {import("./types").SiteConfig} newConfig The new site configuration to set.
|
|
305
|
+
* @return {Promise<void>} A Promise that resolves when the configuration has been updated.
|
|
306
|
+
*/
|
|
307
|
+
updateConfig(newConfig) {
|
|
308
|
+
return new Promise(function (resolve) {
|
|
309
|
+
process.send({
|
|
310
|
+
type: "siteconfigmodify",
|
|
311
|
+
siteConfig: newConfig
|
|
312
|
+
}, async () => {
|
|
313
|
+
await waitUntilProcessed();
|
|
314
|
+
resolve();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Modifies the request object. You must provide a complete request object.
|
|
321
|
+
*
|
|
322
|
+
* This is only effective during a pre-request hook.
|
|
323
|
+
* @param {import("./types").HTTPRequest} newRequest The new request object to set.
|
|
324
|
+
* @return {Promise<void>} A Promise that resolves when the request has been modified.
|
|
325
|
+
*/
|
|
326
|
+
modifyRequest(newRequest) {
|
|
327
|
+
return new Promise(function (resolve) {
|
|
328
|
+
process.send({
|
|
329
|
+
type: "requestmodify",
|
|
330
|
+
request: newRequest
|
|
331
|
+
}, async () => {
|
|
332
|
+
await waitUntilProcessed();
|
|
333
|
+
resolve();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
244
336
|
}
|
|
245
337
|
};
|
package/package.json
CHANGED
package/types.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { TlsOptions } from "node:tls";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a site configuration.
|
|
5
|
+
* @property {string} type The type of configuration.
|
|
6
|
+
* @property {string} location The path to the root of the site.
|
|
7
|
+
* @property {string[]} [indexList] The list of files to check for when serving a directory.
|
|
8
|
+
* @property {boolean} [upgradeInsecure] Require HTTPS for all requests.
|
|
9
|
+
* @property {boolean} [directoryListing] Whether to serve directory listings.
|
|
10
|
+
* @property {number} [maxBodySize] The maximum body size in bytes to accept.
|
|
11
|
+
* @property {boolean} [sjs] Whether to execute SJS files.
|
|
12
|
+
* @property {Record<string, string>} [headers] The headers to send with the response.
|
|
13
|
+
* @property {TlsOptions} [ssl] The TLS options to use.
|
|
14
|
+
* @property {boolean} [trustProxy] Whether the site trusts the proxy (X-Forwarded-For).
|
|
15
|
+
* @property {boolean} [compress] Whether to compress the response.
|
|
16
|
+
* @property {string[]} [mimesToCompress] The list of MIME types to compress.
|
|
17
|
+
* @property {boolean} [excludeFromLogging] Whether to exclude this site from access logs.
|
|
18
|
+
* @property {string[]} [preRequestHooks] An array of paths to pre-request hook scripts for this site.
|
|
19
|
+
*/
|
|
20
|
+
interface LocalSiteConfig {
|
|
21
|
+
type: "local";
|
|
22
|
+
location: string;
|
|
23
|
+
indexList: string[];
|
|
24
|
+
upgradeInsecure: boolean;
|
|
25
|
+
directoryListing: boolean;
|
|
26
|
+
maxBodySize: number;
|
|
27
|
+
sjs: boolean;
|
|
28
|
+
headers: Record<string, string>;
|
|
29
|
+
ssl: TlsOptions;
|
|
30
|
+
trustProxy: boolean;
|
|
31
|
+
compress: boolean;
|
|
32
|
+
mimesToCompress: string[];
|
|
33
|
+
excludeFromLogging: boolean;
|
|
34
|
+
preRequestHooks: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Represents a remote site configuration.
|
|
39
|
+
* @property {string} type The type of configuration.
|
|
40
|
+
* @property {string | string[]} location The URL or URLs of the target server. The request path will be appended to this. If multiple locations are provided, one will be chosen at random.
|
|
41
|
+
* @property {boolean} upgradeInsecure Require HTTPS for all requests.
|
|
42
|
+
* @property {number} maxBodySize The maximum body size in bytes to accept.
|
|
43
|
+
* @property {Record<string, string>} headers The headers to send with the response.
|
|
44
|
+
* @property {TlsOptions} ssl The TLS options to use when connecting to the target.
|
|
45
|
+
* @property {boolean} trustProxy Whether the site trusts the proxy (X-Forwarded-For).
|
|
46
|
+
* @property {boolean} preserveHost Whether to preserve the original host header (true), or rewrite it (false; default)
|
|
47
|
+
* @property {boolean} allowInsecure Whether to allow insecure TLS connections to the target (self-signed certificates, etc).
|
|
48
|
+
* @property {string} errorPagesLocation The location to find custom error pages for this site.
|
|
49
|
+
* @property {boolean} excludeFromLogging Whether to exclude this site from access logs.
|
|
50
|
+
*/
|
|
51
|
+
interface RemoteSiteConfig {
|
|
52
|
+
type: "remote";
|
|
53
|
+
location: string | string[];
|
|
54
|
+
upgradeInsecure: boolean;
|
|
55
|
+
maxBodySize: number;
|
|
56
|
+
headers: Record<string, string>;
|
|
57
|
+
ssl: TlsOptions;
|
|
58
|
+
trustProxy: boolean;
|
|
59
|
+
preserveHost: boolean;
|
|
60
|
+
allowInsecure: boolean;
|
|
61
|
+
errorPagesLocation: string;
|
|
62
|
+
excludeFromLogging: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type SiteConfig = LocalSiteConfig | RemoteSiteConfig;
|
|
66
|
+
|
|
67
|
+
enum HTTP_VERSION {
|
|
68
|
+
HTTP0_9 = "HTTP/0.9",
|
|
69
|
+
HTTP1_0 = "HTTP/1.0",
|
|
70
|
+
HTTP1_1 = "HTTP/1.1",
|
|
71
|
+
HTTP2_0 = "HTTP/2.0",
|
|
72
|
+
HTTP3_0 = "HTTP/3.0"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
enum HTTP_METHOD {
|
|
76
|
+
GET = "GET",
|
|
77
|
+
HEAD = "HEAD",
|
|
78
|
+
POST = "POST",
|
|
79
|
+
PUT = "PUT",
|
|
80
|
+
DELETE = "DELETE",
|
|
81
|
+
CONNECT = "CONNECT",
|
|
82
|
+
OPTIONS = "OPTIONS",
|
|
83
|
+
TRACE = "TRACE",
|
|
84
|
+
PATCH = "PATCH",
|
|
85
|
+
OTHER = "OTHER"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type HTTPRequest = {
|
|
89
|
+
/**
|
|
90
|
+
* The HTTP version.
|
|
91
|
+
*/
|
|
92
|
+
version: HTTP_VERSION;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The HTTP method.
|
|
96
|
+
*/
|
|
97
|
+
method: HTTP_METHOD;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The path of the request.
|
|
101
|
+
*/
|
|
102
|
+
path: string;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The parameters of the request.
|
|
106
|
+
*/
|
|
107
|
+
params: Record<string, string>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The headers of the request.
|
|
111
|
+
*/
|
|
112
|
+
headers: Record<string, string>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The body of the request.
|
|
116
|
+
* This parameter is a path to a temporary file containing the body.
|
|
117
|
+
*/
|
|
118
|
+
body?: string;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The session ID.
|
|
122
|
+
*/
|
|
123
|
+
session?: string;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The remote family (IPv4 or IPv6).
|
|
127
|
+
*/
|
|
128
|
+
remoteFamily: string;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The remote address.
|
|
132
|
+
*/
|
|
133
|
+
remoteAddress: string;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The remote port.
|
|
137
|
+
*/
|
|
138
|
+
remotePort: number;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The protocol used (http or https).
|
|
142
|
+
*/
|
|
143
|
+
protocol: "http" | "https";
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether the connection should be kept alive.
|
|
147
|
+
*/
|
|
148
|
+
keepAlive: boolean;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Request ID for logging and tracing.
|
|
152
|
+
*/
|
|
153
|
+
requestId: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export { SiteConfig, HTTPRequest };
|