@toa.io/extensions.exposition 0.23.0-dev.0 → 0.24.0-alpha.0
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/documentation/access.md +2 -3
- package/documentation/cache.md +42 -0
- package/documentation/octets.md +3 -3
- package/documentation/tree.md +1 -5
- package/features/cache.feature +160 -0
- package/features/steps/HTTP.ts +8 -2
- package/features/steps/Parameters.ts +1 -2
- package/features/steps/Workspace.ts +5 -3
- package/package.json +13 -10
- package/readme.md +1 -0
- package/source/HTTP/Server.test.ts +5 -3
- package/source/HTTP/Server.ts +30 -18
- package/source/HTTP/messages.ts +4 -4
- package/source/RTD/syntax/parse.ts +2 -1
- package/source/Tenant.ts +5 -0
- package/source/directives/auth/Family.ts +2 -2
- package/source/directives/cache/Control.ts +59 -0
- package/source/directives/cache/Exact.ts +7 -0
- package/source/directives/cache/Family.ts +36 -0
- package/source/directives/cache/index.ts +3 -0
- package/source/directives/cache/types.ts +9 -0
- package/source/directives/index.ts +2 -1
- package/source/directives/octets/Fetch.ts +3 -3
- package/transpiled/Annotation.d.ts +7 -0
- package/transpiled/Annotation.js +3 -0
- package/transpiled/Annotation.js.map +1 -0
- package/transpiled/Branch.d.ts +7 -0
- package/transpiled/Branch.js +3 -0
- package/transpiled/Branch.js.map +1 -0
- package/transpiled/Composition.d.ts +14 -0
- package/transpiled/Composition.js +43 -0
- package/transpiled/Composition.js.map +1 -0
- package/transpiled/Context.d.ts +5 -0
- package/transpiled/Context.js +3 -0
- package/transpiled/Context.js.map +1 -0
- package/transpiled/Directive.d.ts +32 -0
- package/transpiled/Directive.js +76 -0
- package/transpiled/Directive.js.map +1 -0
- package/transpiled/Endpoint.d.ts +20 -0
- package/transpiled/Endpoint.js +45 -0
- package/transpiled/Endpoint.js.map +1 -0
- package/transpiled/Factory.d.ts +10 -0
- package/transpiled/Factory.js +66 -0
- package/transpiled/Factory.js.map +1 -0
- package/transpiled/Gateway.d.ts +19 -0
- package/transpiled/Gateway.js +92 -0
- package/transpiled/Gateway.js.map +1 -0
- package/transpiled/HTTP/Server.d.ts +22 -0
- package/transpiled/HTTP/Server.fixtures.d.ts +11 -0
- package/transpiled/HTTP/Server.fixtures.js +32 -0
- package/transpiled/HTTP/Server.fixtures.js.map +1 -0
- package/transpiled/HTTP/Server.js +131 -0
- package/transpiled/HTTP/Server.js.map +1 -0
- package/transpiled/HTTP/exceptions.d.ts +34 -0
- package/transpiled/HTTP/exceptions.js +71 -0
- package/transpiled/HTTP/exceptions.js.map +1 -0
- package/transpiled/HTTP/formats/index.d.ts +10 -0
- package/transpiled/HTTP/formats/index.js +38 -0
- package/transpiled/HTTP/formats/index.js.map +1 -0
- package/transpiled/HTTP/formats/json.d.ts +6 -0
- package/transpiled/HTTP/formats/json.js +17 -0
- package/transpiled/HTTP/formats/json.js.map +1 -0
- package/transpiled/HTTP/formats/msgpack.d.ts +6 -0
- package/transpiled/HTTP/formats/msgpack.js +38 -0
- package/transpiled/HTTP/formats/msgpack.js.map +1 -0
- package/transpiled/HTTP/formats/text.d.ts +6 -0
- package/transpiled/HTTP/formats/text.js +15 -0
- package/transpiled/HTTP/formats/text.js.map +1 -0
- package/transpiled/HTTP/formats/yaml.d.ts +6 -0
- package/transpiled/HTTP/formats/yaml.js +41 -0
- package/transpiled/HTTP/formats/yaml.js.map +1 -0
- package/transpiled/HTTP/index.d.ts +3 -0
- package/transpiled/HTTP/index.js +20 -0
- package/transpiled/HTTP/index.js.map +1 -0
- package/transpiled/HTTP/messages.d.ts +28 -0
- package/transpiled/HTTP/messages.js +70 -0
- package/transpiled/HTTP/messages.js.map +1 -0
- package/transpiled/Mapping.d.ts +8 -0
- package/transpiled/Mapping.js +38 -0
- package/transpiled/Mapping.js.map +1 -0
- package/transpiled/Query.d.ts +13 -0
- package/transpiled/Query.js +107 -0
- package/transpiled/Query.js.map +1 -0
- package/transpiled/RTD/Context.d.ts +11 -0
- package/transpiled/RTD/Context.js +3 -0
- package/transpiled/RTD/Context.js.map +1 -0
- package/transpiled/RTD/Directives.d.ts +7 -0
- package/transpiled/RTD/Directives.js +3 -0
- package/transpiled/RTD/Directives.js.map +1 -0
- package/transpiled/RTD/Endpoint.d.ts +9 -0
- package/transpiled/RTD/Endpoint.js +3 -0
- package/transpiled/RTD/Endpoint.js.map +1 -0
- package/transpiled/RTD/Match.d.ts +11 -0
- package/transpiled/RTD/Match.js +3 -0
- package/transpiled/RTD/Match.js.map +1 -0
- package/transpiled/RTD/Method.d.ts +9 -0
- package/transpiled/RTD/Method.js +16 -0
- package/transpiled/RTD/Method.js.map +1 -0
- package/transpiled/RTD/Node.d.ts +21 -0
- package/transpiled/RTD/Node.js +61 -0
- package/transpiled/RTD/Node.js.map +1 -0
- package/transpiled/RTD/Route.d.ts +14 -0
- package/transpiled/RTD/Route.js +49 -0
- package/transpiled/RTD/Route.js.map +1 -0
- package/transpiled/RTD/Tree.d.ts +14 -0
- package/transpiled/RTD/Tree.js +40 -0
- package/transpiled/RTD/Tree.js.map +1 -0
- package/transpiled/RTD/factory.d.ts +6 -0
- package/transpiled/RTD/factory.js +36 -0
- package/transpiled/RTD/factory.js.map +1 -0
- package/transpiled/RTD/index.d.ts +8 -0
- package/transpiled/RTD/index.js +38 -0
- package/transpiled/RTD/index.js.map +1 -0
- package/transpiled/RTD/segment.d.ts +8 -0
- package/transpiled/RTD/segment.js +25 -0
- package/transpiled/RTD/segment.js.map +1 -0
- package/transpiled/RTD/syntax/index.d.ts +2 -0
- package/transpiled/RTD/syntax/index.js +19 -0
- package/transpiled/RTD/syntax/index.js.map +1 -0
- package/transpiled/RTD/syntax/parse.d.ts +4 -0
- package/transpiled/RTD/syntax/parse.js +128 -0
- package/transpiled/RTD/syntax/parse.js.map +1 -0
- package/transpiled/RTD/syntax/types.d.ts +41 -0
- package/transpiled/RTD/syntax/types.js +5 -0
- package/transpiled/RTD/syntax/types.js.map +1 -0
- package/transpiled/Remotes.d.ts +9 -0
- package/transpiled/Remotes.js +25 -0
- package/transpiled/Remotes.js.map +1 -0
- package/transpiled/Tenant.d.ts +13 -0
- package/transpiled/Tenant.js +34 -0
- package/transpiled/Tenant.js.map +1 -0
- package/transpiled/deployment.d.ts +3 -0
- package/transpiled/deployment.js +67 -0
- package/transpiled/deployment.js.map +1 -0
- package/transpiled/directives/auth/Anonymous.d.ts +6 -0
- package/transpiled/directives/auth/Anonymous.js +17 -0
- package/transpiled/directives/auth/Anonymous.js.map +1 -0
- package/transpiled/directives/auth/Echo.d.ts +6 -0
- package/transpiled/directives/auth/Echo.js +13 -0
- package/transpiled/directives/auth/Echo.js.map +1 -0
- package/transpiled/directives/auth/Family.d.ts +20 -0
- package/transpiled/directives/auth/Family.js +118 -0
- package/transpiled/directives/auth/Family.js.map +1 -0
- package/transpiled/directives/auth/Id.d.ts +7 -0
- package/transpiled/directives/auth/Id.js +17 -0
- package/transpiled/directives/auth/Id.js.map +1 -0
- package/transpiled/directives/auth/Incept.d.ts +10 -0
- package/transpiled/directives/auth/Incept.js +58 -0
- package/transpiled/directives/auth/Incept.js.map +1 -0
- package/transpiled/directives/auth/Role.d.ts +11 -0
- package/transpiled/directives/auth/Role.js +44 -0
- package/transpiled/directives/auth/Role.js.map +1 -0
- package/transpiled/directives/auth/Rule.d.ts +9 -0
- package/transpiled/directives/auth/Rule.js +22 -0
- package/transpiled/directives/auth/Rule.js.map +1 -0
- package/transpiled/directives/auth/Scheme.d.ts +7 -0
- package/transpiled/directives/auth/Scheme.js +47 -0
- package/transpiled/directives/auth/Scheme.js.map +1 -0
- package/transpiled/directives/auth/index.d.ts +2 -0
- package/transpiled/directives/auth/index.js +7 -0
- package/transpiled/directives/auth/index.js.map +1 -0
- package/transpiled/directives/auth/schemes.d.ts +3 -0
- package/transpiled/directives/auth/schemes.js +9 -0
- package/transpiled/directives/auth/schemes.js.map +1 -0
- package/transpiled/directives/auth/split.d.ts +2 -0
- package/transpiled/directives/auth/split.js +38 -0
- package/transpiled/directives/auth/split.js.map +1 -0
- package/transpiled/directives/auth/types.d.ts +31 -0
- package/transpiled/directives/auth/types.js +3 -0
- package/transpiled/directives/auth/types.js.map +1 -0
- package/transpiled/directives/cache/Control.d.ts +9 -0
- package/transpiled/directives/cache/Control.js +42 -0
- package/transpiled/directives/cache/Control.js.map +1 -0
- package/transpiled/directives/cache/Exact.d.ts +4 -0
- package/transpiled/directives/cache/Exact.js +11 -0
- package/transpiled/directives/cache/Exact.js.map +1 -0
- package/transpiled/directives/cache/Family.d.ts +12 -0
- package/transpiled/directives/cache/Family.js +26 -0
- package/transpiled/directives/cache/Family.js.map +1 -0
- package/transpiled/directives/cache/index.d.ts +2 -0
- package/transpiled/directives/cache/index.js +7 -0
- package/transpiled/directives/cache/index.js.map +1 -0
- package/transpiled/directives/cache/types.d.ts +7 -0
- package/transpiled/directives/cache/types.js +3 -0
- package/transpiled/directives/cache/types.js.map +1 -0
- package/transpiled/directives/dev/Family.d.ts +10 -0
- package/transpiled/directives/dev/Family.js +27 -0
- package/transpiled/directives/dev/Family.js.map +1 -0
- package/transpiled/directives/dev/Stub.d.ts +7 -0
- package/transpiled/directives/dev/Stub.js +14 -0
- package/transpiled/directives/dev/Stub.js.map +1 -0
- package/transpiled/directives/dev/Throw.d.ts +7 -0
- package/transpiled/directives/dev/Throw.js +14 -0
- package/transpiled/directives/dev/Throw.js.map +1 -0
- package/transpiled/directives/dev/index.d.ts +2 -0
- package/transpiled/directives/dev/index.js +7 -0
- package/transpiled/directives/dev/index.js.map +1 -0
- package/transpiled/directives/dev/types.d.ts +4 -0
- package/transpiled/directives/dev/types.js +3 -0
- package/transpiled/directives/dev/types.js.map +1 -0
- package/transpiled/directives/index.d.ts +2 -0
- package/transpiled/directives/index.js +12 -0
- package/transpiled/directives/index.js.map +1 -0
- package/transpiled/directives/octets/Context.d.ts +8 -0
- package/transpiled/directives/octets/Context.js +40 -0
- package/transpiled/directives/octets/Context.js.map +1 -0
- package/transpiled/directives/octets/Delete.d.ts +10 -0
- package/transpiled/directives/octets/Delete.js +47 -0
- package/transpiled/directives/octets/Delete.js.map +1 -0
- package/transpiled/directives/octets/Family.d.ts +12 -0
- package/transpiled/directives/octets/Family.js +49 -0
- package/transpiled/directives/octets/Family.js.map +1 -0
- package/transpiled/directives/octets/Fetch.d.ts +18 -0
- package/transpiled/directives/octets/Fetch.js +77 -0
- package/transpiled/directives/octets/Fetch.js.map +1 -0
- package/transpiled/directives/octets/List.d.ts +10 -0
- package/transpiled/directives/octets/List.js +47 -0
- package/transpiled/directives/octets/List.js.map +1 -0
- package/transpiled/directives/octets/Permute.d.ts +10 -0
- package/transpiled/directives/octets/Permute.js +51 -0
- package/transpiled/directives/octets/Permute.js.map +1 -0
- package/transpiled/directives/octets/Store.d.ts +33 -0
- package/transpiled/directives/octets/Store.js +124 -0
- package/transpiled/directives/octets/Store.js.map +1 -0
- package/transpiled/directives/octets/index.d.ts +2 -0
- package/transpiled/directives/octets/index.js +7 -0
- package/transpiled/directives/octets/index.js.map +1 -0
- package/transpiled/directives/octets/schemas.d.ts +6 -0
- package/transpiled/directives/octets/schemas.js +17 -0
- package/transpiled/directives/octets/schemas.js.map +1 -0
- package/transpiled/directives/octets/types.d.ts +9 -0
- package/transpiled/directives/octets/types.js +3 -0
- package/transpiled/directives/octets/types.js.map +1 -0
- package/transpiled/discovery.d.ts +1 -0
- package/transpiled/discovery.js +3 -0
- package/transpiled/discovery.js.map +1 -0
- package/transpiled/exceptions.d.ts +2 -0
- package/transpiled/exceptions.js +39 -0
- package/transpiled/exceptions.js.map +1 -0
- package/transpiled/index.d.ts +5 -0
- package/transpiled/index.js +12 -0
- package/transpiled/index.js.map +1 -0
- package/transpiled/manifest.d.ts +3 -0
- package/transpiled/manifest.js +61 -0
- package/transpiled/manifest.js.map +1 -0
- package/transpiled/root.d.ts +2 -0
- package/transpiled/root.js +39 -0
- package/transpiled/root.js.map +1 -0
- package/transpiled/schemas.d.ts +3 -0
- package/transpiled/schemas.js +14 -0
- package/transpiled/schemas.js.map +1 -0
- package/transpiled/tsconfig.tsbuildinfo +1 -0
package/documentation/access.md
CHANGED
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
The Authorization is implemented as a set of [RTD Directives](tree.md#directives).
|
|
15
15
|
|
|
16
|
-
Directives are executed in a predetermined order until one of them grants access to a resource.
|
|
17
|
-
none of the
|
|
18
|
-
directives grants access, then the Authorization interrupts request processing and responds with an
|
|
16
|
+
Directives are executed in a predetermined order until one of them grants access to a resource.
|
|
17
|
+
If none of the directives grants access, then the Authorization interrupts request processing and responds with an
|
|
19
18
|
authorization error.
|
|
20
19
|
|
|
21
20
|
> The Authorization directive provider is named `authorization`,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Caching
|
|
2
|
+
|
|
3
|
+
Directive family `cache` implements the
|
|
4
|
+
HTTP [Cache-Control](https://datatracker.ietf.org/doc/html/rfc2616#section-14.9).
|
|
5
|
+
|
|
6
|
+
## `cache:control`
|
|
7
|
+
|
|
8
|
+
Sets the value of the `Cache-Control` header
|
|
9
|
+
for [successful responses](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2)
|
|
10
|
+
to [safe HTTP methods](https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP).
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
/:
|
|
14
|
+
GET:
|
|
15
|
+
cache:control: max-age=60000
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Implicit modifications
|
|
19
|
+
|
|
20
|
+
In terms of security, the following implicit modifications are made to the `Cache-Control` header:
|
|
21
|
+
|
|
22
|
+
- If it contains the `public` directive without `no-cache` and the request is authenticated,
|
|
23
|
+
the `no-cache` directive is added.
|
|
24
|
+
This is done to prevent the storage of authentication tokens in shared caches.
|
|
25
|
+
- If it does not contain the `private` directive and the request is authenticated, the `private`
|
|
26
|
+
directive is added.
|
|
27
|
+
This is to prevent the storage of private data in shared caches.
|
|
28
|
+
|
|
29
|
+
## `cache:exact`
|
|
30
|
+
|
|
31
|
+
Same as `cache:control` without implicit modifications.
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
/:
|
|
35
|
+
GET:
|
|
36
|
+
cache:exact: public, max-age=60000
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## References
|
|
40
|
+
|
|
41
|
+
- HTTP 14.9.1 [What is cacheable](https://datatracker.ietf.org/doc/html/rfc2616#section-14.9.1)
|
|
42
|
+
- See also [features](/extensions/exposition/features/cache.feature)
|
package/documentation/octets.md
CHANGED
package/documentation/tree.md
CHANGED
|
@@ -124,7 +124,7 @@ Intermediate Nodes must not have Methods as they are unreachable.
|
|
|
124
124
|
|
|
125
125
|
## Directives
|
|
126
126
|
|
|
127
|
-
RTD Directives are declared using RTD node or Method keys following the `{
|
|
127
|
+
RTD Directives are declared using RTD node or Method keys following the `{family}:{directive}` pattern and can be used
|
|
128
128
|
to add or modify the behavior of request processing. Directive declarations are applied to the RTD node where they are
|
|
129
129
|
declared and to all nested nodes.
|
|
130
130
|
|
|
@@ -151,10 +151,6 @@ When it is necessary to avoid directive nesting, a Route can be declared adjacen
|
|
|
151
151
|
In this example, the Route `/posts/:user-id/:post-id/` has only the `authorization:role` directive
|
|
152
152
|
applied.
|
|
153
153
|
|
|
154
|
-
> Directives can be declared without the `{provider}:` prefix unless there are multiple directives
|
|
155
|
-
> with the same name
|
|
156
|
-
> across different providers.
|
|
157
|
-
|
|
158
154
|
Another way to avoid nesting is to declare an _isolated_ Node as follows:
|
|
159
155
|
|
|
160
156
|
```yaml
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Feature: Caching
|
|
2
|
+
|
|
3
|
+
Background:
|
|
4
|
+
Given the `identity.basic` database contains:
|
|
5
|
+
# developer:secret
|
|
6
|
+
# user:12345
|
|
7
|
+
| _id | username | password |
|
|
8
|
+
| b70a7dbca6b14a2eaac8a9eb4b2ff4db | developer | $2b$10$ZRSKkgZoGnrcTNA5w5eCcu3pxDzdTduhteVYXcp56AaNcilNkwJ.O |
|
|
9
|
+
Given the `identity.roles` database contains:
|
|
10
|
+
| _id | identity | role |
|
|
11
|
+
| 775a648d054e4ce1a65f8f17e5b51803 | b70a7dbca6b14a2eaac8a9eb4b2ff4db | developer |
|
|
12
|
+
|
|
13
|
+
Scenario: Caching successful response
|
|
14
|
+
Given the annotation:
|
|
15
|
+
"""yaml
|
|
16
|
+
/:
|
|
17
|
+
anonymous: true
|
|
18
|
+
GET:
|
|
19
|
+
cache:control: max-age=60000
|
|
20
|
+
dev:stub: hello
|
|
21
|
+
"""
|
|
22
|
+
When the following request is received:
|
|
23
|
+
"""
|
|
24
|
+
GET / HTTP/1.1
|
|
25
|
+
accept: text/plain
|
|
26
|
+
"""
|
|
27
|
+
Then the following reply is sent:
|
|
28
|
+
"""
|
|
29
|
+
200 OK
|
|
30
|
+
content-type: text/plain
|
|
31
|
+
cache-control: max-age=60000
|
|
32
|
+
|
|
33
|
+
hello
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
Scenario: Nested cache directives
|
|
37
|
+
Given the annotation:
|
|
38
|
+
"""yaml
|
|
39
|
+
/:
|
|
40
|
+
cache:control: max-age=30000
|
|
41
|
+
GET:
|
|
42
|
+
anonymous: true
|
|
43
|
+
dev:stub: hello
|
|
44
|
+
/foo:
|
|
45
|
+
auth:role: developer
|
|
46
|
+
GET:
|
|
47
|
+
dev:stub: hello
|
|
48
|
+
/bar:
|
|
49
|
+
auth:role: developer
|
|
50
|
+
cache:control: max-age=60000, public
|
|
51
|
+
GET:
|
|
52
|
+
dev:stub: hello
|
|
53
|
+
"""
|
|
54
|
+
When the following request is received:
|
|
55
|
+
"""
|
|
56
|
+
GET / HTTP/1.1
|
|
57
|
+
accept: text/plain
|
|
58
|
+
"""
|
|
59
|
+
Then the following reply is sent:
|
|
60
|
+
"""
|
|
61
|
+
200 OK
|
|
62
|
+
content-type: text/plain
|
|
63
|
+
cache-control: max-age=30000
|
|
64
|
+
|
|
65
|
+
hello
|
|
66
|
+
"""
|
|
67
|
+
When the following request is received:
|
|
68
|
+
"""
|
|
69
|
+
GET /foo/ HTTP/1.1
|
|
70
|
+
accept: text/plain
|
|
71
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
72
|
+
"""
|
|
73
|
+
Then the following reply is sent:
|
|
74
|
+
"""
|
|
75
|
+
200 OK
|
|
76
|
+
content-type: text/plain
|
|
77
|
+
cache-control: private, max-age=30000
|
|
78
|
+
|
|
79
|
+
hello
|
|
80
|
+
"""
|
|
81
|
+
When the following request is received:
|
|
82
|
+
"""
|
|
83
|
+
GET /bar/ HTTP/1.1
|
|
84
|
+
accept: text/plain
|
|
85
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
86
|
+
"""
|
|
87
|
+
Then the following reply is sent:
|
|
88
|
+
"""
|
|
89
|
+
200 OK
|
|
90
|
+
content-type: text/plain
|
|
91
|
+
cache-control: no-cache, max-age=60000, public
|
|
92
|
+
|
|
93
|
+
hello
|
|
94
|
+
"""
|
|
95
|
+
And the reply does not contain:
|
|
96
|
+
"""
|
|
97
|
+
cache-control: private, max-age=30000
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
Scenario: Cache-control is not added when request is unsafe
|
|
101
|
+
Given the annotation:
|
|
102
|
+
"""yaml
|
|
103
|
+
/:
|
|
104
|
+
anonymous: true
|
|
105
|
+
cache:control: max-age=60000
|
|
106
|
+
POST:
|
|
107
|
+
dev:stub: hello
|
|
108
|
+
"""
|
|
109
|
+
When the following request is received:
|
|
110
|
+
"""
|
|
111
|
+
POST / HTTP/1.1
|
|
112
|
+
accept: application/yaml
|
|
113
|
+
"""
|
|
114
|
+
Then the reply does not contain:
|
|
115
|
+
"""
|
|
116
|
+
cache-control: max-age=60000
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
Scenario: Cache-control is added without implicit modifications
|
|
120
|
+
Given the annotation:
|
|
121
|
+
"""yaml
|
|
122
|
+
/:
|
|
123
|
+
auth:role: developer
|
|
124
|
+
cache:exact: max-age=60000, public
|
|
125
|
+
GET:
|
|
126
|
+
dev:stub: hello
|
|
127
|
+
"""
|
|
128
|
+
When the following request is received:
|
|
129
|
+
"""
|
|
130
|
+
GET / HTTP/1.1
|
|
131
|
+
authorization: Basic ZGV2ZWxvcGVyOnNlY3JldA==
|
|
132
|
+
accept: text/plain
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
Then the following reply is sent:
|
|
136
|
+
"""
|
|
137
|
+
200 OK
|
|
138
|
+
content-type: text/plain
|
|
139
|
+
cache-control: max-age=60000, public
|
|
140
|
+
|
|
141
|
+
hello
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
Scenario: Response without caching
|
|
145
|
+
Given the annotation:
|
|
146
|
+
"""yaml
|
|
147
|
+
/:
|
|
148
|
+
anonymous: true
|
|
149
|
+
GET:
|
|
150
|
+
dev:stub: hello
|
|
151
|
+
"""
|
|
152
|
+
When the following request is received:
|
|
153
|
+
"""
|
|
154
|
+
GET / HTTP/1.1
|
|
155
|
+
accept: text/plain
|
|
156
|
+
"""
|
|
157
|
+
Then the reply does not contain:
|
|
158
|
+
"""
|
|
159
|
+
cache-control:
|
|
160
|
+
"""
|
package/features/steps/HTTP.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as http from '@toa.io/http'
|
|
|
4
4
|
import { trim } from '@toa.io/generic'
|
|
5
5
|
import { buffer } from '@toa.io/streams'
|
|
6
6
|
import { open } from '../../../storages/source/test/util'
|
|
7
|
-
import { Parameters } from './
|
|
7
|
+
import { Parameters } from './Parameters'
|
|
8
8
|
import { Gateway } from './Gateway'
|
|
9
9
|
|
|
10
10
|
@binding([Gateway, Parameters])
|
|
@@ -85,7 +85,13 @@ export class HTTP {
|
|
|
85
85
|
const { url, method, headers } = http.parse.request(head)
|
|
86
86
|
const href = new URL(url, this.origin).href
|
|
87
87
|
const body = open(filename)
|
|
88
|
-
|
|
88
|
+
|
|
89
|
+
const request = {
|
|
90
|
+
method,
|
|
91
|
+
headers,
|
|
92
|
+
body: body as unknown as ReadableStream,
|
|
93
|
+
duplex: 'half'
|
|
94
|
+
}
|
|
89
95
|
|
|
90
96
|
try {
|
|
91
97
|
const response = await fetch(href, request)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
|
-
import {
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { mkdtemp, copy } from 'fs-extra'
|
|
3
4
|
import * as yaml from '@toa.io/yaml'
|
|
4
5
|
|
|
5
6
|
export class Workspace {
|
|
@@ -10,7 +11,8 @@ export class Workspace {
|
|
|
10
11
|
const method = descriptor.value
|
|
11
12
|
|
|
12
13
|
descriptor.value = async function (this: Workspace, ...args: any[]): Promise<any> {
|
|
13
|
-
if (this.root === devnull) this.root =
|
|
14
|
+
if (this.root === devnull) this.root =
|
|
15
|
+
await mkdtemp(join(tmpdir(), Math.random().toString(36).slice(2)))
|
|
14
16
|
|
|
15
17
|
return method.apply(this, args)
|
|
16
18
|
}
|
|
@@ -23,7 +25,7 @@ export class Workspace {
|
|
|
23
25
|
const source = join(__dirname, 'components', name)
|
|
24
26
|
const target = join(this.root, name)
|
|
25
27
|
|
|
26
|
-
await
|
|
28
|
+
await copy(source, target)
|
|
27
29
|
|
|
28
30
|
if (patch !== undefined)
|
|
29
31
|
await this.patchManifest(target, patch)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0-alpha.0",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@toa.io/core": "0.
|
|
21
|
-
"@toa.io/generic": "0.
|
|
22
|
-
"@toa.io/http": "0.
|
|
23
|
-
"@toa.io/schemas": "0.
|
|
24
|
-
"@toa.io/streams": "0.
|
|
20
|
+
"@toa.io/core": "0.24.0-alpha.0",
|
|
21
|
+
"@toa.io/generic": "0.24.0-alpha.0",
|
|
22
|
+
"@toa.io/http": "0.24.0-alpha.0",
|
|
23
|
+
"@toa.io/schemas": "0.24.0-alpha.0",
|
|
24
|
+
"@toa.io/streams": "0.24.0-alpha.0",
|
|
25
25
|
"bcryptjs": "2.4.3",
|
|
26
26
|
"cors": "2.8.5",
|
|
27
27
|
"error-value": "0.3.0",
|
|
@@ -38,17 +38,20 @@
|
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"test": "jest",
|
|
41
|
-
"transpile": "rm -rf transpiled && npx tsc && npm run transpile:basic && npm run transpile:tokens",
|
|
41
|
+
"transpile": "rm -rf transpiled && npx tsc && npm run transpile:basic && npm run transpile:tokens && npm run transpile:roles",
|
|
42
42
|
"transpile:basic": "rm -rf ./components/identity.basic/operations && npx tsc -p ./components/identity.basic",
|
|
43
43
|
"transpile:tokens": "rm -rf ./components/identity.tokens/operations && npx tsc -p ./components/identity.tokens",
|
|
44
|
+
"transpile:roles": "rm -rf ./components/identity.roles/operations && npx tsc -p ./components/identity.roles",
|
|
44
45
|
"features": "npx cucumber-js"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
|
-
"@toa.io/extensions.storages": "0.
|
|
48
|
+
"@toa.io/extensions.storages": "0.24.0-alpha.0",
|
|
48
49
|
"@types/bcryptjs": "2.4.3",
|
|
49
50
|
"@types/cors": "2.8.13",
|
|
50
51
|
"@types/express": "4.17.17",
|
|
51
|
-
"@types/
|
|
52
|
+
"@types/fs-extra": "11.0.4",
|
|
53
|
+
"@types/negotiator": "0.6.1",
|
|
54
|
+
"fs-extra": "11.1.1"
|
|
52
55
|
},
|
|
53
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "09a88bdf444bc5dba66dbf0005c382f3526f0296"
|
|
54
57
|
}
|
package/readme.md
CHANGED
|
@@ -97,7 +97,9 @@ describe('result', () => {
|
|
|
97
97
|
it('should send status code 200 if the result has a value', async () => {
|
|
98
98
|
const req = createRequest()
|
|
99
99
|
|
|
100
|
-
server.attach(async (): Promise<OutgoingMessage> => ({
|
|
100
|
+
server.attach(async (): Promise<OutgoingMessage> => ({
|
|
101
|
+
headers: new Headers(), body: generate()
|
|
102
|
+
}))
|
|
101
103
|
await use(req)
|
|
102
104
|
|
|
103
105
|
expect(res.status).toHaveBeenCalledWith(200)
|
|
@@ -106,7 +108,7 @@ describe('result', () => {
|
|
|
106
108
|
it('should send status code 204 if the result has no value', async () => {
|
|
107
109
|
const req = createRequest()
|
|
108
110
|
|
|
109
|
-
server.attach(async (): Promise<OutgoingMessage> => ({ headers:
|
|
111
|
+
server.attach(async (): Promise<OutgoingMessage> => ({ headers: new Headers() }))
|
|
110
112
|
await use(req)
|
|
111
113
|
|
|
112
114
|
expect(res.status).toHaveBeenCalledWith(204)
|
|
@@ -118,7 +120,7 @@ describe('result', () => {
|
|
|
118
120
|
const buf = Buffer.from(json)
|
|
119
121
|
const req = createRequest({ headers: { accept: 'application/json' } })
|
|
120
122
|
|
|
121
|
-
server.attach(async (): Promise<OutgoingMessage> => ({ headers:
|
|
123
|
+
server.attach(async (): Promise<OutgoingMessage> => ({ headers: new Headers(), body }))
|
|
122
124
|
await use(req)
|
|
123
125
|
|
|
124
126
|
expect(res.end).toHaveBeenCalledWith(buf)
|
package/source/HTTP/Server.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
1
3
|
import express from 'express'
|
|
2
4
|
import cors from 'cors'
|
|
3
5
|
import { Connector } from '@toa.io/core'
|
|
@@ -22,8 +24,10 @@ export class Server extends Connector {
|
|
|
22
24
|
this.debug = debug
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
public static create (options
|
|
26
|
-
const properties: Properties =
|
|
27
|
+
public static create (options?: Partial<Properties>): Server {
|
|
28
|
+
const properties: Properties = options === undefined
|
|
29
|
+
? DEFAULTS
|
|
30
|
+
: { ...DEFAULTS, ...options }
|
|
27
31
|
|
|
28
32
|
const app = express()
|
|
29
33
|
|
|
@@ -35,7 +39,7 @@ export class Server extends Connector {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
public attach (process: Processing): void {
|
|
38
|
-
this.app.use((request: any, response: Response)
|
|
42
|
+
this.app.use((request: any, response: Response) => {
|
|
39
43
|
this.extend(request)
|
|
40
44
|
.then(process)
|
|
41
45
|
.then(this.success(request, response))
|
|
@@ -84,9 +88,8 @@ export class Server extends Connector {
|
|
|
84
88
|
else if (message.body === undefined) status = 204
|
|
85
89
|
else status = 200
|
|
86
90
|
|
|
87
|
-
response
|
|
88
|
-
|
|
89
|
-
.set(message.headers)
|
|
91
|
+
response.status(status)
|
|
92
|
+
message.headers?.forEach((value, key) => response.set(key, value))
|
|
90
93
|
|
|
91
94
|
if (message.body !== undefined && message.body !== null)
|
|
92
95
|
write(request, response, message)
|
|
@@ -96,7 +99,10 @@ export class Server extends Connector {
|
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
private fail (request: IncomingMessage, response: Response) {
|
|
99
|
-
return (exception: Error) => {
|
|
102
|
+
return async (exception: Error) => {
|
|
103
|
+
if (!request.complete)
|
|
104
|
+
await adam(request)
|
|
105
|
+
|
|
100
106
|
const status = exception instanceof Exception
|
|
101
107
|
? exception.status
|
|
102
108
|
: 500
|
|
@@ -113,10 +119,6 @@ export class Server extends Connector {
|
|
|
113
119
|
write(request, response, { body })
|
|
114
120
|
} else
|
|
115
121
|
response.end()
|
|
116
|
-
|
|
117
|
-
// stop accepting request
|
|
118
|
-
if (!request.complete)
|
|
119
|
-
request.destroy()
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
}
|
|
@@ -135,16 +137,26 @@ function negotiate (request: Request): Format | null {
|
|
|
135
137
|
return mediaType === undefined ? null : formats[mediaType]
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
// https://github.com/whatwg/fetch/issues/1254
|
|
141
|
+
async function adam (request: Request): Promise<void> {
|
|
142
|
+
const completed = promex()
|
|
143
|
+
const devnull = fs.createWriteStream(os.devNull)
|
|
144
|
+
|
|
145
|
+
request
|
|
146
|
+
.on('end', completed.callback)
|
|
147
|
+
.pipe(devnull)
|
|
148
|
+
|
|
149
|
+
return await completed
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const DEFAULTS = {
|
|
153
|
+
methods: new Set<string>(['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
|
154
|
+
debug: false
|
|
155
|
+
}
|
|
156
|
+
|
|
138
157
|
interface Properties {
|
|
139
158
|
methods: Set<string>
|
|
140
159
|
debug: boolean
|
|
141
160
|
}
|
|
142
161
|
|
|
143
|
-
function defaults (): Properties {
|
|
144
|
-
return {
|
|
145
|
-
methods: new Set<string>(['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']),
|
|
146
|
-
debug: false
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
162
|
export type Processing = (input: IncomingMessage) => Promise<OutgoingMessage>
|
package/source/HTTP/messages.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type IncomingHttpHeaders
|
|
1
|
+
import { type IncomingHttpHeaders } from 'node:http'
|
|
2
2
|
import { Readable } from 'node:stream'
|
|
3
3
|
import { type Request, type Response } from 'express'
|
|
4
4
|
import { buffer } from '@toa.io/streams'
|
|
@@ -53,7 +53,7 @@ function send (message: OutgoingMessage, request: IncomingMessage, response: Res
|
|
|
53
53
|
|
|
54
54
|
function stream
|
|
55
55
|
(message: OutgoingMessage, request: IncomingMessage, response: Response): void {
|
|
56
|
-
const encoded = message.headers !== undefined && 'content-type'
|
|
56
|
+
const encoded = message.headers !== undefined && message.headers.has('content-type')
|
|
57
57
|
|
|
58
58
|
if (encoded)
|
|
59
59
|
pipe(message, response)
|
|
@@ -67,7 +67,7 @@ function stream
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function pipe (message: OutgoingMessage, response: Response): void {
|
|
70
|
-
response.set(
|
|
70
|
+
message.headers?.forEach((value, key) => response.set(key, value))
|
|
71
71
|
message.body.pipe(response)
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -101,7 +101,7 @@ export interface IncomingMessage extends Request {
|
|
|
101
101
|
|
|
102
102
|
export interface OutgoingMessage {
|
|
103
103
|
status?: number
|
|
104
|
-
headers?:
|
|
104
|
+
headers?: Headers
|
|
105
105
|
body?: any
|
|
106
106
|
}
|
|
107
107
|
|
package/source/Tenant.ts
CHANGED
|
@@ -30,6 +30,11 @@ export class Tenant extends Connector {
|
|
|
30
30
|
`'${this.branch.namespace}.${this.branch.component}' has started.`)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
public override async dispose (): Promise<void> {
|
|
34
|
+
console.info('Exposition Tenant for ' +
|
|
35
|
+
`'${this.branch.namespace}.${this.branch.component}' has been stopped.`)
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
private async expose (): Promise<void> {
|
|
34
39
|
await this.broadcast.transmit('expose', this.branch)
|
|
35
40
|
}
|
|
@@ -90,9 +90,9 @@ class Authorization implements Family<Directive, Extension> {
|
|
|
90
90
|
const authorization = `Token ${token}`
|
|
91
91
|
|
|
92
92
|
if (response.headers === undefined)
|
|
93
|
-
response.headers =
|
|
93
|
+
response.headers = new Headers()
|
|
94
94
|
|
|
95
|
-
response.headers.authorization
|
|
95
|
+
response.headers.set('authorization', authorization)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
private async resolve (authorization: string | undefined): Promise<Identity | null> {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { match } from 'matchacho'
|
|
2
|
+
import type { AuthenticatedRequest, Directive } from './types'
|
|
3
|
+
|
|
4
|
+
export class Control implements Directive {
|
|
5
|
+
protected readonly value: string
|
|
6
|
+
private cache: string | null = null
|
|
7
|
+
|
|
8
|
+
public constructor (value: string) {
|
|
9
|
+
this.value = value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public set (request: AuthenticatedRequest, headers: Headers): void {
|
|
13
|
+
if (!['GET', 'HEAD', 'OPTIONS'].includes(request.method))
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
this.cache ??= this.resolve(request)
|
|
17
|
+
|
|
18
|
+
headers.set('cache-control', this.cache)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected resolve (request: AuthenticatedRequest): string {
|
|
22
|
+
if (request.identity === null)
|
|
23
|
+
return this.value
|
|
24
|
+
|
|
25
|
+
const directives = this.mask()
|
|
26
|
+
|
|
27
|
+
if ((directives & (PUBLIC | NO_CACHE)) === PUBLIC)
|
|
28
|
+
return 'no-cache, ' + this.value
|
|
29
|
+
|
|
30
|
+
if ((directives & (PUBLIC | PRIVATE)) === 0)
|
|
31
|
+
return 'private, ' + this.value
|
|
32
|
+
|
|
33
|
+
return this.value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private mask (): number {
|
|
37
|
+
const directives = this.value.match(DIRECTIVES_RX)
|
|
38
|
+
|
|
39
|
+
if (directives === null)
|
|
40
|
+
return 0
|
|
41
|
+
|
|
42
|
+
let mask = 0
|
|
43
|
+
|
|
44
|
+
for (const directive of directives)
|
|
45
|
+
mask |= match<number>(directive,
|
|
46
|
+
'private', PRIVATE,
|
|
47
|
+
'public', PUBLIC,
|
|
48
|
+
'no-cache', NO_CACHE,
|
|
49
|
+
0)
|
|
50
|
+
|
|
51
|
+
return mask
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DIRECTIVES_RX = /\b(private|public|no-cache)\b/ig
|
|
56
|
+
|
|
57
|
+
const PUBLIC = 1
|
|
58
|
+
const PRIVATE = 2
|
|
59
|
+
const NO_CACHE = 4
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Input, type Output, type Family } from '../../Directive'
|
|
2
|
+
import { Control } from './Control'
|
|
3
|
+
import { type Directive } from './types'
|
|
4
|
+
import { Exact } from './Exact'
|
|
5
|
+
import type * as http from '../../HTTP'
|
|
6
|
+
|
|
7
|
+
class Cache implements Family<Directive> {
|
|
8
|
+
public readonly name: string = 'cache'
|
|
9
|
+
public readonly mandatory: boolean = false
|
|
10
|
+
|
|
11
|
+
public create (name: string, value: any): Directive {
|
|
12
|
+
const Class = constructors[name]
|
|
13
|
+
|
|
14
|
+
if (Class === undefined)
|
|
15
|
+
throw new Error(`Directive '${name}' is not provided by the '${this.name}' family.`)
|
|
16
|
+
|
|
17
|
+
return new Class(value)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public preflight (): Output {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async settle
|
|
25
|
+
(directives: Directive[], request: Input, response: http.OutgoingMessage): Promise<void> {
|
|
26
|
+
response.headers ??= new Headers()
|
|
27
|
+
directives[0]?.set(request, response.headers)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const constructors: Record<string, new (value: any) => Directive> = {
|
|
32
|
+
control: Control,
|
|
33
|
+
exact: Exact
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export = new Cache()
|