@faasjs/http 8.0.0-beta.6 → 8.0.0-beta.7

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/index.mjs CHANGED
@@ -1,444 +1,383 @@
1
- import { createBrotliCompress, createGzip, createDeflate } from 'zlib';
2
- import { deepMerge } from '@faasjs/deep_merge';
3
- import { usePlugin } from '@faasjs/func';
4
- import { randomBytes, pbkdf2Sync, createCipheriv, createHmac, createDecipheriv } from 'crypto';
1
+ import { createBrotliCompress, createDeflate, createGzip } from "node:zlib";
2
+ import { usePlugin } from "@faasjs/func";
3
+ import { deepMerge } from "@faasjs/node-utils";
4
+ import { createCipheriv, createDecipheriv, createHmac, pbkdf2Sync, randomBytes } from "node:crypto";
5
5
 
6
- // src/index.ts
6
+ //#region src/session.ts
7
7
  var Session = class {
8
- content;
9
- config;
10
- secret;
11
- signedSecret;
12
- cookie;
13
- changed;
14
- constructor(cookie, config) {
15
- this.cookie = cookie;
16
- if (!config?.secret) cookie.logger.warn("Session's secret is missing.");
17
- this.config = Object.assign(
18
- {
19
- key: "key",
20
- secret: randomBytes(128).toString("hex"),
21
- salt: "salt",
22
- signedSalt: "signedSalt",
23
- keylen: 64,
24
- iterations: 100,
25
- digest: "sha256",
26
- cipherName: "aes-256-cbc"
27
- },
28
- config
29
- );
30
- this.secret = pbkdf2Sync(
31
- this.config.secret,
32
- this.config.salt,
33
- this.config.iterations,
34
- this.config.keylen / 2,
35
- this.config.digest
36
- );
37
- this.signedSecret = pbkdf2Sync(
38
- this.config.secret,
39
- this.config.signedSalt,
40
- this.config.iterations,
41
- this.config.keylen,
42
- this.config.digest
43
- );
44
- this.content = /* @__PURE__ */ Object.create(null);
45
- }
46
- invoke(cookie, logger) {
47
- try {
48
- this.content = cookie ? this.decode(cookie) : /* @__PURE__ */ Object.create(null);
49
- } catch (error) {
50
- logger?.error(error);
51
- this.content = /* @__PURE__ */ Object.create(null);
52
- }
53
- this.changed = false;
54
- }
55
- encode(text) {
56
- if (typeof text !== "string") text = JSON.stringify(text);
57
- const iv = randomBytes(16);
58
- const cipher = createCipheriv(this.config.cipherName, this.secret, iv);
59
- const encrypted = Buffer.concat([
60
- cipher.update(text),
61
- cipher.final()
62
- ]).toString("base64");
63
- const main = Buffer.from(
64
- [encrypted, iv.toString("base64")].join("--")
65
- ).toString("base64");
66
- const hmac = createHmac(this.config.digest, this.signedSecret);
67
- hmac.update(main);
68
- const digest = hmac.digest("hex");
69
- return `${main}--${digest}`;
70
- }
71
- decode(text) {
72
- text = decodeURIComponent(text);
73
- const signedParts = text.split("--");
74
- const hmac = createHmac(this.config.digest, this.signedSecret);
75
- hmac.update(signedParts[0]);
76
- const digest = hmac.digest("hex");
77
- if (signedParts[1] !== digest) throw Error("Session Not valid");
78
- const message = Buffer.from(signedParts[0], "base64").toString();
79
- const parts = message.split("--").map((part2) => Buffer.from(part2, "base64"));
80
- const cipher = createDecipheriv(
81
- this.config.cipherName,
82
- this.secret,
83
- parts[1]
84
- );
85
- const part = Buffer.from(cipher.update(parts[0])).toString("utf8");
86
- const final = cipher.final("utf8");
87
- const decrypt = [part, final].join("");
88
- return JSON.parse(decrypt);
89
- }
90
- read(key) {
91
- return this.content[key];
92
- }
93
- write(key, value) {
94
- if (value === null || typeof value === "undefined") delete this.content[key];
95
- else this.content[key] = value;
96
- this.changed = true;
97
- return this;
98
- }
99
- update() {
100
- if (this.changed)
101
- this.cookie.write(
102
- this.config.key,
103
- this.encode(JSON.stringify(this.content))
104
- );
105
- return this;
106
- }
8
+ content;
9
+ config;
10
+ secret;
11
+ signedSecret;
12
+ cookie;
13
+ changed;
14
+ constructor(cookie, config) {
15
+ this.cookie = cookie;
16
+ if (!config?.secret) cookie.logger?.warn("Session's secret is missing.");
17
+ this.config = Object.assign({
18
+ key: "key",
19
+ secret: randomBytes(128).toString("hex"),
20
+ salt: "salt",
21
+ signedSalt: "signedSalt",
22
+ keylen: 64,
23
+ iterations: 100,
24
+ digest: "sha256",
25
+ cipherName: "aes-256-cbc"
26
+ }, config);
27
+ this.secret = pbkdf2Sync(this.config.secret, this.config.salt, this.config.iterations, this.config.keylen / 2, this.config.digest);
28
+ this.signedSecret = pbkdf2Sync(this.config.secret, this.config.signedSalt, this.config.iterations, this.config.keylen, this.config.digest);
29
+ this.content = Object.create(null);
30
+ }
31
+ invoke(cookie, logger) {
32
+ try {
33
+ this.content = cookie ? this.decode(cookie) : Object.create(null);
34
+ } catch (error) {
35
+ logger?.error(error);
36
+ this.content = Object.create(null);
37
+ }
38
+ this.changed = false;
39
+ }
40
+ encode(text) {
41
+ if (typeof text !== "string") text = JSON.stringify(text);
42
+ const iv = randomBytes(16);
43
+ const cipher = createCipheriv(this.config.cipherName, this.secret, iv);
44
+ const encrypted = Buffer.concat([cipher.update(text), cipher.final()]).toString("base64");
45
+ const main = Buffer.from([encrypted, iv.toString("base64")].join("--")).toString("base64");
46
+ const hmac = createHmac(this.config.digest, this.signedSecret);
47
+ hmac.update(main);
48
+ return `${main}--${hmac.digest("hex")}`;
49
+ }
50
+ decode(text) {
51
+ text = decodeURIComponent(text);
52
+ const signedParts = text.split("--");
53
+ const hmac = createHmac(this.config.digest, this.signedSecret);
54
+ hmac.update(signedParts[0]);
55
+ const digest = hmac.digest("hex");
56
+ if (signedParts[1] !== digest) throw Error("Session Not valid");
57
+ const parts = Buffer.from(signedParts[0], "base64").toString().split("--").map((part) => Buffer.from(part, "base64"));
58
+ const cipher = createDecipheriv(this.config.cipherName, this.secret, parts[1]);
59
+ const decrypt = [Buffer.from(cipher.update(parts[0])).toString("utf8"), cipher.final("utf8")].join("");
60
+ return JSON.parse(decrypt);
61
+ }
62
+ read(key) {
63
+ return this.content[key];
64
+ }
65
+ write(key, value) {
66
+ if (value === null || typeof value === "undefined") delete this.content[key];
67
+ else this.content[key] = value;
68
+ this.changed = true;
69
+ return this;
70
+ }
71
+ update() {
72
+ if (this.changed) this.cookie.write(this.config.key, this.encode(JSON.stringify(this.content)));
73
+ return this;
74
+ }
107
75
  };
