@opra/core 0.33.13 → 1.0.0-alpha.1

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.
Files changed (155) hide show
  1. package/cjs/augmentation/18n.augmentation.js +17 -4
  2. package/cjs/augmentation/http-controller.augmentation.js +25 -0
  3. package/cjs/constants.js +5 -0
  4. package/cjs/execution-context.js +25 -12
  5. package/cjs/{services → helpers}/logger.js +1 -2
  6. package/cjs/{services/api-service.js → helpers/service-base.js} +8 -8
  7. package/cjs/http/express-adapter.js +164 -0
  8. package/cjs/http/http-adapter.js +27 -0
  9. package/cjs/http/http-context.js +116 -0
  10. package/cjs/http/impl/asset-cache.js +21 -0
  11. package/cjs/http/impl/http-handler.js +575 -0
  12. package/cjs/http/{http-server-request.js → impl/http-incoming.host.js} +21 -46
  13. package/cjs/http/{http-server-response.js → impl/http-outgoing.host.js} +7 -26
  14. package/cjs/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +24 -22
  15. package/cjs/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +13 -54
  16. package/cjs/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +11 -14
  17. package/cjs/http/interfaces/http-incoming.interface.js +25 -0
  18. package/cjs/http/interfaces/http-outgoing.interface.js +22 -0
  19. package/cjs/http/interfaces/node-incoming-message.interface.js +63 -0
  20. package/cjs/http/interfaces/node-outgoing-message.interface.js +15 -0
  21. package/cjs/http/utils/body-reader.js +215 -0
  22. package/cjs/http/{helpers → utils}/convert-to-raw-headers.js +1 -2
  23. package/cjs/http/{helpers → utils}/match-known-fields.js +11 -9
  24. package/cjs/http/utils/wrap-exception.js +34 -0
  25. package/cjs/index.js +25 -25
  26. package/cjs/platform-adapter.js +21 -0
  27. package/cjs/type-guards.js +23 -0
  28. package/esm/augmentation/18n.augmentation.js +20 -7
  29. package/esm/augmentation/http-controller.augmentation.js +23 -0
  30. package/esm/constants.js +2 -0
  31. package/esm/execution-context.js +25 -13
  32. package/esm/{services → helpers}/logger.js +1 -2
  33. package/esm/{services/api-service.js → helpers/service-base.js} +6 -6
  34. package/esm/http/express-adapter.js +159 -0
  35. package/esm/http/http-adapter.js +23 -0
  36. package/esm/http/http-context.js +111 -0
  37. package/esm/http/impl/asset-cache.js +17 -0
  38. package/esm/http/impl/http-handler.js +570 -0
  39. package/esm/http/{http-server-request.js → impl/http-incoming.host.js} +17 -43
  40. package/esm/http/{http-server-response.js → impl/http-outgoing.host.js} +6 -26
  41. package/esm/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +22 -20
  42. package/esm/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +11 -52
  43. package/esm/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +9 -12
  44. package/esm/http/interfaces/http-incoming.interface.js +22 -0
  45. package/esm/http/interfaces/http-outgoing.interface.js +19 -0
  46. package/esm/http/interfaces/node-incoming-message.interface.js +60 -0
  47. package/esm/http/interfaces/node-outgoing-message.interface.js +12 -0
  48. package/esm/http/utils/body-reader.js +210 -0
  49. package/esm/http/{helpers → utils}/convert-to-headers.js +1 -1
  50. package/esm/http/{helpers → utils}/convert-to-raw-headers.js +2 -3
  51. package/esm/http/{helpers → utils}/match-known-fields.js +11 -9
  52. package/esm/http/utils/wrap-exception.js +30 -0
  53. package/esm/index.js +25 -26
  54. package/esm/platform-adapter.js +19 -1
  55. package/esm/type-guards.js +16 -0
  56. package/package.json +21 -9
  57. package/types/augmentation/18n.augmentation.d.ts +32 -2
  58. package/types/augmentation/http-controller.augmentation.d.ts +21 -0
  59. package/types/constants.d.ts +2 -0
  60. package/types/execution-context.d.ts +24 -26
  61. package/types/helpers/service-base.d.ts +10 -0
  62. package/types/http/express-adapter.d.ts +13 -0
  63. package/types/http/http-adapter.d.ts +27 -0
  64. package/types/http/http-context.d.ts +44 -0
  65. package/types/http/impl/asset-cache.d.ts +5 -0
  66. package/types/http/impl/http-handler.d.ts +73 -0
  67. package/types/http/impl/http-incoming.host.d.ts +23 -0
  68. package/types/http/impl/http-outgoing.host.d.ts +17 -0
  69. package/types/http/{helpers/multipart-helper.d.ts → impl/multipart-reader.d.ts} +8 -6
  70. package/types/http/impl/{http-incoming-message.host.d.ts → node-incoming-message.host.d.ts} +9 -22
  71. package/types/http/impl/{http-outgoing-message.host.d.ts → node-outgoing-message.host.d.ts} +11 -27
  72. package/types/http/{http-server-request.d.ts → interfaces/http-incoming.interface.d.ts} +28 -17
  73. package/types/http/{http-server-response.d.ts → interfaces/http-outgoing.interface.d.ts} +17 -10
  74. package/types/http/interfaces/node-incoming-message.interface.d.ts +38 -0
  75. package/types/http/interfaces/node-outgoing-message.interface.d.ts +29 -0
  76. package/types/http/utils/body-reader.d.ts +41 -0
  77. package/types/http/utils/wrap-exception.d.ts +2 -0
  78. package/types/index.d.ts +24 -26
  79. package/types/platform-adapter.d.ts +20 -48
  80. package/types/type-guards.d.ts +8 -0
  81. package/cjs/augmentation/collection.augmentation.js +0 -2
  82. package/cjs/augmentation/container.augmentation.js +0 -2
  83. package/cjs/augmentation/resource.augmentation.js +0 -26
  84. package/cjs/augmentation/singleton.augmentation.js +0 -2
  85. package/cjs/augmentation/storage.augmentation.js +0 -2
  86. package/cjs/execution-context.host.js +0 -46
  87. package/cjs/http/adapters/express-adapter.host.js +0 -34
  88. package/cjs/http/adapters/express-adapter.js +0 -14
  89. package/cjs/http/adapters/node-http-adapter.host.js +0 -70
  90. package/cjs/http/adapters/node-http-adapter.js +0 -14
  91. package/cjs/http/helpers/json-body-loader.js +0 -29
  92. package/cjs/http/helpers/query-parsers.js +0 -16
  93. package/cjs/http/http-adapter-host.js +0 -715
  94. package/cjs/interfaces/interceptor.interface.js +0 -2
  95. package/cjs/interfaces/request-handler.interface.js +0 -2
  96. package/cjs/platform-adapter.host.js +0 -154
  97. package/cjs/request-context.js +0 -25
  98. package/cjs/request.host.js +0 -24
  99. package/cjs/request.js +0 -2
  100. package/cjs/response.host.js +0 -22
  101. package/cjs/response.js +0 -2
  102. package/esm/augmentation/collection.augmentation.js +0 -1
  103. package/esm/augmentation/container.augmentation.js +0 -1
  104. package/esm/augmentation/resource.augmentation.js +0 -24
  105. package/esm/augmentation/singleton.augmentation.js +0 -1
  106. package/esm/augmentation/storage.augmentation.js +0 -1
  107. package/esm/execution-context.host.js +0 -42
  108. package/esm/http/adapters/express-adapter.host.js +0 -30
  109. package/esm/http/adapters/express-adapter.js +0 -11
  110. package/esm/http/adapters/node-http-adapter.host.js +0 -65
  111. package/esm/http/adapters/node-http-adapter.js +0 -11
  112. package/esm/http/helpers/json-body-loader.js +0 -24
  113. package/esm/http/helpers/query-parsers.js +0 -12
  114. package/esm/http/http-adapter-host.js +0 -710
  115. package/esm/interfaces/interceptor.interface.js +0 -1
  116. package/esm/interfaces/request-handler.interface.js +0 -1
  117. package/esm/platform-adapter.host.js +0 -149
  118. package/esm/request-context.js +0 -22
  119. package/esm/request.host.js +0 -20
  120. package/esm/request.js +0 -1
  121. package/esm/response.host.js +0 -18
  122. package/esm/response.js +0 -1
  123. package/types/augmentation/collection.augmentation.d.ts +0 -146
  124. package/types/augmentation/container.augmentation.d.ts +0 -14
  125. package/types/augmentation/resource.augmentation.d.ts +0 -38
  126. package/types/augmentation/singleton.augmentation.d.ts +0 -83
  127. package/types/augmentation/storage.augmentation.d.ts +0 -50
  128. package/types/execution-context.host.d.ts +0 -25
  129. package/types/http/adapters/express-adapter.d.ts +0 -15
  130. package/types/http/adapters/express-adapter.host.d.ts +0 -12
  131. package/types/http/adapters/node-http-adapter.d.ts +0 -17
  132. package/types/http/adapters/node-http-adapter.host.d.ts +0 -19
  133. package/types/http/helpers/json-body-loader.d.ts +0 -5
  134. package/types/http/helpers/query-parsers.d.ts +0 -1
  135. package/types/http/http-adapter-host.d.ts +0 -34
  136. package/types/interfaces/interceptor.interface.d.ts +0 -2
  137. package/types/interfaces/request-handler.interface.d.ts +0 -4
  138. package/types/platform-adapter.host.d.ts +0 -43
  139. package/types/request-context.d.ts +0 -13
  140. package/types/request.d.ts +0 -14
  141. package/types/request.host.d.ts +0 -27
  142. package/types/response.d.ts +0 -22
  143. package/types/response.host.d.ts +0 -22
  144. package/types/services/api-service.d.ts +0 -10
  145. /package/cjs/http/{helpers → utils}/common.js +0 -0
  146. /package/cjs/http/{helpers → utils}/concat-readable.js +0 -0
  147. /package/cjs/http/{helpers → utils}/convert-to-headers.js +0 -0
  148. /package/esm/http/{helpers → utils}/common.js +0 -0
  149. /package/esm/http/{helpers → utils}/concat-readable.js +0 -0
  150. /package/types/{services → helpers}/logger.d.ts +0 -0
  151. /package/types/http/{helpers → utils}/common.d.ts +0 -0
  152. /package/types/http/{helpers → utils}/concat-readable.d.ts +0 -0
  153. /package/types/http/{helpers → utils}/convert-to-headers.d.ts +0 -0
  154. /package/types/http/{helpers → utils}/convert-to-raw-headers.d.ts +0 -0
  155. /package/types/http/{helpers → utils}/match-known-fields.d.ts +0 -0
