@faasjs/http 0.0.2-beta.3 → 0.0.2-beta.302

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/lib/index.es.js CHANGED
@@ -1,6 +1,8 @@
1
- import deepMerge from '@faasjs/deep_merge';
2
- import Logger from '@faasjs/logger';
1
+ import { usePlugin } from '@faasjs/func';
2
+ import { deepMerge } from '@faasjs/deep_merge';
3
+ import { Logger } from '@faasjs/logger';
3
4
  import { randomBytes, pbkdf2Sync, createCipheriv, createHmac, createDecipheriv } from 'crypto';
5
+ import { brotliCompressSync, gzipSync, deflateSync } from 'zlib';
4
6
 
5
7
  class Session {
6
8
  constructor(cookie, config) {
@@ -30,9 +32,8 @@ class Session {
30
32
  this.changed = false;
31
33
  }
32
34
  encode(text) {
33
- if (typeof text !== 'string') {
35
+ if (typeof text !== 'string')
34
36
  text = JSON.stringify(text);
35
- }
36
37
  const iv = randomBytes(16);
37
38
  const cipher = createCipheriv(this.config.cipherName, this.secret, iv);
38
39
  const encrypted = Buffer.concat([cipher.update(text), cipher.final()]).toString('base64');
@@ -48,9 +49,8 @@ class Session {
48
49
  const hmac = createHmac(this.config.digest, this.signedSecret);
49
50
  hmac.update(signedParts[0]);
50
51
  const digest = hmac.digest('hex');
51
- if (signedParts[1] !== digest) {
52
+ if (signedParts[1] !== digest)
52
53
  throw Error('Not valid');
53
- }
54
54
  const message = Buffer.from(signedParts[0], 'base64').toString();
55
55
  const parts = message.split('--').map(function (part) {
56
56
  return Buffer.from(part, 'base64');
@@ -58,26 +58,23 @@ class Session {
58
58
  const cipher = createDecipheriv(this.config.cipherName, this.secret, parts[1]);
59
59
  const part = Buffer.from(cipher.update(parts[0])).toString('utf8');
60
60
  const final = cipher.final('utf8');
61
- const decryptor = [part, final].join('');
62
- return JSON.parse(decryptor);
61
+ const decrypt = [part, final].join('');
62
+ return JSON.parse(decrypt);
63
63
  }
64
64
  read(key) {
65
65
  return this.content[key];
66
66
  }
67
67
  write(key, value) {
68
- if (value === null || typeof value === 'undefined') {
68
+ if (value === null || typeof value === 'undefined')
69
69
  delete this.content[key];
70
- }
71
- else {
70
+ else
72
71
  this.content[key] = value;
73
- }
74
72
  this.changed = true;
75
73
  return this;
76
74
  }
77
75
  update() {
78
- if (this.changed) {
76
+ if (this.changed)
79
77
  this.cookie.write(this.config.key, this.encode(JSON.stringify(this.content)));
80
- }
81
78
  return this;
82
79
  }
83
80
  }
@@ -98,15 +95,13 @@ class Cookie {
98
95
  invoke(cookie) {
99
96
  this.content = Object.create(null);
100
97
  // 解析 cookie
101
- if (cookie) {
102
- cookie.split(';').map((x) => {
98
+ if (cookie)
99
+ cookie.split(';').forEach(x => {
103
100
  x = x.trim();
104
- const k = x.match(/([^=]+)/);
105
- if (k !== null) {
101
+ const k = /([^=]+)/.exec(x);
102
+ if (k !== null)
106
103
  this.content[k[0]] = decodeURIComponent(x.replace(`${k[0]}=`, '').replace(/;$/, ''));
107
- }
108
104
  });
109
- }
110
105
  this.setCookie = Object.create(null);
111
106
  // 预读取 session
112
107
  this.session.invoke(this.read(this.session.config.key));
@@ -127,78 +122,60 @@ class Cookie {
127
122
  cookie = `${key}=${encodeURIComponent(value)};`;
128
123
  this.content[key] = value;
129
124
  }
130
- if (typeof opts.expires === 'number') {
125
+ if (typeof opts.expires === 'number')
131
126
  cookie += `max-age=${opts.expires};`;
132
- }
133
- else if (typeof opts.expires === 'string') {
127
+ else if (typeof opts.expires === 'string')
134
128
  cookie += `expires=${opts.expires};`;
135
- }
136
129
  cookie += `path=${opts.path || '/'};`;
137
- if (opts.domain) {
130
+ if (opts.domain)
138
131
  cookie += `domain=${opts.domain};`;
139
- }
140
- if (opts.secure) {
132
+ if (opts.secure)
141
133
  cookie += 'Secure;';
142
- }
143
- if (opts.httpOnly) {
134
+ if (opts.httpOnly)
144
135
  cookie += 'HttpOnly;';
145
- }
146
- if (opts.sameSite) {
136
+ if (opts.sameSite)
147
137
  cookie += `SameSite=${opts.sameSite};`;
148
- }
149
138
  this.setCookie[key] = cookie;
150
139
  return this;
151
140
  }
152
141
  headers() {
153
- if (!Object.keys(this.setCookie).length) {
142
+ if (Object.keys(this.setCookie).length === 0)
154
143
  return {};
155
- }
156
- else {
157
- return {
158
- 'Set-Cookie': Object.values(this.setCookie)
159
- };
160
- }
144
+ else
145
+ return { 'Set-Cookie': Object.values(this.setCookie) };
161
146
  }
162
147
  }
163
148
 
164
- class ValidError extends Error {
165
- constructor({ statusCode, headers, message }) {
166
- super(message);
167
- this.statusCode = statusCode || 500;
168
- this.headers = headers || Object.create(null);
169
- }
170
- }
171
149
  class Validator {
172
- constructor(config) {
150
+ constructor(config, logger) {
173
151
  this.paramsConfig = config.params;
174
152
  this.cookieConfig = config.cookie;
175
153
  this.sessionConfig = config.session;
176
- this.logger = new Logger('Http.Validator');
154
+ this.before = config.before;
155
+ this.logger = logger;
177
156
  }
178
- valid({ params, cookie, session }) {
179
- this.request = {
180
- params,
181
- cookie,
182
- session
183
- };
184
- this.logger.debug('Begin');
185
- if (this.paramsConfig) {
186
- this.logger.debug('Valid params');
187
- this.validContent('params', params, '', this.paramsConfig);
188
- }
189
- if (this.cookieConfig) {
190
- this.logger.debug('Valid cookie');
191
- if (!cookie) {
157
+ async valid(request) {
158
+ if (this.before) {
159
+ const result = await this.before(request);
160
+ if (result)
161
+ throw new HttpError(result);
162
+ }
163
+ this.request = request;
164
+ if (this.paramsConfig && request.params) {
165
+ this.logger.debug('Valid Params');
166
+ this.validContent('params', request.params, '', this.paramsConfig);
167
+ }
168
+ if (this.cookieConfig && request.cookie) {
169
+ this.logger.debug('Valid Cookie');
170
+ if (request.cookie == null)
192
171
  throw Error('Not found Cookie');
193
- }
194
- this.validContent('cookie', cookie.content, '', this.cookieConfig);
172
+ this.validContent('cookie', request.cookie.content, '', this.cookieConfig);
195
173
  }
196
- if (this.sessionConfig) {
174
+ if (this.sessionConfig && request.session) {
197
175
  this.logger.debug('Valid Session');
198
- if (!session) {
176
+ if (request.session == null)
199
177
  throw Error('Not found Session');
200
- }
201
- this.validContent('session', session.content, '', this.sessionConfig);
178
+ this.validContent('session', request.session.content, '', this.sessionConfig);
202
179
  }
203
180
  }
204
181
  validContent(type, params, baseKey, config) {
@@ -206,57 +183,50 @@ class Validator {
206
183
  const paramsKeys = Object.keys(params);
207
184
  const rulesKeys = Object.keys(config.rules);
208
185
  const diff = paramsKeys.filter(k => !rulesKeys.includes(k));
209
- if (diff.length) {
186
+ if (diff.length > 0)
210
187
  if (config.whitelist === 'error') {
211
188
  const diffKeys = diff.map(k => `${baseKey}${k}`);
212
- const error = Error(`[${type}] Unpermitted keys: ${diffKeys.join(', ')}`);
189
+ const error = Error(`[${type}] Not permitted keys: ${diffKeys.join(', ')}`);
213
190
  if (config.onError) {
214
191
  const res = config.onError(`${type}.whitelist`, baseKey, diffKeys);
215
- if (res) {
216
- throw new ValidError(res);
217
- }
192
+ if (res)
193
+ throw new HttpError(res);
218
194
  }
219
195
  throw error;
220
196
  }
221
- else if (config.whitelist === 'ignore') {
222
- for (const key of diff) {
197
+ else if (config.whitelist === 'ignore')
198
+ for (const key of diff)
223
199
  delete params[key];
224
- }
225
- }
226
- }
227
200
  }
228
201
  for (const key in config.rules) {
229
202
  const rule = config.rules[key];
203
+ if (!rule)
204
+ continue;
230
205
  let value = params[key];
231
206
  // default
232
- if (rule.default) {
233
- if (type === 'cookie' || type === 'session') {
207
+ if (rule.default)
208
+ if (type === 'cookie' || type === 'session')
234
209
  this.logger.warn('Cookie and Session not support default rule.');
235
- }
236
210
  else if (typeof value === 'undefined' && rule.default) {
237
211
  value = typeof rule.default === 'function' ? rule.default(this.request) : rule.default;
238
212
  params[key] = value;
239
213
  }
240
- }
241
214
  // required
242
- if (rule.required === true) {
215
+ if (rule.required)
243
216
  if (typeof value === 'undefined' || value === null) {
244
217
  const error = Error(`[${type}] ${baseKey}${key} is required.`);
245
218
  if (config.onError) {
246
219
  const res = config.onError(`${type}.rule.required`, `${baseKey}${key}`, value);
247
- if (res) {
248
- throw new ValidError(res);
249
- }
220
+ if (res)
221
+ throw new HttpError(res);
250
222
  }
251
223
  throw error;
252
224
  }
253
- }
254
225
  if (typeof value !== 'undefined' && value !== null) {
255
226
  // type
256
- if (rule.type) {
257
- if (type === 'cookie') {
227
+ if (rule.type)
228
+ if (type === 'cookie')
258
229
  this.logger.warn('Cookie not support type rule');
259
- }
260
230
  else {
261
231
  let typed = true;
262
232
  switch (rule.type) {
@@ -274,43 +244,33 @@ class Validator {
274
244
  const error = Error(`[${type}] ${baseKey}${key} must be a ${rule.type}.`);
275
245
  if (config.onError) {
276
246
  const res = config.onError(`${type}.rule.type`, `${baseKey}${key}`, value);
277
- if (res) {
278
- throw new ValidError(res);
279
- }
247
+ if (res)
248
+ throw new HttpError(res);
280
249
  }
281
250
  throw error;
282
251
  }
283
252
  }
284
- }
285
253
  // in
286
- if (rule.in && !rule.in.includes(value)) {
254
+ if ((rule.in) && !rule.in.includes(value)) {
287
255
  const error = Error(`[${type}] ${baseKey}${key} must be in ${rule.in.join(', ')}.`);
288
256
  if (config.onError) {
289
257
  const res = config.onError(`${type}.rule.in`, `${baseKey}${key}`, value);
290
- if (res) {
291
- throw new ValidError(res);
292
- }
258
+ if (res)
259
+ throw new HttpError(res);
293
260
  }
294
261
  throw error;
295
262
  }
296
263
  // nest config
297
- if (rule.config) {
298
- if (type === 'cookie') {
264
+ if (rule.config)
265
+ if (type === 'cookie')
299
266
  this.logger.warn('Cookie not support nest rule.');
300
- }
301
- else {
302
- if (Array.isArray(value)) {
303
- // array
304
- for (const val of value) {
305
- this.validContent(type, val, (baseKey ? `${baseKey}.${key}.` : `${key}.`), rule.config);
306
- }
307
- }
308
- else if (typeof value === 'object') {
309
- // object
310
- this.validContent(type, value, (baseKey ? `${baseKey}.${key}.` : `${key}.`), rule.config);
311
- }
312
- }
313
- }
267
+ else if (Array.isArray(value))
268
+ // array
269
+ for (const val of value)
270
+ this.validContent(type, val, (baseKey ? `${baseKey}.${key}.` : `${key}.`), rule.config);
271
+ else if (typeof value === 'object')
272
+ // object
273
+ this.validContent(type, value, (baseKey ? `${baseKey}.${key}.` : `${key}.`), rule.config);
314
274
  }
315
275
  }
316
276
  }
@@ -326,12 +286,26 @@ const ContentType = {
326
286
  json: 'application/json',
327
287
  jsonp: 'application/javascript'
328
288
  };
289
+ class HttpError extends Error {
290
+ constructor({ statusCode, message }) {
291
+ super(message);
292
+ if (Error.captureStackTrace)
293
+ Error.captureStackTrace(this, HttpError);
294
+ this.statusCode = statusCode || 500;
295
+ this.message = message;
296
+ }
297
+ }
298
+ const Name = 'http';
329
299
  class Http {
330
300
  /**
331
301
  * 创建 Http 插件实例
332
302
  * @param config {object} 配置项
333
303
  * @param config.name {string} 配置名
334
304
  * @param config.config {object} 网关配置
305
+ * @param config.config.method {string} 请求方法,默认为 POST
306
+ * @param config.config.path {string} 请求路径,默认为云函数文件路径
307
+ * @param config.config.ignorePathPrefix {string} 排除的路径前缀,当设置 path 时无效
308
+ * @param config.config.cookie {object} Cookie 配置
335
309
  * @param config.validator {object} 入参校验配置
336
310
  * @param config.validator.params {object} params 校验配置
337
311
  * @param config.validator.params.whitelist {string} 白名单配置
@@ -345,56 +319,66 @@ class Http {
345
319
  * @param config.validator.session.whitelist {string} 白名单配置
346
320
  * @param config.validator.session.onError {function} 自定义报错
347
321
  * @param config.validator.session.rules {object} 参数校验规则
322
+ * @param config.validator.before {function} 参数校验前自定义函数
348
323
  */
349
- constructor(config = Object.create(null)) {
350
- this.logger = new Logger('Http');
351
- this.type = 'http';
352
- this.name = config.name;
353
- this.config = config.config || Object.create(null);
354
- if (config.validator) {
324
+ constructor(config) {
325
+ this.type = Name;
326
+ this.name = Name;
327
+ this.name = (config === null || config === void 0 ? void 0 : config.name) || this.type;
328
+ this.config = ((config === null || config === void 0 ? void 0 : config.config)) || Object.create(null);
329
+ if ((config === null || config === void 0 ? void 0 : config.validator))
355
330
  this.validatorOptions = config.validator;
356
- }
331
+ this.logger = new Logger(this.name);
357
332
  this.headers = Object.create(null);
358
333
  this.cookie = new Cookie(this.config.cookie || {});
359
334
  this.session = this.cookie.session;
360
335
  }
361
336
  async onDeploy(data, next) {
337
+ var _a;
362
338
  await next();
363
- this.logger.debug('[Http] 组装网关配置');
364
- this.logger.debug('%o', data);
365
- const config = deepMerge(data.config.plugins[this.name || this.type], { config: this.config });
339
+ this.logger.debug('组装网关配置');
340
+ this.logger.debug('%j', data);
341
+ const config = data.config.plugins ?
342
+ deepMerge(data.config.plugins[this.name || this.type], { config: this.config }) :
343
+ { config: this.config };
366
344
  // 根据文件及文件夹名生成路径
367
- config.config.path = '=/' + data.name.replace(/_/g, '/').replace(/\/index$/, '');
368
- this.logger.debug('[Http] 组装完成 %o', config);
345
+ if (!config.config.path) {
346
+ config.config.path = '/' + ((_a = data.name) === null || _a === void 0 ? void 0 : _a.replace(/_/g, '/').replace(/\/index$/, ''));
347
+ if (config.config.path === '/index')
348
+ config.config.path = '/';
349
+ if (config.config.ignorePathPrefix) {
350
+ config.config.path = config.config.path.replace(new RegExp('^' + config.config.ignorePathPrefix), '');
351
+ if (config.config.path === '')
352
+ config.config.path = '/';
353
+ }
354
+ }
355
+ this.logger.debug('组装完成 %j', config);
369
356
  // 引用服务商部署插件
370
- // eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-var-requires
371
- const Provider = require(config.provider.type);
357
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
358
+ const Provider = require(config.provider.type).Provider;
372
359
  const provider = new Provider(config.provider.config);
373
360
  // 部署网关
374
361
  await provider.deploy(this.type, data, config);
375
362
  }
376
363
  async onMount(data, next) {
377
364
  this.logger.debug('[onMount] merge config');
378
- if (data.config.plugins[this.name || this.type]) {
365
+ if (data.config.plugins && data.config.plugins[this.name || this.type])
379
366
  this.config = deepMerge(this.config, data.config.plugins[this.name || this.type].config);
380
- }
381
367
  this.logger.debug('[onMount] prepare cookie & session');
382
368
  this.cookie = new Cookie(this.config.cookie || {});
383
369
  this.session = this.cookie.session;
384
370
  if (this.validatorOptions) {
385
371
  this.logger.debug('[onMount] prepare validator');
386
- this.validator = new Validator(this.validatorOptions);
372
+ this.validator = new Validator(this.validatorOptions, this.logger);
387
373
  }
388
374
  await next();
389
375
  }
390
376
  async onInvoke(data, next) {
391
- this.logger.debug('[onInvoke] Parse & valid');
392
- this.logger.time('http');
377
+ var _a, _b;
393
378
  this.headers = data.event.headers || Object.create(null);
379
+ this.body = data.event.body;
394
380
  this.params = Object.create(null);
395
- this.response = {
396
- headers: Object.create(null)
397
- };
381
+ this.response = { headers: Object.create(null) };
398
382
  if (data.event.body) {
399
383
  if (data.event.headers && data.event.headers['content-type'] && data.event.headers['content-type'].includes('application/json')) {
400
384
  this.logger.debug('[onInvoke] Parse params from json body');
@@ -404,19 +388,23 @@ class Http {
404
388
  this.logger.debug('[onInvoke] Parse params from raw body');
405
389
  this.params = data.event.body;
406
390
  }
391
+ this.logger.debug('[onInvoke] Params: %j', this.params);
407
392
  }
408
393
  else if (data.event.queryString) {
409
394
  this.logger.debug('[onInvoke] Parse params from queryString');
410
395
  this.params = data.event.queryString;
396
+ this.logger.debug('[onInvoke] Params: %j', this.params);
397
+ }
398
+ this.cookie.invoke(this.headers.cookie);
399
+ if (this.headers.cookie) {
400
+ this.logger.debug('[onInvoke] Cookie: %j', this.cookie.content);
401
+ this.logger.debug('[onInvoke] Session: %j', this.session.content);
411
402
  }
412
- this.logger.debug('[onInvoke] Parse cookie');
413
- this.cookie.invoke(this.headers['cookie']);
414
- this.logger.debug('[onInvoke] Cookie: %o', this.cookie.content);
415
- this.logger.debug('[onInvoke] Session: %o', this.session.content);
416
403
  if (this.validator) {
417
404
  this.logger.debug('[onInvoke] Valid request');
418
405
  try {
419
- this.validator.valid({
406
+ await this.validator.valid({
407
+ headers: this.headers,
420
408
  params: this.params,
421
409
  cookie: this.cookie,
422
410
  session: this.session
@@ -426,53 +414,85 @@ class Http {
426
414
  this.logger.error(error);
427
415
  data.response = {
428
416
  statusCode: error.statusCode || 500,
429
- headers: Object.assign({
430
- 'Content-Type': 'application/json; charset=utf-8',
431
- 'X-Request-Id': data.context.request_id
432
- }, error.headers || {}),
433
- body: JSON.stringify({
434
- error: {
435
- message: error.message
436
- }
437
- })
417
+ headers: { 'Content-Type': 'application/json; charset=utf-8' },
418
+ body: JSON.stringify({ error: { message: error.message } })
438
419
  };
439
420
  return;
440
421
  }
441
422
  }
442
- this.logger.timeEnd('http', '[onInvoke] Parse & valid done');
443
- await next();
444
- this.logger.debug('[onInvoke] Generate response');
445
- this.logger.time('http');
446
- // update seesion
423
+ try {
424
+ await next();
425
+ }
426
+ catch (error) {
427
+ data.response = error;
428
+ }
429
+ // update session
447
430
  this.session.update();
448
431
  // 处理 body
449
- if (data.response) {
450
- if (data.response instanceof Error || (data.response.constructor && data.response.constructor.name === 'Error')) {
432
+ if (data.response)
433
+ if (data.response instanceof Error || ((_a = data.response.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'Error') {
451
434
  // 当结果是错误类型时
452
435
  this.logger.error(data.response);
453
436
  this.response.body = JSON.stringify({ error: { message: data.response.message } });
454
- this.response.statusCode = 500;
437
+ try {
438
+ this.response.statusCode = data.response.statusCode || 500;
439
+ }
440
+ catch (error) {
441
+ this.response.statusCode = 500;
442
+ }
455
443
  }
456
- else if (Object.prototype.toString.call(data.response) === '[object Object]' && data.response.statusCode && data.response.headers) {
444
+ else if (Object.prototype.toString.call(data.response) === '[object Object]' && data.response.statusCode && data.response.headers)
457
445
  // 当返回结果是响应结构体时
458
446
  this.response = data.response;
459
- }
460
- else {
447
+ else
461
448
  this.response.body = JSON.stringify({ data: data.response });
462
- }
463
- }
464
449
  // 处理 statusCode
465
- if (!this.response.statusCode) {
450
+ if (!this.response.statusCode)
466
451
  this.response.statusCode = this.response.body ? 200 : 201;
467
- }
468
452
  // 处理 headers
469
453
  this.response.headers = Object.assign({
470
454
  'Content-Type': 'application/json; charset=utf-8',
471
- 'X-Request-Id': new Date().getTime().toString()
455
+ 'Cache-Control': 'no-cache, no-store'
472
456
  }, this.cookie.headers(), this.response.headers);
473
- /* eslint-disable-next-line require-atomic-updates */
474
457
  data.response = this.response;
475
- this.logger.timeEnd('http', '[onInvoke] done');
458
+ if (process.env.FaasMode === 'local') {
459
+ this.logger.debug('[onInvoke] Response: %j', data.response);
460
+ return;
461
+ }
462
+ // 判断是否需要压缩
463
+ if (data.response.isBase64Encoded ||
464
+ typeof data.response.body !== 'string' ||
465
+ !((_b = data.response.headers['Content-Type']) === null || _b === void 0 ? void 0 : _b.includes('json')) ||
466
+ data.response.body.length < 100)
467
+ return;
468
+ const acceptEncoding = this.headers['accept-encoding'] || this.headers['Accept-Encoding'];
469
+ if (!acceptEncoding || !/(br|gzip|deflate)/.test(acceptEncoding))
470
+ return;
471
+ const originBody = data.response.body;
472
+ try {
473
+ if (acceptEncoding.includes('br')) {
474
+ data.response.headers['Content-Encoding'] = 'br';
475
+ data.response.body = brotliCompressSync(originBody).toString('base64');
476
+ }
477
+ else if (acceptEncoding.includes('gzip')) {
478
+ data.response.headers['Content-Encoding'] = 'gzip';
479
+ data.response.body = gzipSync(originBody).toString('base64');
480
+ }
481
+ else if (acceptEncoding.includes('deflate')) {
482
+ data.response.headers['Content-Encoding'] = 'deflate';
483
+ data.response.body = deflateSync(originBody).toString('base64');
484
+ }
485
+ else
486
+ throw Error('No matched compression.');
487
+ data.response.isBase64Encoded = true;
488
+ data.response.originBody = originBody;
489
+ }
490
+ catch (error) {
491
+ console.error(error);
492
+ // 若压缩失败还原
493
+ data.response.body = originBody;
494
+ delete data.response.headers['Content-Encoding'];
495
+ }
476
496
  }
477
497
  /**
478
498
  * 设置 header
@@ -489,12 +509,10 @@ class Http {
489
509
  * @param charset {string} 编码
490
510
  */
491
511
  setContentType(type, charset = 'utf-8') {
492
- if (ContentType[type]) {
512
+ if (ContentType[type])
493
513
  this.setHeader('Content-Type', `${ContentType[type]}; charset=${charset}`);
494
- }
495
- else {
514
+ else
496
515
  this.setHeader('Content-Type', `${type}; charset=${charset}`);
497
- }
498
516
  return this;
499
517
  }
500
518
  /**
@@ -513,6 +531,9 @@ class Http {
513
531
  this.response.body = body;
514
532
  return this;
515
533
  }
534
+ }
535
+ function useHttp(config) {
536
+ return usePlugin(new Http(config));
516
537
  }
517
538
 
518
- export { ContentType, Cookie, Http, Session, Validator };
539
+ export { ContentType, Cookie, Http, HttpError, Session, Validator, useHttp };