108
76
 
109
- // src/cookie.ts
77
+ //#endregion
78
+ //#region src/cookie.ts
110
79
  var Cookie = class {
111
- session;
112
- content;
113
- config;
114
- logger;
115
- setCookie;
116
- constructor(config, logger) {
117
- this.logger = logger;
118
- this.config = deepMerge(
119
- {
120
- path: "/",
121
- expires: 31536e3,
122
- secure: true,
123
- httpOnly: true,
124
- session: {}
125
- },
126
- config
127
- );
128
- this.session = new Session(this, this.config.session);
129
- this.content = /* @__PURE__ */ Object.create(null);
130
- this.setCookie = /* @__PURE__ */ Object.create(null);
131
- }
132
- invoke(cookie, logger) {
133
- this.content = /* @__PURE__ */ Object.create(null);
134
- if (cookie)
135
- for (const x of cookie.split(";")) {
136
- const trimX = x.trim();
137
- const k = /([^=]+)/.exec(trimX);
138
- if (k !== null)
139
- this.content[k[0]] = decodeURIComponent(
140
- trimX.replace(`${k[0]}=`, "").replace(/;$/, "")
141
- );
142
- }
143
- this.setCookie = /* @__PURE__ */ Object.create(null);
144
- this.session.invoke(this.read(this.session.config.key), logger);
145
- return this;
146
- }
147
- read(key) {
148
- return this.content[key];
149
- }
150
- write(key, value, opts) {
151
- opts = Object.assign(this.config, opts || {});
152
- let cookie;
153
- if (value === null || typeof value === "undefined") {
154
- opts.expires = "Thu, 01 Jan 1970 00:00:01 GMT";
155
- cookie = `${key}=;`;
156
- delete this.content[key];
157
- } else {
158
- cookie = `${key}=${encodeURIComponent(value)};`;
159
- this.content[key] = value;
160
- }
161
- if (typeof opts.expires === "number") cookie += `max-age=${opts.expires};`;
162
- else if (typeof opts.expires === "string")
163
- cookie += `expires=${opts.expires};`;
164
- cookie += `path=${opts.path || "/"};`;
165
- if (opts.domain) cookie += `domain=${opts.domain};`;
166
- if (opts.secure) cookie += "Secure;";
167
- if (opts.httpOnly) cookie += "HttpOnly;";
168
- if (opts.sameSite) cookie += `SameSite=${opts.sameSite};`;
169
- this.setCookie[key] = cookie;
170
- return this;
171
- }
172
- headers() {
173
- if (Object.keys(this.setCookie).length === 0) return {};
174
- return { "Set-Cookie": Object.values(this.setCookie) };
175
- }
80
+ session;
81
+ content;
82
+ config;
83
+ logger;
84
+ setCookie;
85
+ constructor(config, logger) {
86
+ this.logger = logger;
87
+ this.config = deepMerge({
88
+ path: "/",
89
+ expires: 31536e3,
90
+ secure: true,
91
+ httpOnly: true,
92
+ session: {}
93
+ }, config);
94
+ this.session = new Session(this, this.config.session);
95
+ this.content = Object.create(null);
96
+ this.setCookie = Object.create(null);
97
+ }
98
+ invoke(cookie, logger) {
99
+ this.content = Object.create(null);
100
+ if (cookie) for (const x of cookie.split(";")) {
101
+ const trimX = x.trim();
102
+ const k = /([^=]+)/.exec(trimX);
103
+ if (k !== null) this.content[k[0]] = decodeURIComponent(trimX.replace(`${k[0]}=`, "").replace(/;$/, ""));
104
+ }
105
+ this.setCookie = Object.create(null);
106
+ this.session.invoke(this.read(this.session.config.key), logger);
107
+ return this;
108
+ }
109
+ read(key) {
110
+ return this.content[key];
111
+ }
112
+ write(key, value, opts) {
113
+ opts = Object.assign(this.config, opts || {});
114
+ let cookie;
115
+ if (value === null || typeof value === "undefined") {
116
+ opts.expires = "Thu, 01 Jan 1970 00:00:01 GMT";
117
+ cookie = `${key}=;`;
118
+ delete this.content[key];
119
+ } else {
120
+ cookie = `${key}=${encodeURIComponent(value)};`;
121
+ this.content[key] = value;
122
+ }
123
+ if (typeof opts.expires === "number") cookie += `max-age=${opts.expires};`;
124
+ else if (typeof opts.expires === "string") cookie += `expires=${opts.expires};`;
125
+ cookie += `path=${opts.path || "/"};`;
126
+ if (opts.domain) cookie += `domain=${opts.domain};`;
127
+ if (opts.secure) cookie += "Secure;";
128
+ if (opts.httpOnly) cookie += "HttpOnly;";
129
+ if (opts.sameSite) cookie += `SameSite=${opts.sameSite};`;
130
+ this.setCookie[key] = cookie;
131
+ return this;
132
+ }
133
+ headers() {
134
+ if (Object.keys(this.setCookie).length === 0) return {};
135
+ return { "Set-Cookie": Object.values(this.setCookie) };
136
+ }
176
137
  };
