@superhero/http-server 4.1.0 → 4.2.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.
- package/config.json +3 -3
- package/{middleware → dispatcher}/upstream/header/accept.js +14 -5
- package/{middleware → dispatcher}/upstream/header/content-type/application/json.js +6 -7
- package/{middleware → dispatcher}/upstream/header/content-type.js +16 -7
- package/{middleware → dispatcher}/upstream/method.js +3 -3
- package/index.test.js +5 -5
- package/package.json +3 -2
- package/view.js +3 -2
package/config.json
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
"locator":
|
|
7
7
|
{
|
|
8
8
|
"@superhero/http-server": true,
|
|
9
|
-
"@superhero/http-server/
|
|
10
|
-
"@superhero/http-server/
|
|
11
|
-
"@superhero/http-server/
|
|
9
|
+
"@superhero/http-server/dispatcher/*/*": "./dispatcher/*/*.js",
|
|
10
|
+
"@superhero/http-server/dispatcher/*/*/*": "./dispatcher/*/*/*.js",
|
|
11
|
+
"@superhero/http-server/dispatcher/*/*/*/*/*": "./dispatcher/*/*/*/*/*.js"
|
|
12
12
|
},
|
|
13
13
|
"http-server":
|
|
14
14
|
{
|
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @memberof @superhero/http-server:
|
|
2
|
+
* @memberof @superhero/http-server:dispatcher/upstream/header
|
|
3
3
|
*/
|
|
4
|
-
export default new class
|
|
4
|
+
export default new class AcceptHeaderUpstreamDispatcher
|
|
5
5
|
{
|
|
6
6
|
#listFormat = new Intl.ListFormat('en', { style:'long', type:'disjunction' })
|
|
7
7
|
#normalize = (route) => route.replace('accept.', '').trim()
|
|
8
8
|
|
|
9
9
|
dispatch(request, session)
|
|
10
10
|
{
|
|
11
|
+
if(false === !!request.headers['accept'])
|
|
12
|
+
{
|
|
13
|
+
const error = new Error(`The requested resource "${request.method} ${request.url}" requires a accept header`)
|
|
14
|
+
error.code = 'E_HTTP_SERVER_ACCEPT_HEADER_MISSING'
|
|
15
|
+
error.status = 406
|
|
16
|
+
error.cause = `The requested resource requires a accept header to be set`
|
|
17
|
+
return session.abortion.abort(error)
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
const
|
|
12
21
|
splitHeader = request.headers['accept']?.toLowerCase().split(',') || [],
|
|
13
22
|
accepts = splitHeader.map(this.#normalize),
|
|
14
23
|
routes = Object.keys(session.route).filter((key) => key.startsWith('accept.') && session.route[key]),
|
|
15
|
-
supports = routes.map((route) => [this.#normalize(route), route])
|
|
24
|
+
supports = routes.map((route) => [ this.#normalize(route), route ])
|
|
16
25
|
|
|
17
26
|
for(let accepted of accepts)
|
|
18
27
|
{
|
|
19
28
|
accepted = accepted.split(';')[0].split('*')[0]
|
|
20
29
|
|
|
21
|
-
for(let [supported, route]
|
|
30
|
+
for(let [ supported, route ] of supports)
|
|
22
31
|
{
|
|
23
32
|
supported = supported.split('*')[0]
|
|
24
33
|
|
|
@@ -42,7 +51,7 @@ export default new class AcceptHeaderUpstreamMiddleware
|
|
|
42
51
|
allowed = supports.map(([ supported ]) => supported),
|
|
43
52
|
error = new Error(`The requested resource "${request.method} ${request.url}" can not be delivered in requested header accept media types: ${this.#listFormat.format(accepts) || 'none are defined'}`)
|
|
44
53
|
|
|
45
|
-
error.code = '
|
|
54
|
+
error.code = 'E_HTTP_SERVER_ACCEPT_HEADER_NO_ROUTE'
|
|
46
55
|
error.status = 406
|
|
47
56
|
error.headers = { accept:allowed.join(',') }
|
|
48
57
|
error.cause = `Supported accept header media types are: ${this.#listFormat.format(allowed) || 'none are defined'}`
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Does not validate headers, just assumes that the body is a JSON string
|
|
3
3
|
*
|
|
4
|
-
* @memberof @superhero/http-server:
|
|
4
|
+
* @memberof @superhero/http-server:dispatcher/upstream/header/content-type/application
|
|
5
5
|
*/
|
|
6
|
-
export default new class
|
|
6
|
+
export default new class ContentTypeApplicationJsonHeaderUpstreamDispatcher
|
|
7
7
|
{
|
|
8
8
|
async dispatch(request, session)
|
|
9
9
|
{
|
|
@@ -13,16 +13,15 @@ export default new class ContentTypeApplicationJsonHeaderUpstreamMiddleware
|
|
|
13
13
|
{
|
|
14
14
|
try
|
|
15
15
|
{
|
|
16
|
-
request.body = JSON.parse(body)
|
|
16
|
+
request.body = JSON.parse(String(body) || '{}')
|
|
17
17
|
}
|
|
18
18
|
catch(reason)
|
|
19
19
|
{
|
|
20
20
|
const error = new Error('The body is not a valid JSON string')
|
|
21
|
-
error.code = '
|
|
21
|
+
error.code = 'E_HTTP_SERVER_CONTENT_TYPE_HEADER_APPLICATION_JSON'
|
|
22
22
|
error.status = 400
|
|
23
|
-
error.cause =
|
|
24
|
-
|
|
25
|
-
session.abortion.abort(error)
|
|
23
|
+
error.cause = reason
|
|
24
|
+
return session.abortion.abort(error)
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @memberof @superhero/http-server:
|
|
2
|
+
* @memberof @superhero/http-server:dispatcher/upstream/header
|
|
3
3
|
*/
|
|
4
|
-
export default new class
|
|
4
|
+
export default new class ContentTypeHeaderUpstreamDispatcher
|
|
5
5
|
{
|
|
6
6
|
#listFormat = new Intl.ListFormat('en', { style:'long', type:'disjunction' })
|
|
7
7
|
|
|
8
8
|
dispatch(request, session)
|
|
9
9
|
{
|
|
10
|
+
if(false === !!request.headers['content-type'])
|
|
11
|
+
{
|
|
12
|
+
const error = new Error(`The requested resource "${request.method} ${request.url}" requires a content-type header`)
|
|
13
|
+
error.code = 'E_HTTP_SERVER_CONTENT_TYPE_HEADER_MISSING'
|
|
14
|
+
error.status = 415
|
|
15
|
+
error.cause = `The requested resource requires a content-type header to be set`
|
|
16
|
+
return session.abortion.abort(error)
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
const
|
|
11
|
-
contentType = request.headers['content-type']
|
|
20
|
+
contentType = request.headers['content-type'].toLowerCase().split(';')[0].split('*')[0].trim(),
|
|
12
21
|
routes = Object.keys(session.route).filter((key) => key.startsWith('content-type.') && session.route[key]),
|
|
13
|
-
supports = routes.map((route) => [route.replace('content-type.', '').trim(), route])
|
|
22
|
+
supports = routes.map((route) => [ route.replace('content-type.', '').trim(), route ])
|
|
14
23
|
|
|
15
|
-
for(let [supported, route]
|
|
24
|
+
for(let [ supported, route ] of supports)
|
|
16
25
|
{
|
|
17
26
|
supported = supported.split('*')[0]
|
|
18
27
|
|
|
@@ -30,12 +39,12 @@ export default new class ContentTypeHeaderUpstreamMiddleware
|
|
|
30
39
|
return
|
|
31
40
|
}
|
|
32
41
|
}
|
|
33
|
-
|
|
42
|
+
|
|
34
43
|
const
|
|
35
44
|
allowed = supports.map(([ supported ]) => supported),
|
|
36
45
|
error = new Error(`The requested resource "${request.method} ${request.url}" does not support content-type "${request.headers['content-type']}"`)
|
|
37
46
|
|
|
38
|
-
error.code = '
|
|
47
|
+
error.code = 'E_HTTP_SERVER_CONTENT_TYPE_HEADER_NO_ROUTE'
|
|
39
48
|
error.status = 415
|
|
40
49
|
error.headers = { accept:allowed.join(',') }
|
|
41
50
|
error.cause = `Supported content-type headers are: ${this.#listFormat.format(allowed) || 'none are defined'}`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @memberof @superhero/http-server:
|
|
2
|
+
* @memberof @superhero/http-server:dispatcher/upstream
|
|
3
3
|
*/
|
|
4
|
-
export default new class
|
|
4
|
+
export default new class MethodUpstreamDispatcher
|
|
5
5
|
{
|
|
6
6
|
#listFormat = new Intl.ListFormat('en', { style:'long', type:'disjunction' })
|
|
7
7
|
|
|
@@ -28,7 +28,7 @@ export default new class MethodUpstreamMiddleware
|
|
|
28
28
|
allowed = supports.map((supported) => supported.replace('method.', '').toUpperCase()).sort(),
|
|
29
29
|
error = new Error(`The requested resource "${request.url}" does not support method "${request.method}"`)
|
|
30
30
|
|
|
31
|
-
error.code = '
|
|
31
|
+
error.code = 'E_HTTP_SERVER_METHOD_NO_ROUTE'
|
|
32
32
|
error.status = 405
|
|
33
33
|
error.headers = { allow:allowed.join(',') }
|
|
34
34
|
error.cause = `Supported methods are: ${this.#listFormat.format(allowed) || 'none are defined'}`
|
package/index.test.js
CHANGED
|
@@ -75,12 +75,12 @@ suite('@superhero/http-server', () =>
|
|
|
75
75
|
assert.ok(config.find('http-server/router/routes'))
|
|
76
76
|
|
|
77
77
|
await locator.eagerload(config.find('locator'))
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
assert.ok(locator.has('@superhero/http-server'))
|
|
80
|
-
assert.ok(locator.has('@superhero/http-server/
|
|
81
|
-
assert.ok(locator.has('@superhero/http-server/
|
|
82
|
-
assert.ok(locator.has('@superhero/http-server/
|
|
83
|
-
assert.ok(locator.has('@superhero/http-server/
|
|
80
|
+
assert.ok(locator.has('@superhero/http-server/dispatcher/upstream/method'))
|
|
81
|
+
assert.ok(locator.has('@superhero/http-server/dispatcher/upstream/header/accept'))
|
|
82
|
+
assert.ok(locator.has('@superhero/http-server/dispatcher/upstream/header/content-type'))
|
|
83
|
+
assert.ok(locator.has('@superhero/http-server/dispatcher/upstream/header/content-type/application/json'))
|
|
84
84
|
})
|
|
85
85
|
|
|
86
86
|
test('Listens and closes the server as expected', async () =>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superhero/http-server",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "HTTP(S) server component supporting both HTTP 1.1 and HTTP 2.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"http server",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"./*/*/*/*/*/*": "./*/*/*/*/*/*.js"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@superhero/router": "^4.1.0"
|
|
25
|
+
"@superhero/router": "^4.1.0",
|
|
26
|
+
"@superhero/deep": "^4.1.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@superhero/config": "^4.1.2",
|
package/view.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import deepmerge from '@superhero/deep/merge'
|
|
2
|
+
import { Transform } from 'node:stream'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A view model is a data model specifically designed for the view layer to
|
|
@@ -48,7 +49,7 @@ export default class View
|
|
|
48
49
|
Object.defineProperties(this,
|
|
49
50
|
{
|
|
50
51
|
// The body property is an object that represents the response body.
|
|
51
|
-
body : { enumerable: true, value: {} },
|
|
52
|
+
body : { enumerable: true, value: {}, set: (value) => deepmerge(this.body, value) },
|
|
52
53
|
// The stream property is a transform stream in object mode that by default encodes objects
|
|
53
54
|
// as stringified JSON data records according to HTML5 standard Server-Sent Events (SSE).
|
|
54
55
|
stream : { enumerable: true, configurable: true, get: () => this.#lazyloadStream },
|