@opra/core 0.33.13 → 1.0.0-alpha.2
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/cjs/augmentation/18n.augmentation.js +17 -4
- package/cjs/augmentation/http-controller.augmentation.js +25 -0
- package/cjs/constants.js +5 -0
- package/cjs/execution-context.js +25 -12
- package/cjs/{services → helpers}/logger.js +1 -2
- package/cjs/{services/api-service.js → helpers/service-base.js} +8 -8
- package/cjs/http/express-adapter.js +164 -0
- package/cjs/http/http-adapter.js +27 -0
- package/cjs/http/http-context.js +116 -0
- package/cjs/http/impl/asset-cache.js +21 -0
- package/cjs/http/impl/http-handler.js +575 -0
- package/cjs/http/{http-server-request.js → impl/http-incoming.host.js} +21 -46
- package/cjs/http/{http-server-response.js → impl/http-outgoing.host.js} +7 -26
- package/cjs/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +24 -22
- package/cjs/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +13 -54
- package/cjs/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +11 -14
- package/cjs/http/interfaces/http-incoming.interface.js +25 -0
- package/cjs/http/interfaces/http-outgoing.interface.js +22 -0
- package/cjs/http/interfaces/node-incoming-message.interface.js +63 -0
- package/cjs/http/interfaces/node-outgoing-message.interface.js +15 -0
- package/cjs/http/utils/body-reader.js +215 -0
- package/cjs/http/{helpers → utils}/convert-to-raw-headers.js +1 -2
- package/cjs/http/{helpers → utils}/match-known-fields.js +11 -9
- package/cjs/http/utils/wrap-exception.js +34 -0
- package/cjs/index.js +25 -25
- package/cjs/platform-adapter.js +21 -0
- package/cjs/type-guards.js +23 -0
- package/esm/augmentation/18n.augmentation.js +20 -7
- package/esm/augmentation/http-controller.augmentation.js +23 -0
- package/esm/constants.js +2 -0
- package/esm/execution-context.js +25 -13
- package/esm/{services → helpers}/logger.js +1 -2
- package/esm/{services/api-service.js → helpers/service-base.js} +6 -6
- package/esm/http/express-adapter.js +159 -0
- package/esm/http/http-adapter.js +23 -0
- package/esm/http/http-context.js +111 -0
- package/esm/http/impl/asset-cache.js +17 -0
- package/esm/http/impl/http-handler.js +570 -0
- package/esm/http/{http-server-request.js → impl/http-incoming.host.js} +17 -43
- package/esm/http/{http-server-response.js → impl/http-outgoing.host.js} +6 -26
- package/esm/http/{helpers/multipart-helper.js → impl/multipart-reader.js} +22 -20
- package/esm/http/impl/{http-incoming-message.host.js → node-incoming-message.host.js} +11 -52
- package/esm/http/impl/{http-outgoing-message.host.js → node-outgoing-message.host.js} +9 -12
- package/esm/http/interfaces/http-incoming.interface.js +22 -0
- package/esm/http/interfaces/http-outgoing.interface.js +19 -0
- package/esm/http/interfaces/node-incoming-message.interface.js +60 -0
- package/esm/http/interfaces/node-outgoing-message.interface.js +12 -0
- package/esm/http/utils/body-reader.js +210 -0
- package/esm/http/{helpers → utils}/convert-to-headers.js +1 -1
- package/esm/http/{helpers → utils}/convert-to-raw-headers.js +2 -3
- package/esm/http/{helpers → utils}/match-known-fields.js +11 -9
- package/esm/http/utils/wrap-exception.js +30 -0
- package/esm/index.js +25 -26
- package/esm/platform-adapter.js +19 -1
- package/esm/type-guards.js +16 -0
- package/package.json +22 -10
- package/types/augmentation/18n.augmentation.d.ts +32 -2
- package/types/augmentation/http-controller.augmentation.d.ts +21 -0
- package/types/constants.d.ts +2 -0
- package/types/execution-context.d.ts +24 -26
- package/types/helpers/service-base.d.ts +10 -0
- package/types/http/express-adapter.d.ts +13 -0
- package/types/http/http-adapter.d.ts +27 -0
- package/types/http/http-context.d.ts +44 -0
- package/types/http/impl/asset-cache.d.ts +5 -0
- package/types/http/impl/http-handler.d.ts +73 -0
- package/types/http/impl/http-incoming.host.d.ts +23 -0
- package/types/http/impl/http-outgoing.host.d.ts +17 -0
- package/types/http/{helpers/multipart-helper.d.ts → impl/multipart-reader.d.ts} +8 -6
- package/types/http/impl/{http-incoming-message.host.d.ts → node-incoming-message.host.d.ts} +9 -22
- package/types/http/impl/{http-outgoing-message.host.d.ts → node-outgoing-message.host.d.ts} +11 -27
- package/types/http/{http-server-request.d.ts → interfaces/http-incoming.interface.d.ts} +28 -17
- package/types/http/{http-server-response.d.ts → interfaces/http-outgoing.interface.d.ts} +17 -10
- package/types/http/interfaces/node-incoming-message.interface.d.ts +38 -0
- package/types/http/interfaces/node-outgoing-message.interface.d.ts +29 -0
- package/types/http/utils/body-reader.d.ts +41 -0
- package/types/http/utils/wrap-exception.d.ts +2 -0
- package/types/index.d.ts +24 -26
- package/types/platform-adapter.d.ts +20 -48
- package/types/type-guards.d.ts +8 -0
- package/cjs/augmentation/collection.augmentation.js +0 -2
- package/cjs/augmentation/container.augmentation.js +0 -2
- package/cjs/augmentation/resource.augmentation.js +0 -26
- package/cjs/augmentation/singleton.augmentation.js +0 -2
- package/cjs/augmentation/storage.augmentation.js +0 -2
- package/cjs/execution-context.host.js +0 -46
- package/cjs/http/adapters/express-adapter.host.js +0 -34
- package/cjs/http/adapters/express-adapter.js +0 -14
- package/cjs/http/adapters/node-http-adapter.host.js +0 -70
- package/cjs/http/adapters/node-http-adapter.js +0 -14
- package/cjs/http/helpers/json-body-loader.js +0 -29
- package/cjs/http/helpers/query-parsers.js +0 -16
- package/cjs/http/http-adapter-host.js +0 -715
- package/cjs/interfaces/interceptor.interface.js +0 -2
- package/cjs/interfaces/request-handler.interface.js +0 -2
- package/cjs/platform-adapter.host.js +0 -154
- package/cjs/request-context.js +0 -25
- package/cjs/request.host.js +0 -24
- package/cjs/request.js +0 -2
- package/cjs/response.host.js +0 -22
- package/cjs/response.js +0 -2
- package/esm/augmentation/collection.augmentation.js +0 -1
- package/esm/augmentation/container.augmentation.js +0 -1
- package/esm/augmentation/resource.augmentation.js +0 -24
- package/esm/augmentation/singleton.augmentation.js +0 -1
- package/esm/augmentation/storage.augmentation.js +0 -1
- package/esm/execution-context.host.js +0 -42
- package/esm/http/adapters/express-adapter.host.js +0 -30
- package/esm/http/adapters/express-adapter.js +0 -11
- package/esm/http/adapters/node-http-adapter.host.js +0 -65
- package/esm/http/adapters/node-http-adapter.js +0 -11
- package/esm/http/helpers/json-body-loader.js +0 -24
- package/esm/http/helpers/query-parsers.js +0 -12
- package/esm/http/http-adapter-host.js +0 -710
- package/esm/interfaces/interceptor.interface.js +0 -1
- package/esm/interfaces/request-handler.interface.js +0 -1
- package/esm/platform-adapter.host.js +0 -149
- package/esm/request-context.js +0 -22
- package/esm/request.host.js +0 -20
- package/esm/request.js +0 -1
- package/esm/response.host.js +0 -18
- package/esm/response.js +0 -1
- package/i18n/i18n/en/error.json +0 -21
- package/types/augmentation/collection.augmentation.d.ts +0 -146
- package/types/augmentation/container.augmentation.d.ts +0 -14
- package/types/augmentation/resource.augmentation.d.ts +0 -38
- package/types/augmentation/singleton.augmentation.d.ts +0 -83
- package/types/augmentation/storage.augmentation.d.ts +0 -50
- package/types/execution-context.host.d.ts +0 -25
- package/types/http/adapters/express-adapter.d.ts +0 -15
- package/types/http/adapters/express-adapter.host.d.ts +0 -12
- package/types/http/adapters/node-http-adapter.d.ts +0 -17
- package/types/http/adapters/node-http-adapter.host.d.ts +0 -19
- package/types/http/helpers/json-body-loader.d.ts +0 -5
- package/types/http/helpers/query-parsers.d.ts +0 -1
- package/types/http/http-adapter-host.d.ts +0 -34
- package/types/interfaces/interceptor.interface.d.ts +0 -2
- package/types/interfaces/request-handler.interface.d.ts +0 -4
- package/types/platform-adapter.host.d.ts +0 -43
- package/types/request-context.d.ts +0 -13
- package/types/request.d.ts +0 -14
- package/types/request.host.d.ts +0 -27
- package/types/response.d.ts +0 -22
- package/types/response.host.d.ts +0 -22
- package/types/services/api-service.d.ts +0 -10
- /package/cjs/http/{helpers → utils}/common.js +0 -0
- /package/cjs/http/{helpers → utils}/concat-readable.js +0 -0
- /package/cjs/http/{helpers → utils}/convert-to-headers.js +0 -0
- /package/esm/http/{helpers → utils}/common.js +0 -0
- /package/esm/http/{helpers → utils}/concat-readable.js +0 -0
- /package/types/{services → helpers}/logger.d.ts +0 -0
- /package/types/http/{helpers → utils}/common.d.ts +0 -0
- /package/types/http/{helpers → utils}/concat-readable.d.ts +0 -0
- /package/types/http/{helpers → utils}/convert-to-headers.d.ts +0 -0
- /package/types/http/{helpers → utils}/convert-to-raw-headers.d.ts +0 -0
- /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
|
|
15
|
-
import { HttpOutgoingMessageHost } from './impl/http-outgoing-message.host.js';
|
|
14
|
+
import { HttpStatusCode } from '@opra/common';
|
|
16
15
|
const charsetRegExp = /;\s*charset\s*=/;
|
|
17
|
-
|
|
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
|
|
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
|
-
|
|
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', (
|
|
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', (
|
|
29
|
-
const item = {
|
|
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',
|
|
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
|
|
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
|
-
})
|
|
90
|
+
})
|
|
91
|
+
.then(() => {
|
|
86
92
|
return fs.unlink(file.filepath);
|
|
87
|
-
})
|
|
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 {
|
|
9
|
-
import {
|
|
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
|
|
13
|
+
* @class NodeIncomingMessageHost
|
|
19
14
|
*/
|
|
20
|
-
export class
|
|
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
|
|
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 '../
|
|
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
|
|
12
|
+
* @class NodeOutgoingMessageHost
|
|
13
13
|
*/
|
|
14
|
-
export class
|
|
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))
|