@@ -11,23 +11,9 @@ import mime from 'mime-types';
11
11
  import path from 'path';
12
12
  import { toString } from 'putil-varhelpers';
13
13
  import vary from 'vary';
14
- import { HttpStatusCode, isStream, mergePrototype } from '@opra/common';
15
- import { HttpOutgoingMessageHost } from './impl/http-outgoing-message.host.js';
14
+ import { HttpStatusCode } from '@opra/common';
16
15
  const charsetRegExp = /;\s*charset\s*=/;
17
- function isHttpIncomingMessage(v) {
18
- return v && typeof v.getHeaders === 'function' && isStream(v);
19
- }
20
- export var HttpServerResponse;
21
- (function (HttpServerResponse) {
22
- function from(instance) {
23
- if (!isHttpIncomingMessage(instance))
24
- instance = new HttpOutgoingMessageHost(instance);
25
- mergePrototype(instance, HttpServerResponseHost.prototype);
26
- return instance;
27
- }
28
- HttpServerResponse.from = from;
29
- })(HttpServerResponse || (HttpServerResponse = {}));
30
- class HttpServerResponseHost {
16
+ export class HttpOutgoingHost {
31
17
  attachment(filename) {
32
18
  if (filename)
33
19
  this.contentType(path.extname(filename));
@@ -35,9 +21,7 @@ class HttpServerResponseHost {
35
21
  return this;
36
22
  }
37
23
  contentType(type) {
38
- const ct = type.indexOf('/') === -1
39
- ? mime.lookup(type)
40
- : type;
24
+ const ct = type.indexOf('/') === -1 ? mime.lookup(type) : type;
41
25
  this.setHeader('Content-Type', ct);
42
26
  return this;
43
27
  }
@@ -50,9 +34,7 @@ class HttpServerResponseHost {
50
34
  return this;
51
35
  }
52
36
  const fieldLower = field.toLowerCase();
53
- let value = Array.isArray(val)
54
- ? val.map(String)
55
- : (val ? String(val) : '');
37
+ let value = Array.isArray(val) ? val.map(String) : val ? String(val) : '';
56
38
  // add charset to content-type
57
39
  if (fieldLower === 'content-type') {
58
40
  if (Array.isArray(value)) {
@@ -71,15 +53,13 @@ class HttpServerResponseHost {
71
53
  const opts = {
72
54
  expires: new Date(1),
73
55
  path: '/',
74
- ...options
56
+ ...options,
75
57
  };
76
58
  return this.cookie(name, '', opts);
77
59
  }
78
60
  cookie(name, value, options) {
79
61
  const opts = { ...options };
80
- let val = typeof value === 'object'
81
- ? 'j:' + JSON.stringify(value)
82
- : String(value);
62
+ let val = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value);
83
63
  if (opts.signed) {
84
64
  const secret = opts.secret || this.req?.secret;
85
65
  if (!secret)
@@ -1,34 +1,38 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import formidable from 'formidable';
3
3
  import fs from 'fs/promises';
4
- export class MultipartIterator extends EventEmitter {
5
- constructor(options) {
4
+ export class MultipartReader extends EventEmitter {
5
+ constructor(incoming, options) {
6
6
  super();
7
+ this._started = false;
7
8
  this._cancelled = false;
8
9
  this._items = [];
9
10
  this._stack = [];
10
11
  this.setMaxListeners(1000);
11
- const form = this._form = formidable({
12
+ this._incoming = incoming;
13
+ const form = (this._form = formidable({
12
14
  ...options,
13
15
  filter: (part) => {
14
16
  return !this._cancelled && (!options?.filter || options.filter(part));
15
- }
16
- });
17
+ },
18
+ }));
17
19
  form.once('error', () => {
18
20
  this._cancelled = true;
19
21
  if (this.listenerCount('error') > 0)
20
22
  this.emit('error');
21
23
  });
22
- form.on('field', (field, value) => {
23
- const item = { field, value };
24
+ form.on('field', (fieldName, value) => {
25
+ const item = { fieldName, type: 'field', value };
24
26
  this._items.push(item);
25
27
  this._stack.push(item);
28
+ this.emit('field', item);
26
29
  this.emit('item', item);
27
30
  });
28
- form.on('file', (field, file) => {
29
- const item = { field, file };
31
+ form.on('file', (fieldName, file) => {
32
+ const item = { fieldName, type: 'file', file };
30
33
  this._items.push(item);
31
34
  this._stack.push(item);
35
+ this.emit('file', item);
32
36
  this.emit('item', item);
33
37
  });
34
38
  }
@@ -44,7 +48,7 @@ export class MultipartIterator extends EventEmitter {
44
48
  if (this._form.ended)
45
49
  return resolve(undefined);
46
50
  this.once('item', () => resolve(this._stack.shift()));
47
- this.once('error', (e) => reject(e));
51
+ this.once('error', e => reject(e));
48
52
  });
49
53
  }
50
54
  getAll() {
@@ -64,6 +68,8 @@ export class MultipartIterator extends EventEmitter {
64
68
  this.resume();
65
69
  }
66
70
  resume() {
71
+ if (!this._started)
72
+ this._form.parse(this._incoming, () => void 0);
67
73
  if (this._form.req)
68
74
  this._form.resume();
69
75
  }
@@ -71,10 +77,9 @@ export class MultipartIterator extends EventEmitter {
71
77
  if (this._form.req)
72
78
  this._form.pause();
73
79
  }
74
- async deleteFiles() {
80
+ async deleteTempFiles() {
75
81
  const promises = [];
76
- this._items
77
- .forEach(item => {
82
+ this._items.forEach(item => {
78
83
  if (!item.file)
79
84
  return;
80
85
  const file = item.file;
@@ -82,17 +87,14 @@ export class MultipartIterator extends EventEmitter {
82
87
  if (file._writeStream.closed)
83
88
  return resolve();
84
89
  file._writeStream.once('close', resolve);
85
- }).then(() => {
90
+ })
91
+ .then(() => {
86
92
  return fs.unlink(file.filepath);
87
- }).then(() => {
93
+ })
94
+ .then(() => {
88
95
  return 0;
89
96
  }));
90
97
  });
91
98
  return Promise.allSettled(promises);
92
99
  }
93
- static async create(incoming, options) {
94
- const out = new MultipartIterator(options);
95
- await out._form.parse(incoming);
96
- return out;
97
- }
98
100
  }
@@ -1,23 +1,18 @@
1
- /*
2
- This file contains code blocks from open source NodeJs project
3
- https://github.com/nodejs/
4
- */
5
1
  import { Duplex, Readable } from 'stream';
6
- import { HTTPParser } from '@browsery/http-parser';
7
2
  import { isAsyncIterable, isIterable } from '@opra/common';
8
- import { concatReadable } from '../helpers/concat-readable.js';
9
- import { convertToHeaders, convertToHeadersDistinct } from '../helpers/convert-to-headers.js';
10
- import { convertToRawHeaders } from '../helpers/convert-to-raw-headers.js';
3
+ import { convertToHeaders, convertToHeadersDistinct } from '../utils/convert-to-headers.js';
4
+ import { convertToRawHeaders } from '../utils/convert-to-raw-headers.js';
11
5
  export const CRLF = Buffer.from('\r\n');
12
6
  export const kHeaders = Symbol.for('kHeaders');
13
7
  export const kHeadersDistinct = Symbol.for('kHeadersDistinct');
14
8
  export const kTrailers = Symbol.for('kTrailers');
15
9
  export const kTrailersDistinct = Symbol.for('kTrailersDistinct');
10
+ export const kHttpParser = Symbol.for('kHttpParser');
16
11
  /**
17
12
  *
18
- * @class HttpIncomingMessageHost
13
+ * @class NodeIncomingMessageHost
19
14
  */
20
- export class HttpIncomingMessageHost extends Duplex {
15
+ export class NodeIncomingMessageHost extends Duplex {
21
16
  constructor(init) {
22
17
  super();
23
18
  this.rawHeaders = [];
@@ -46,12 +41,14 @@ export class HttpIncomingMessageHost extends Duplex {
46
41
  this.ips = init.ips || (this.ip ? [this.ip] : []);
47
42
  if (this.body && !this.headers['content-length'])
48
43
  this.headers['content-length'] = String(this.body.length);
44
+ if (init.params)
45
+ this.params = init.params;
46
+ if (init.cookies)
47
+ this.cookies = init.cookies;
49
48
  }
50
49
  }
51
50
  get httpVersion() {
52
- return this.httpVersionMajor
53
- ? this.httpVersionMajor + '.' + this.httpVersionMinor
54
- : '';
51
+ return this.httpVersionMajor ? this.httpVersionMajor + '.' + this.httpVersionMinor : '';
55
52
  }
56
53
  get headers() {
57
54
  if (!this[kHeaders])
@@ -95,50 +92,12 @@ export class HttpIncomingMessageHost extends Duplex {
95
92
  }
96
93
  const chunk = this._readStream.read(size);
97
94
  this.push(chunk);
98
- // this.push(null);s
99
95
  }
100
96
  _write(chunk, encoding, callback) {
101
- const error = this._httpParser?.execute(chunk);
97
+ const error = this[kHttpParser]?.execute(chunk);
102
98
  if (error && typeof error === 'object')
103
99
  callback(error);
104
100
  else
105
101
  callback();
106
102
  }
107
- static from(iterable) {
108
- if (typeof iterable === 'object' && !(isIterable(iterable) || isAsyncIterable(iterable)))
109
- return new HttpIncomingMessageHost(iterable);
110
- const msg = new HttpIncomingMessageHost();
111
- const parser = msg._httpParser = new HTTPParser(HTTPParser.REQUEST);
112
- let bodyChunks;
113
- parser[HTTPParser.kOnHeadersComplete] = (info) => {
114
- msg.httpVersionMajor = info.versionMajor;
115
- msg.httpVersionMinor = info.versionMinor;
116
- msg.rawHeaders = info.headers;
117
- msg.method = HTTPParser.methods[info.method];
118
- msg.url = info.url;
119
- };
120
- parser[HTTPParser.kOnHeaders] = (trailers) => {
121
- msg.rawTrailers = trailers;
122
- };
123
- parser[HTTPParser.kOnBody] = (chunk, offset, length) => {
124
- bodyChunks = bodyChunks || [];
125
- bodyChunks.push(chunk.subarray(offset, offset + length));
126
- };
127
- parser[HTTPParser.kOnMessageComplete] = () => {
128
- msg.complete = true;
129
- if (bodyChunks)
130
- msg.body = Buffer.concat(bodyChunks);
131
- };
132
- const readable = concatReadable(Readable.from(iterable), Readable.from(CRLF));
133
- msg.once('finish', () => parser.finish());
134
- readable.pipe(msg);
135
- return msg;
136
- }
137
- static async fromAsync(iterable) {
138
- return new Promise((resolve, reject) => {
139
- const msg = this.from(iterable);
140
- msg.once('finish', () => resolve(msg));
141
- msg.once('error', (error) => reject(error));
142
- });
143
- }
144
103
  }
@@ -3,15 +3,15 @@
3
3
  https://github.com/nodejs/
4
4
  */
5
5
  import { Duplex } from 'stream';
6
- import { validateHeaderName, validateHeaderValue, validateString } from '../helpers/common.js';
6
+ import { validateHeaderName, validateHeaderValue, validateString } from '../utils/common.js';
7
7
  export const kOutHeaders = Symbol.for('kOutHeaders');
8
8
  export const kOutTrailers = Symbol.for('kOutTrailers');
9
9
  // noinspection JSUnusedLocalSymbols
10
10
  /**
11
11
  *
12
- * @class HttpOutgoingMessageHost
12
+ * @class NodeOutgoingMessageHost
13
13
  */
14
- export class HttpOutgoingMessageHost extends Duplex {
14
+ export class NodeOutgoingMessageHost extends Duplex {
15
15
  constructor(init) {
16
16
  super();
17
17
  this._headersSent = false;
@@ -64,8 +64,7 @@ export class HttpOutgoingMessageHost extends Duplex {
64
64
  }
65
65
  addTrailers(headers) {
66
66
  if (headers && typeof headers === 'object') {
67
- const entries = typeof headers.entries === 'function'
68
- ? headers.entries() : Object.entries(headers);
67
+ const entries = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers);
69
68
  let trailers = this[kOutTrailers];
70
69
  if (trailers == null)
71
70
  this[kOutTrailers] = trailers = { __proto__: null };
@@ -78,6 +77,9 @@ export class HttpOutgoingMessageHost extends Duplex {
78
77
  }
79
78
  throw new TypeError('Invalid "headers" argument. Value must be an object or raw headers array');
80
79
  }
80
+ flushHeaders() {
81
+ // nothing to do
82
+ }
81
83
  setHeader(name, value) {
82
84
  if (this.headersSent)
83
85
  throw new Error(`Cannot set headers after they are sent to the client`);
@@ -93,8 +95,7 @@ export class HttpOutgoingMessageHost extends Duplex {
93
95
  if (this.headersSent)
94
96
  throw new Error(`Cannot set headers after they are sent to the client`);
95
97
  if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
96
- const entries = typeof headers.entries === 'function'
97
- ? headers.entries() : Object.entries(headers);
98
+ const entries = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers);
98
99
  for (const entry of entries) {
99
100
  this.setHeader(entry[0], entry[1]);
100
101
  }
@@ -142,8 +143,7 @@ export class HttpOutgoingMessageHost extends Duplex {
142
143
  }
143
144
  hasHeader(name) {
144
145
  validateString(name);
145
- return this[kOutHeaders] != null &&
146
- !!this[kOutHeaders][name.toLowerCase()];
146
+ return this[kOutHeaders] != null && !!this[kOutHeaders][name.toLowerCase()];
147
147
  }
148
148
  removeHeader(name) {
149
149
  validateString(name);
@@ -188,7 +188,4 @@ export class HttpOutgoingMessageHost extends Duplex {
188
188
  //
189
189
  return this;
190
190
  }
191
- static from(init) {
192
- return new HttpOutgoingMessageHost(init);
193
- }
194
191
  }
@@ -0,0 +1,22 @@
1
+ import { mergePrototype } from '@opra/common';
2
+ import { isHttpIncoming, isNodeIncomingMessage } from '../../type-guards.js';
3
+ import { HttpIncomingHost } from '../impl/http-incoming.host.js';
4
+ import { NodeIncomingMessage } from './node-incoming-message.interface.js';
5
+ /**
6
+ * @namespace HttpIncoming
7
+ */
8
+ export var HttpIncoming;
9
+ (function (HttpIncoming) {
10
+ function from(instance) {
11
+ if (isHttpIncoming(instance))
12
+ return instance;
13
+ if (!isNodeIncomingMessage(instance))
14
+ instance = NodeIncomingMessage.from(instance);
15
+ mergePrototype(instance, HttpIncomingHost.prototype);
16
+ const req = instance;
17
+ req.baseUrl = req.baseUrl || '';
18
+ req.params = req.params || {};
19
+ return req;
20
+ }
21
+ HttpIncoming.from = from;
22
+ })(HttpIncoming || (HttpIncoming = {}));
@@ -0,0 +1,19 @@
1
+ import { mergePrototype } from '@opra/common';
2
+ import { isHttpOutgoing, isNodeOutgoingMessage } from '../../type-guards.js';
3
+ import { HttpOutgoingHost } from '../impl/http-outgoing.host.js';
4
+ import { NodeOutgoingMessage } from './node-outgoing-message.interface.js';
5
+ /**
6
+ * @namespace HttpIncoming
7
+ */
8
+ export var HttpOutgoing;
9
+ (function (HttpOutgoing) {
10
+ function from(instance) {
11
+ if (isHttpOutgoing(instance))
12
+ return instance;
13
+ if (!isNodeOutgoingMessage(instance))
14
+ instance = NodeOutgoingMessage.from(instance);
15
+ mergePrototype(instance, HttpOutgoingHost.prototype);
16
+ return instance;
17
+ }
18
+ HttpOutgoing.from = from;
19
+ })(HttpOutgoing || (HttpOutgoing = {}));
@@ -0,0 +1,60 @@
1
+ import { Readable } from 'stream';
2
+ import { HTTPParser } from '@browsery/http-parser';
3
+ import { isAsyncIterable, isIterable } from '@opra/common';
4
+ import { CRLF, kHttpParser, NodeIncomingMessageHost } from '../impl/node-incoming-message.host.js';
5
+ import { concatReadable } from '../utils/concat-readable.js';
6
+ /**
7
+ *
8
+ * @namespace NodeIncomingMessage
9
+ */
10
+ export var NodeIncomingMessage;
11
+ (function (NodeIncomingMessage) {
12
+ /**
13
+ * Creates a new NodeIncomingMessage from given argument
14
+ * @param iterable
15
+ */
16
+ function from(iterable) {
17
+ if (typeof iterable === 'object' && !(isIterable(iterable) || isAsyncIterable(iterable)))
18
+ return new NodeIncomingMessageHost(iterable);
19
+ const msg = new NodeIncomingMessageHost();
20
+ const parser = (msg[kHttpParser] = new HTTPParser(HTTPParser.REQUEST));
21
+ let bodyChunks;
22
+ parser[HTTPParser.kOnHeadersComplete] = (info) => {
23
+ msg.httpVersionMajor = info.versionMajor;
24
+ msg.httpVersionMinor = info.versionMinor;
25
+ msg.rawHeaders = info.headers;
26
+ msg.method = HTTPParser.methods[info.method];
27
+ msg.url = info.url;
28
+ msg.emit('headers');
29
+ };
30
+ parser[HTTPParser.kOnHeaders] = (trailers) => {
31
+ msg.rawTrailers = trailers;
32
+ };
33
+ parser[HTTPParser.kOnBody] = (chunk, offset, length) => {
34
+ bodyChunks = bodyChunks || [];
35
+ bodyChunks.push(chunk.subarray(offset, offset + length));
36
+ };
37
+ const readable = concatReadable(Readable.from(iterable), Readable.from(CRLF));
38
+ msg.once('finish', () => {
39
+ parser.finish();
40
+ msg.complete = true;
41
+ if (bodyChunks)
42
+ msg.body = Buffer.concat(bodyChunks);
43
+ });
44
+ readable.pipe(msg);
45
+ return msg;
46
+ }
47
+ NodeIncomingMessage.from = from;
48
+ /**
49
+ * Creates a new NodeIncomingMessage from given argument
50
+ * @param iterable
51
+ */
52
+ async function fromAsync(iterable) {
53
+ return new Promise((resolve, reject) => {
54
+ const msg = from(iterable);
55
+ msg.once('finish', () => resolve(msg));
56
+ msg.once('error', error => reject(error));
57
+ });
58
+ }
59
+ NodeIncomingMessage.fromAsync = fromAsync;
60
+ })(NodeIncomingMessage || (NodeIncomingMessage = {}));
@@ -0,0 +1,12 @@
1
+ import { NodeOutgoingMessageHost } from '../impl/node-outgoing-message.host.js';
2
+ /**
3
+ *
4
+ * @namespace NodeOutgoingMessage
5
+ */
6
+ export var NodeOutgoingMessage;
7
+ (function (NodeOutgoingMessage) {
8
+ function from(init) {
9
+ return new NodeOutgoingMessageHost(init);
10
+ }
11
+ NodeOutgoingMessage.from = from;
12
+ })(NodeOutgoingMessage || (NodeOutgoingMessage = {}));
@@ -0,0 +1,210 @@
1
+ import { Base64Decode } from 'base64-stream';
2
+ import byteParser from 'bytes';
3
+ import { parse as parseContentType } from 'content-type';
4
+ import { EventEmitter } from 'events';
5
+ import iconv from 'iconv-lite';
6
+ import { Writable } from 'stream';
7
+ import * as zlib from 'zlib';
8
+ import typeIs from '@browsery/type-is';
9
+ import { BadRequestError, InternalServerError, OpraHttpError } from '@opra/common';
10
+ /**
11
+ *
12
+ * @class BodyReader
13
+ */
14
+ export class BodyReader extends EventEmitter {
15
+ constructor(req, options) {
16
+ super();
17
+ this.req = req;
18
+ this._completed = false;
19
+ this._receivedSize = 0;
20
+ this.onAborted = () => this._onAborted();
21
+ this.onData = (chunk) => this._onData(chunk);
22
+ this.onEnd = (err) => this._onEnd(err);
23
+ this.cleanup = () => this._cleanup();
24
+ this.limit = options?.limit
25
+ ? typeof options.limit === 'number'
26
+ ? options.limit
27
+ : byteParser(options.limit)
28
+ : undefined;
29
+ }
30
+ async read() {
31
+ /* istanbul ignore next */
32
+ if (this._completed)
33
+ throw new InternalServerError({
34
+ message: 'Stream already read',
35
+ code: 'STREAM_ALREADY_READ',
36
+ });
37
+ if (!this.req.readable) {
38
+ throw new InternalServerError({
39
+ message: 'Stream is not readable',
40
+ code: 'STREAM_NOT_READABLE',
41
+ });
42
+ }
43
+ return new Promise((resolve, reject) => {
44
+ // eslint-disable-next-line prefer-const
45
+ let sizeStream;
46
+ this.once('finish', (error, data) => {
47
+ if (sizeStream)
48
+ this.req.unpipe(sizeStream);
49
+ if (error)
50
+ return reject(error);
51
+ resolve(data);
52
+ });
53
+ /**
54
+ * Check if a request has a request body.
55
+ * A request with a body __must__ either have `transfer-encoding`
56
+ * or `content-length` headers set.
57
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
58
+ */
59
+ const contentLength = parseInt(this.req.headers['content-length'] || '0', 10);
60
+ if (this.req.headers['transfer-encoding'] === undefined && !(contentLength && !isNaN(contentLength)))
61
+ return this.onEnd();
62
+ // check the length and limit options.
63
+ // note: we intentionally leave the stream paused,
64
+ // so users should handle the stream themselves.
65
+ if (this.limit != null && contentLength != null && contentLength > this.limit) {
66
+ return this.onEnd(new OpraHttpError({
67
+ message: 'Content Too Large',
68
+ code: 'HTTP.CONTENT_TOO_LARGE',
69
+ details: {
70
+ length: contentLength,
71
+ limit: this.limit,
72
+ },
73
+ }, 413));
74
+ }
75
+ // Pipe to a Writable stream to count received bytes
76
+ const _this = this;
77
+ sizeStream = new Writable({
78
+ write(chunk, encoding, callback) {
79
+ if (_this._completed)
80
+ return;
81
+ _this._receivedSize += chunk.length;
82
+ if (_this.limit != null && _this._receivedSize > _this.limit) {
83
+ callback(new OpraHttpError({
84
+ message: 'Content Too Large',
85
+ code: 'HTTP.CONTENT_TOO_LARGE',
86
+ details: {
87
+ limit: _this.limit,
88
+ received: _this._receivedSize,
89
+ },
90
+ }, 413));
91
+ }
92
+ },
93
+ });
94
+ this.req.pipe(sizeStream);
95
+ let stream = BodyReader.encoderPipeline(this.req);
96
+ const mediaType = parseContentType(this.req.headers['content-type'] || '');
97
+ let charset = (mediaType.parameters.charset || '').toLowerCase();
98
+ if (!charset && typeIs.is(mediaType.type, ['json', 'xml', 'txt']))
99
+ charset = 'utf-8';
100
+ if (charset) {
101
+ const newStream = iconv.decodeStream(charset);
102
+ stream.pipe(newStream);
103
+ stream = newStream;
104
+ }
105
+ this._stream = stream;
106
+ // attach listeners
107
+ stream.on('aborted', this.onAborted);
108
+ stream.on('close', this.cleanup);
109
+ stream.on('data', this.onData);
110
+ stream.on('end', this.onEnd);
111
+ stream.on('error', this.onEnd);
112
+ });
113
+ }
114
+ _onEnd(error) {
115
+ if (this._completed)
116
+ return;
117
+ this._completed = true;
118
+ if (error) {
119
+ this._stream?.unpipe();
120
+ this._stream?.pause();
121
+ }
122
+ if (error)
123
+ this.emit('finish', error);
124
+ else if (Array.isArray(this._buffer))
125
+ this.emit('finish', error, Buffer.concat(this._buffer));
126
+ else
127
+ this.emit('finish', error, this._buffer);
128
+ this._cleanup();
129
+ }
130
+ _cleanup() {
131
+ if (this._stream) {
132
+ this._stream.removeListener('aborted', this.onAborted);
133
+ this._stream.removeListener('close', this.cleanup);
134
+ this._stream.removeListener('data', this.onData);
135
+ this._stream.removeListener('end', this.onEnd);
136
+ this._stream.removeListener('error', this.onEnd);
137
+ }
138
+ }
139
+ _onAborted() {
140
+ if (this._completed)
141
+ return;
142
+ this.onEnd(new BadRequestError({
143
+ message: 'request aborted',
144
+ code: 'ECONNABORTED',
145
+ details: {
146
+ length,
147
+ received: this._receivedSize,
148
+ },
149
+ }));
150
+ }
151
+ _onData(chunk) {
152
+ if (this._completed)
153
+ return;
154
+ if (typeof chunk === 'string') {
155
+ this._buffer = this._buffer || '';
156
+ this._buffer += chunk;
157
+ }
158
+ else {
159
+ this._buffer = this._buffer || [];
160
+ this._buffer.push(chunk);
161
+ }
162
+ }
163
+ static async read(req, options) {
164
+ const bodyReady = new BodyReader(req, options);
165
+ return bodyReady.read();
166
+ }
167
+ static encoderPipeline(req) {
168
+ const contentEncoding = req.headers['content-encoding'] || 'identity';
169
+ const contentEncodings = (Array.isArray(contentEncoding) ? contentEncoding : contentEncoding.split(/\s*,\s*/))
170
+ .map(s => s.toLowerCase())
171
+ .reverse();
172
+ return contentEncodings.reduce((prev, encoding) => {
173
+ switch (encoding) {
174
+ case 'gzip':
175
+ case 'x-gzip': {
176
+ const newStream = zlib.createGunzip();
177
+ prev.pipe(newStream);
178
+ return newStream;
179
+ }
180
+ case 'deflate':
181
+ case 'x-deflate': {
182
+ const newStream = zlib.createInflate();
183
+ prev.pipe(newStream);
184
+ return newStream;
185
+ }
186
+ case 'br': {
187
+ const newStream = zlib.createBrotliDecompress();
188
+ prev.pipe(newStream);
189
+ return newStream;
190
+ }
191
+ case 'base64': {
192
+ const newStream = new Base64Decode();
193
+ prev.pipe(newStream);
194
+ return newStream;
195
+ }
196
+ case 'identity':
197
+ // prev.length = 0;
198
+ return prev;
199
+ default:
200
+ throw new BadRequestError({
201
+ message: 'unsupported content encoding "' + encoding + '"',
202
+ code: '',
203
+ details: {
204
+ encoding,
205
+ },
206
+ }, 415);
207
+ }
208
+ }, req);
209
+ }
210
+ }
@@ -1,4 +1,4 @@
1
- import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD } from './match-known-fields.js';
1
+ import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD, } from './match-known-fields.js';
2
2
  export function convertToHeaders(src, dst, joinDuplicateHeaders) {
3
3
  for (let n = 0; n < src.length; n += 2) {
4
4
  addHeaderLine(src[n], src[n + 1], dst, joinDuplicateHeaders);
@@ -1,7 +1,6 @@
1
- import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD } from './match-known-fields.js';
1
+ import { ARRAY_FIELD, COMMA_DELIMITED_FIELD, matchKnownFields, SEMICOLON_DELIMITED_FIELD, } from './match-known-fields.js';
2
2
  export function convertToRawHeaders(src) {
3
- return Object.entries(src)
4
- .reduce((a, [field, v]) => {
3
+ return Object.entries(src).reduce((a, [field, v]) => {
5
4
  const [name, flag] = matchKnownFields(field);
6
5
  if (flag === ARRAY_FIELD) {
7
6
  if (Array.isArray(v))