177
138
 
178
- // src/index.ts
179
- var ContentType = {
180
- plain: "text/plain",
181
- html: "text/html",
182
- xml: "application/xml",
183
- csv: "text/csv",
184
- css: "text/css",
185
- javascript: "application/javascript",
186
- json: "application/json",
187
- jsonp: "application/javascript"
139
+ //#endregion
140
+ //#region src/index.ts
141
+ /**
142
+ * FaasJS's http plugin.
143
+ *
144
+ * [![License: MIT](https://img.shields.io/npm/l/@faasjs/http.svg)](https://github.com/faasjs/faasjs/blob/main/packages/http/LICENSE)
145
+ * [![NPM Version](https://img.shields.io/npm/v/@faasjs/http.svg)](https://www.npmjs.com/package/@faasjs/http)
146
+ *
147
+ * ## Install
148
+ *
149
+ * ```sh
150
+ * npm install @faasjs/http
151
+ * ```
152
+ *
153
+ * @packageDocumentation
154
+ */
155
+ const ContentType = {
156
+ plain: "text/plain",
157
+ html: "text/html",
158
+ xml: "application/xml",
159
+ csv: "text/csv",
160
+ css: "text/css",
161
+ javascript: "application/javascript",
162
+ json: "application/json",
163
+ jsonp: "application/javascript"
188
164
  };
189
- var HttpError = class _HttpError extends Error {
190
- statusCode;
191
- message;
192
- constructor({
193
- statusCode,
194
- message
195
- }) {
196
- super(message);
197
- if (Error.captureStackTrace) Error.captureStackTrace(this, _HttpError);
198
- this.statusCode = statusCode || 500;
199
- this.message = message;
200
- }
165
+ var HttpError = class HttpError extends Error {
166
+ statusCode;
167
+ message;
168
+ constructor({ statusCode, message }) {
169
+ super(message);
170
+ if (Error.captureStackTrace) Error.captureStackTrace(this, HttpError);
171
+ this.statusCode = statusCode || 500;
172
+ this.message = message;
173
+ }
201
174
  };
202
- var Name = "http";
175
+ const Name = "http";
203
176
  function stringToStream(text) {
204
- return new ReadableStream({
205
- start(controller) {
206
- try {
207
- const encoder = new TextEncoder();
208
- controller.enqueue(encoder.encode(text));
209
- controller.close();
210
- } catch (error) {
211
- controller.error(error);
212
- }
213
- }
214
- });
177
+ return new ReadableStream({ start(controller) {
178
+ try {
179
+ const encoder = new TextEncoder();
180
+ controller.enqueue(encoder.encode(text));
181
+ controller.close();
182
+ } catch (error) {
183
+ controller.error(error);
184
+ }
185
+ } });
215
186
  }
216
187
  function deepClone(obj) {
217
- if (obj === null || typeof obj !== "object") return obj;
218
- if (Array.isArray(obj)) return JSON.parse(JSON.stringify(obj));
219
- const clone = {};
220
- for (const key in obj) {
221
- if (!Object.hasOwn(obj, key)) continue;
222
- if (typeof obj[key] === "function") {
223
- clone[key] = obj[key];
224
- continue;
225
- }
226
- clone[key] = deepClone(obj[key]);
227
- }
228
- return clone;
188
+ if (obj === null || typeof obj !== "object") return obj;
189
+ if (Array.isArray(obj)) return JSON.parse(JSON.stringify(obj));
190
+ const clone = {};
191
+ for (const key in obj) {
192
+ if (!Object.hasOwn(obj, key)) continue;
193
+ if (typeof obj[key] === "function") {
194
+ clone[key] = obj[key];
195
+ continue;
196
+ }
197
+ clone[key] = deepClone(obj[key]);
198
+ }
199
+ return clone;
229
200
  }
230
201
  function createCompressedStream(body, encoding) {
231
- const compressStream = encoding === "br" ? createBrotliCompress() : encoding === "gzip" ? createGzip() : createDeflate();
232
- return new ReadableStream({
233
- async start(controller) {
234
- try {
235
- const compressed = await new Promise((resolve, reject) => {
236
- const chunks = [];
237
- compressStream.on("data", (chunk) => chunks.push(chunk));
238
- compressStream.on("end", () => resolve(Buffer.concat(chunks)));
239
- compressStream.on("error", reject);
240
- compressStream.write(Buffer.from(body));
241
- compressStream.end();
242
- });
243
- const chunkSize = 16 * 1024;
244
- for (let i = 0; i < compressed.length; i += chunkSize) {
245
- controller.enqueue(compressed.subarray(i, i + chunkSize));
246
- }
247
- controller.close();
248
- } catch (error) {
249
- controller.error(error);
250
- }
251
- }
252
- });
202
+ const compressStream = encoding === "br" ? createBrotliCompress() : encoding === "gzip" ? createGzip() : createDeflate();
203
+ return new ReadableStream({ async start(controller) {
204
+ try {
205
+ const compressed = await new Promise((resolve, reject) => {
206
+ const chunks = [];
207
+ compressStream.on("data", (chunk) => chunks.push(chunk));
208
+ compressStream.on("end", () => resolve(Buffer.concat(chunks)));
209
+ compressStream.on("error", reject);
210
+ compressStream.write(Buffer.from(body));
211
+ compressStream.end();
212
+ });
213
+ const chunkSize = 16 * 1024;
214
+ for (let i = 0; i < compressed.length; i += chunkSize) controller.enqueue(compressed.subarray(i, i + chunkSize));
215
+ controller.close();
216
+ } catch (error) {
217
+ controller.error(error);
218
+ }
219
+ } });
253
220
  }
254
221
  var Http = class {
255
- type = "http";
256
- name = Name;
257
- headers = /* @__PURE__ */ Object.create(null);
258
- body;
259
- params = /* @__PURE__ */ Object.create(null);
260
- cookie;
261
- session;
262
- config;
263
- response;
264
- constructor(config) {
265
- this.name = config?.name || this.type;
266
- this.config = config?.config || /* @__PURE__ */ Object.create(null);
267
- }
268
- async onMount(data, next) {
269
- data.logger.debug("merge config");
270
- const prefix = `SECRET_${this.name.toUpperCase()}_`;
271
- for (let key in process.env)
272
- if (key.startsWith(prefix)) {
273
- const value = process.env[key];
274
- key = key.replace(prefix, "").toLowerCase();
275
- if (key.includes("_")) {
276
- let config = this.config;
277
- const keys = key.split("_");
278
- for (const k of keys.slice(0, keys.length - 1)) {
279
- if (!config[k]) config[k] = /* @__PURE__ */ Object.create(null);
280
- config = config[k];
281
- }
282
- config[keys[keys.length - 1]] = value;
283
- } else this.config[key] = value;
284
- }
285
- if (!data.config) throw Error(`[${this.name}] Config not found.`);
286
- if (data.config.plugins?.[this.name || this.type])
287
- this.config = deepMerge(
288
- this.config,
289
- data.config.plugins[this.name || this.type].config
290
- );
291
- data.logger.debug("prepare cookie & session");
292
- this.cookie = new Cookie(this.config.cookie || {}, data.logger);
293
- this.session = this.cookie.session;
294
- await next();
295
- }
296
- async onInvoke(data, next) {
297
- this.headers = data.event.headers || /* @__PURE__ */ Object.create(null);
298
- this.body = data.event.body;
299
- this.params = data.event.queryString || /* @__PURE__ */ Object.create(null);
300
- this.response = { headers: /* @__PURE__ */ Object.create(null) };
301
- if (data.event.body) {
302
- if (this.headers["content-type"]?.includes("application/json") && typeof data.event.body === "string" && data.event.body.length > 1) {
303
- data.logger.debug("Parse params from json body");
304
- try {
305
- this.params = Object.keys(this.params).length ? Object.assign(this.params, JSON.parse(data.event.body)) : JSON.parse(data.event.body);
306
- } catch (error) {
307
- data.logger.error(
308
- "Parse params from json body failed: %s",
309
- error.message
310
- );
311
- }
312
- } else {
313
- data.logger.debug("Parse params from raw body");
314
- this.params = data.event.body || /* @__PURE__ */ Object.create(null);
315
- }
316
- if (this.params && typeof this.params === "object" && this.params._)
317
- delete this.params._;
318
- data.event.params = deepClone(this.params);
319
- data.logger.debug("Params: %j", this.params);
320
- }
321
- data.params = data.event.params;
322
- this.cookie.invoke(this.headers.cookie, data.logger);
323
- if (this.headers.cookie) {
324
- data.logger.debug("Cookie: %j", this.cookie.content);
325
- data.logger.debug(
326
- "Session: %s %j",
327
- this.session.config.key,
328
- this.session.content
329
- );
330
- }
331
- data.cookie = this.cookie;
332
- data.session = this.session;
333
- try {
334
- await next();
335
- } catch (error) {
336
- data.response = error;
337
- }
338
- this.session.update();
339
- if (this.response.body && !data.response) {
340
- data.response = this.response.body;
341
- }
342
- if (data.response)
343
- if (data.response instanceof Error || data.response.constructor?.name === "Error") {
344
- data.logger.error(data.response);
345
- this.response.body = JSON.stringify({
346
- error: { message: data.response.message }
347
- });
348
- try {
349
- this.response.statusCode = data.response.statusCode || 500;
350
- } catch (e) {
351
- data.logger.error(e);
352
- this.response.statusCode = 500;
353
- }
354
- } else if (Object.prototype.toString.call(data.response) === "[object Object]" && data.response.statusCode && data.response.headers)
355
- this.response = data.response;
356
- else if (data.response instanceof ReadableStream)
357
- this.response.body = data.response;
358
- else
359
- this.response.body = JSON.stringify({
360
- data: data.response === void 0 ? null : data.response
361
- });
362
- if (!this.response.statusCode)
363
- this.response.statusCode = data.response ? 200 : 204;
364
- this.response.headers = Object.assign(
365
- {
366
- "content-type": this.response.body instanceof ReadableStream ? "text/plain; charset=utf-8" : "application/json; charset=utf-8",
367
- "cache-control": "no-cache, no-store",
368
- "x-faasjs-request-id": data.context.request_id
369
- },
370
- this.cookie.headers(),
371
- this.response.headers
372
- );
373
- data.response = Object.assign({}, data.response, this.response);
374
- const originBody = data.response.body;
375
- data.response.originBody = originBody;
376
- if (data.response.body instanceof ReadableStream) {
377
- data.response.isBase64Encoded = true;
378
- return;
379
- }
380
- if (originBody === void 0 && data.response.statusCode === 204) {
381
- return;
382
- }
383
- if (originBody && typeof originBody !== "string")
384
- data.response.body = JSON.stringify(originBody);
385
- else if (originBody === void 0)
386
- data.response.body = JSON.stringify({ data: null });
387
- if (!data.response.body || typeof data.response.body !== "string" || data.response.body.length < 1024) {
388
- data.response.body = stringToStream(data.response.body);
389
- return;
390
- }
391
- const acceptEncoding = this.headers["accept-encoding"] || this.headers["Accept-Encoding"];
392
- if (!acceptEncoding || !/(br|gzip|deflate)/.test(acceptEncoding)) return;
393
- try {
394
- const encoding = acceptEncoding.includes("br") ? "br" : acceptEncoding.includes("gzip") ? "gzip" : "deflate";
395
- data.response.headers["Content-Encoding"] = encoding;
396
- data.response.body = createCompressedStream(originBody, encoding);
397
- } catch (error) {
398
- data.logger.error("Compression failed: %s", error.message);
399
- data.response.body = originBody;
400
- delete data.response.headers["Content-Encoding"];
401
- }
402
- }
403
- /**
404
- * set header
405
- * @param key {string} key
406
- * @param value {string} value
407
- */
408
- setHeader(key, value) {
409
- this.response.headers[key.toLowerCase()] = value;
410
- return this;
411
- }
412
- /**
413
- * set Content-Type
414
- * @param type {string} 类型
415
- * @param charset {string} 编码
416
- */
417
- setContentType(type, charset = "utf-8") {
418
- if (ContentType[type])
419
- this.setHeader("Content-Type", `${ContentType[type]}; charset=${charset}`);
420
- else this.setHeader("Content-Type", `${type}; charset=${charset}`);
421
- return this;
422
- }
423
- /**
424
- * set status code
425
- * @param code {number} 状态码
426
- */
427
- setStatusCode(code) {
428
- this.response.statusCode = code;
429
- return this;
430
- }
431
- /**
432
- * set body
433
- * @param body {*} 内容
434
- */
435
- setBody(body) {
436
- this.response.body = body;
437
- return this;
438
- }
222
+ type = "http";
223
+ name = Name;
224
+ headers = Object.create(null);
225
+ body;
226
+ params = Object.create(null);
227
+ cookie;
228
+ session;
229
+ config;
230
+ response;
231
+ constructor(config) {
232
+ this.name = config?.name || this.type;
233
+ this.config = config?.config || Object.create(null);
234
+ this.cookie = new Cookie(this.config.cookie || {});
235
+ this.session = this.cookie.session;
236
+ this.response = { headers: Object.create(null) };
237
+ }
238
+ async onMount(data, next) {
239
+ data.logger.debug("merge config");
240
+ const prefix = `SECRET_${this.name.toUpperCase()}_`;
241
+ for (let key in process.env) if (key.startsWith(prefix)) {
242
+ const value = process.env[key];
243
+ key = key.replace(prefix, "").toLowerCase();
244
+ if (key.includes("_")) {
245
+ let config = this.config;
246
+ const keys = key.split("_");
247
+ for (const k of keys.slice(0, keys.length - 1)) {
248
+ if (!config[k]) config[k] = Object.create(null);
249
+ config = config[k];
250
+ }
251
+ config[keys[keys.length - 1]] = value;
252
+ } else this.config[key] = value;
253
+ }
254
+ if (!data.config) throw Error(`[${this.name}] Config not found.`);
255
+ if (data.config.plugins?.[this.name || this.type]) this.config = deepMerge(this.config, data.config.plugins[this.name || this.type].config);
256
+ data.logger.debug("prepare cookie & session");
257
+ this.cookie = new Cookie(this.config.cookie || {}, data.logger);
258
+ this.session = this.cookie.session;
259
+ await next();
260
+ }
261
+ async onInvoke(data, next) {
262
+ this.headers = data.event.headers || Object.create(null);
263
+ this.body = data.event.body;
264
+ this.params = data.event.queryString || Object.create(null);
265
+ this.response = { headers: Object.create(null) };
266
+ if (data.event.body) {
267
+ if (this.headers["content-type"]?.includes("application/json") && typeof data.event.body === "string" && data.event.body.length > 1) {
268
+ data.logger.debug("Parse params from json body");
269
+ try {
270
+ this.params = Object.keys(this.params).length ? Object.assign(this.params, JSON.parse(data.event.body)) : JSON.parse(data.event.body);
271
+ } catch (error) {
272
+ data.logger.error("Parse params from json body failed: %s", error.message);
273
+ }
274
+ } else {
275
+ data.logger.debug("Parse params from raw body");
276
+ this.params = data.event.body || Object.create(null);
277
+ }
278
+ if (this.params && typeof this.params === "object" && this.params._) delete this.params._;
279
+ data.event.params = deepClone(this.params);
280
+ data.logger.debug("Params: %j", this.params);
281
+ }
282
+ data.params = data.event.params;
283
+ this.cookie.invoke(this.headers.cookie, data.logger);
284
+ if (this.headers.cookie) {
285
+ data.logger.debug("Cookie: %j", this.cookie.content);
286
+ data.logger.debug("Session: %s %j", this.session.config.key, this.session.content);
287
+ }
288
+ data.cookie = this.cookie;
289
+ data.session = this.session;
290
+ try {
291
+ await next();
292
+ } catch (error) {
293
+ data.response = error;
294
+ }
295
+ this.session.update();
296
+ if (this.response.body && !data.response) data.response = this.response.body;
297
+ if (data.response) if (data.response instanceof Error || data.response.constructor?.name === "Error") {
298
+ data.logger.error(data.response);
299
+ this.response.body = JSON.stringify({ error: { message: data.response.message } });
300
+ try {
301
+ this.response.statusCode = data.response.statusCode || 500;
302
+ } catch (e) {
303
+ data.logger.error(e);
304
+ this.response.statusCode = 500;
305
+ }
306
+ } else if (Object.prototype.toString.call(data.response) === "[object Object]" && data.response.statusCode && data.response.headers) this.response = data.response;
307
+ else if (data.response instanceof ReadableStream) this.response.body = data.response;
308
+ else this.response.body = JSON.stringify({ data: data.response === void 0 ? null : data.response });
309
+ if (!this.response.statusCode) this.response.statusCode = data.response ? 200 : 204;
310
+ this.response.headers = Object.assign({
311
+ "content-type": this.response.body instanceof ReadableStream ? "text/plain; charset=utf-8" : "application/json; charset=utf-8",
312
+ "cache-control": "no-cache, no-store",
313
+ "x-faasjs-request-id": data.context.request_id
314
+ }, this.cookie.headers(), this.response.headers);
315
+ data.response = Object.assign({}, data.response, this.response);
316
+ const originBody = data.response.body;
317
+ data.response.originBody = originBody;
318
+ if (data.response.body instanceof ReadableStream) {
319
+ data.response.isBase64Encoded = true;
320
+ return;
321
+ }
322
+ if (originBody === void 0 && data.response.statusCode === 204) return;
323
+ if (originBody && typeof originBody !== "string") data.response.body = JSON.stringify(originBody);
324
+ else if (originBody === void 0) data.response.body = JSON.stringify({ data: null });
325
+ if (!data.response.body || typeof data.response.body !== "string" || data.response.body.length < 1024) {
326
+ data.response.body = stringToStream(data.response.body);
327
+ return;
328
+ }
329
+ const acceptEncoding = this.headers["accept-encoding"] || this.headers["Accept-Encoding"];
330
+ if (!acceptEncoding || !/(br|gzip|deflate)/.test(acceptEncoding)) return;
331
+ try {
332
+ const encoding = acceptEncoding.includes("br") ? "br" : acceptEncoding.includes("gzip") ? "gzip" : "deflate";
333
+ data.response.headers["Content-Encoding"] = encoding;
334
+ data.response.body = createCompressedStream(originBody, encoding);
335
+ } catch (error) {
336
+ data.logger.error("Compression failed: %s", error.message);
337
+ data.response.body = originBody;
338
+ delete data.response.headers["Content-Encoding"];
339
+ }
340
+ }
341
+ /**
342
+ * set header
343
+ * @param key {string} key
344
+ * @param value {string} value
345
+ */
346
+ setHeader(key, value) {
347
+ const headers = this.response.headers || (this.response.headers = Object.create(null));
348
+ headers[key.toLowerCase()] = value;
349
+ return this;
350
+ }
351
+ /**
352
+ * set Content-Type
353
+ * @param type {string} 类型
354
+ * @param charset {string} 编码
355
+ */
356
+ setContentType(type, charset = "utf-8") {
357
+ if (ContentType[type]) this.setHeader("Content-Type", `${ContentType[type]}; charset=${charset}`);
358
+ else this.setHeader("Content-Type", `${type}; charset=${charset}`);
359
+ return this;
360
+ }
361
+ /**
362
+ * set status code
363
+ * @param code {number} 状态码
364
+ */
365
+ setStatusCode(code) {
366
+ this.response.statusCode = code;
367
+ return this;
368
+ }
369
+ /**
370
+ * set body
371
+ * @param body {*} 内容
372
+ */
373
+ setBody(body) {
374
+ this.response.body = body;
375
+ return this;
376
+ }
439
377
  };
440
378
  function useHttp(config) {
441
- return usePlugin(new Http(config));
379
+ return usePlugin(new Http(config));
442
380
  }
443
381
 
444
- export { ContentType, Cookie, Http, HttpError, Session, useHttp };
382
+ //#endregion
383
+ export { ContentType, Cookie, Http, HttpError, Session, useHttp };