@toa.io/extensions.exposition 1.0.0-alpha.267 → 1.0.0-alpha.268
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/CHANGELOG.md +15 -0
- package/components/{octets.storage → exposition.octets}/manifest.toa.yaml +2 -2
- package/components/exposition.stash/manifest.toa.yaml +12 -0
- package/components/exposition.stash/operations/count.js +7 -0
- package/components/identity.basic/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.credentials/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.federation/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.keys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.otp/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.roles/operations/tsconfig.tsbuildinfo +1 -1
- package/components/identity.tokens/operations/tsconfig.tsbuildinfo +1 -1
- package/documentation/io.md +45 -17
- package/documentation/notes/throttling.md +6 -0
- package/features/io.throttle.feature +127 -0
- package/features/map.feature +5 -5
- package/features/octets.cloudinary.feature +12 -9
- package/features/octets.download.feature +4 -3
- package/features/octets.entries.feature +5 -3
- package/features/octets.feature +4 -4
- package/features/octets.head.feature +1 -1
- package/features/octets.location.feature +1 -1
- package/features/octets.meta.feature +2 -2
- package/features/octets.workflows.feature +7 -7
- package/package.json +8 -8
- package/schemas/io/throttle.cos.yaml +9 -2
- package/source/Directive.test.ts +72 -7
- package/source/Directive.ts +15 -5
- package/source/HTTP/messages.ts +5 -1
- package/source/Interception.ts +8 -0
- package/source/RTD/Context.ts +3 -0
- package/source/RTD/Directives.ts +1 -1
- package/source/RTD/Tree.ts +17 -0
- package/source/RTD/factory.ts +11 -1
- package/source/directives/cors/CORS.ts +26 -9
- package/source/directives/io/Directive.ts +4 -2
- package/source/directives/io/IO.ts +18 -7
- package/source/directives/io/Throttle.ts +8 -6
- package/source/directives/io/lib/throttle/Configuration.test.ts +21 -0
- package/source/directives/io/lib/throttle/Configuration.ts +11 -4
- package/source/directives/io/lib/throttle/Keys.ts +12 -11
- package/source/directives/io/lib/throttle/Quotas.test.ts +207 -46
- package/source/directives/io/lib/throttle/Quotas.ts +59 -35
- package/source/directives/io/lib/throttle/components/Component.ts +2 -1
- package/source/directives/io/lib/throttle/components/Identity.ts +19 -0
- package/source/directives/io/lib/throttle/components/Route.ts +19 -0
- package/source/directives/io/lib/throttle/components/Segment.ts +19 -0
- package/source/directives/io/lib/throttle/components/index.ts +8 -2
- package/source/directives/io/schemas.test.ts +16 -0
- package/source/directives/octets/Octets.ts +1 -1
- package/source/directives/octets/workflows/Execution.ts +4 -2
- package/transpiled/Directive.d.ts +3 -1
- package/transpiled/Directive.js +12 -3
- package/transpiled/Directive.js.map +1 -1
- package/transpiled/HTTP/messages.js +4 -1
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/Interception.d.ts +2 -0
- package/transpiled/Interception.js +4 -0
- package/transpiled/Interception.js.map +1 -1
- package/transpiled/RTD/Context.d.ts +2 -0
- package/transpiled/RTD/Directives.d.ts +1 -1
- package/transpiled/RTD/Tree.js +14 -0
- package/transpiled/RTD/Tree.js.map +1 -1
- package/transpiled/RTD/factory.js +8 -1
- package/transpiled/RTD/factory.js.map +1 -1
- package/transpiled/directives/cors/CORS.d.ts +8 -1
- package/transpiled/directives/cors/CORS.js +23 -9
- package/transpiled/directives/cors/CORS.js.map +1 -1
- package/transpiled/directives/io/Directive.d.ts +4 -2
- package/transpiled/directives/io/IO.d.ts +6 -3
- package/transpiled/directives/io/IO.js +11 -5
- package/transpiled/directives/io/IO.js.map +1 -1
- package/transpiled/directives/io/Throttle.d.ts +5 -3
- package/transpiled/directives/io/Throttle.js +6 -6
- package/transpiled/directives/io/Throttle.js.map +1 -1
- package/transpiled/directives/io/lib/throttle/Configuration.d.ts +6 -2
- package/transpiled/directives/io/lib/throttle/Configuration.js +5 -2
- package/transpiled/directives/io/lib/throttle/Configuration.js.map +1 -1
- package/transpiled/directives/io/lib/throttle/Keys.d.ts +4 -3
- package/transpiled/directives/io/lib/throttle/Keys.js +7 -10
- package/transpiled/directives/io/lib/throttle/Keys.js.map +1 -1
- package/transpiled/directives/io/lib/throttle/Quotas.d.ts +27 -8
- package/transpiled/directives/io/lib/throttle/Quotas.js +47 -31
- package/transpiled/directives/io/lib/throttle/Quotas.js.map +1 -1
- package/transpiled/directives/io/lib/throttle/components/Component.d.ts +2 -1
- package/transpiled/directives/io/lib/throttle/components/Identity.d.ts +11 -0
- package/transpiled/directives/io/lib/throttle/components/Identity.js +16 -0
- package/transpiled/directives/io/lib/throttle/components/Identity.js.map +1 -0
- package/transpiled/directives/io/lib/throttle/components/Route.d.ts +12 -0
- package/transpiled/directives/io/lib/throttle/components/Route.js +20 -0
- package/transpiled/directives/io/lib/throttle/components/Route.js.map +1 -0
- package/transpiled/directives/io/lib/throttle/components/Segment.d.ts +9 -0
- package/transpiled/directives/io/lib/throttle/components/Segment.js +20 -0
- package/transpiled/directives/io/lib/throttle/components/Segment.js.map +1 -0
- package/transpiled/directives/io/lib/throttle/components/index.d.ts +1 -1
- package/transpiled/directives/io/lib/throttle/components/index.js +7 -1
- package/transpiled/directives/io/lib/throttle/components/index.js.map +1 -1
- package/transpiled/directives/octets/Octets.js +1 -1
- package/transpiled/directives/octets/Octets.js.map +1 -1
- package/transpiled/directives/octets/workflows/Execution.js +3 -1
- package/transpiled/directives/octets/workflows/Execution.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
- package/features/auth.issue.feature +0 -32
- package/source/directives/io/lib/throttle/Interval.ts +0 -40
- package/source/directives/io/lib/throttle/Quota.ts +0 -22
- package/transpiled/directives/io/lib/throttle/Interval.d.ts +0 -10
- package/transpiled/directives/io/lib/throttle/Interval.js +0 -39
- package/transpiled/directives/io/lib/throttle/Interval.js.map +0 -1
- package/transpiled/directives/io/lib/throttle/Quota.d.ts +0 -8
- package/transpiled/directives/io/lib/throttle/Quota.js +0 -22
- package/transpiled/directives/io/lib/throttle/Quota.js.map +0 -1
- /package/components/{octets.storage → exposition.octets}/operations/delete.js +0 -0
- /package/components/{octets.storage → exposition.octets}/operations/get.js +0 -0
- /package/components/{octets.storage → exposition.octets}/operations/head.js +0 -0
- /package/components/{octets.storage → exposition.octets}/operations/put.js +0 -0
package/documentation/io.md
CHANGED
|
@@ -57,9 +57,7 @@ Output restrictions are not applied to stream responses and errors.
|
|
|
57
57
|
|
|
58
58
|
## Throttling
|
|
59
59
|
|
|
60
|
-
The `io:throttle` directive
|
|
61
|
-
criteria.
|
|
62
|
-
See [algorithm description](notes/throttling.md).
|
|
60
|
+
The `io:throttle` directive limits the rate of requests meeting the specified criteria.
|
|
63
61
|
|
|
64
62
|
```yaml
|
|
65
63
|
exposition:
|
|
@@ -68,29 +66,59 @@ exposition:
|
|
|
68
66
|
key:
|
|
69
67
|
- route
|
|
70
68
|
- ip
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- header: x-real-ip
|
|
74
|
-
- status: [4xx, 5xx]
|
|
69
|
+
condition:
|
|
70
|
+
status: 404
|
|
75
71
|
requests: 500
|
|
76
72
|
interval: 30
|
|
77
73
|
cooldown: 30
|
|
78
|
-
status: 429
|
|
79
74
|
```
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
Requests are counted per `key`. Once `requests` are counted within `interval` seconds, further
|
|
77
|
+
requests carrying that key are answered `429 Too Many Requests` until `cooldown` seconds pass.
|
|
78
|
+
`interval` and `cooldown` are in seconds.
|
|
83
79
|
|
|
84
|
-
|
|
80
|
+
`requests` is a budget for the **whole group** of gateway instances, not for one of them. Counting
|
|
81
|
+
goes through Redis, so the instances converge on one number rather than each enforcing the limit
|
|
82
|
+
separately. The number they act on is a lower bound: it never claims more requests than were really
|
|
83
|
+
made, and it can lag by up to one interval. Precise per-request enforcement is not what this is for.
|
|
85
84
|
|
|
86
|
-
|
|
85
|
+
The lag is worth knowing about when `requests` is small. An instance sends what it has counted once
|
|
86
|
+
an interval, and what the group made of it only comes back an interval later — so between its send
|
|
87
|
+
and that reply, an instance is going on what it alone has counted since. A limit of `1` blocks on
|
|
88
|
+
the first request whatever happens; a limit of a handful can be overshot in the first interval of a
|
|
89
|
+
burst, before any instance has a group number to act on. Set `requests` for the rate you want to
|
|
90
|
+
stop, not for an exact ceiling.
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
An instance that cannot reach Redis keeps throttling on what it has seen itself, and keeps serving.
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
### Key components
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
What a request is counted against. Give one, or a list — a list keys on the combination, so
|
|
97
|
+
`[route, ip]` counts each address separately on each route.
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
- `ip` — the client address, read from `X-Forwarded-For` where it is set and not private,
|
|
100
|
+
and from the connection otherwise.
|
|
101
|
+
- `path` — the path the request came in on, `/users/1`.
|
|
102
|
+
- `route` — the route as declared, `/users/:id`. Every path matching the route shares one budget,
|
|
103
|
+
which `path` cannot do: keyed on `path`, walking ids is a way around the limit.
|
|
104
|
+
- `identity` — the authenticated identity. Requests carrying none share a single budget between
|
|
105
|
+
them, so pair it with `ip` if that matters.
|
|
106
|
+
- `segment: <name>` — the value bound to a named route segment, `1` for `:id` in `/users/:id`.
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
### Conditions
|
|
109
|
+
|
|
110
|
+
What is counted, as opposed to what it is counted against. A request that fails a condition is
|
|
111
|
+
served and not counted. Conditions are evaluated against the response, so they cannot decide whether
|
|
112
|
+
to block — only whether to count.
|
|
113
|
+
|
|
114
|
+
- `status: <code>` — count only responses with this status.
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
io:throttle:
|
|
118
|
+
key: ip
|
|
119
|
+
condition:
|
|
120
|
+
status: 404
|
|
121
|
+
requests: 20
|
|
122
|
+
interval: 60
|
|
123
|
+
cooldown: 600
|
|
124
|
+
```
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Decentralized Request Throttling
|
|
2
2
|
|
|
3
|
+
> **Superseded.** The design below was never implemented. `io:throttle` now counts through
|
|
4
|
+
> [comcount](https://github.com/temich/comcount), by way of the `stash` extension's `count`, which
|
|
5
|
+
> solves the same problem the same way — per-process buffers, periodically reconciled through Redis,
|
|
6
|
+
> never failing the request path — with the interval number derived from Redis' own clock instead of
|
|
7
|
+
> each node's. This note is kept for the problem statement and the forces, which have not changed.
|
|
8
|
+
|
|
3
9
|
## Problem
|
|
4
10
|
|
|
5
11
|
To prevent an unnecessary load, API requests should be throttled when made maliciously or due to an
|
|
@@ -38,3 +38,130 @@ Feature: Request throttling
|
|
|
38
38
|
"""
|
|
39
39
|
200 OK
|
|
40
40
|
"""
|
|
41
|
+
|
|
42
|
+
Scenario: Throttle requests to a route, whichever path they came in on
|
|
43
|
+
Given the `echo.beacon` is running with the following manifest:
|
|
44
|
+
"""yaml
|
|
45
|
+
exposition:
|
|
46
|
+
/:
|
|
47
|
+
/:id:
|
|
48
|
+
io:throttle:
|
|
49
|
+
key: [route]
|
|
50
|
+
requests: 1
|
|
51
|
+
interval: 5
|
|
52
|
+
cooldown: 5
|
|
53
|
+
GET:
|
|
54
|
+
dev:stub:
|
|
55
|
+
hello: true
|
|
56
|
+
"""
|
|
57
|
+
When the following request is received:
|
|
58
|
+
"""
|
|
59
|
+
GET /echo/beacon/1/ HTTP/1.1
|
|
60
|
+
"""
|
|
61
|
+
Then the following reply is sent:
|
|
62
|
+
"""
|
|
63
|
+
200 OK
|
|
64
|
+
"""
|
|
65
|
+
# a different path of the same route, and the budget is already spent —
|
|
66
|
+
# keyed on `path` this would be 200
|
|
67
|
+
When the following request is received:
|
|
68
|
+
"""
|
|
69
|
+
GET /echo/beacon/2/ HTTP/1.1
|
|
70
|
+
"""
|
|
71
|
+
Then the following reply is sent:
|
|
72
|
+
"""
|
|
73
|
+
429 Too Many Requests
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
Scenario: Throttle requests per route segment
|
|
77
|
+
Given the `echo.beacon` is running with the following manifest:
|
|
78
|
+
"""yaml
|
|
79
|
+
exposition:
|
|
80
|
+
/:
|
|
81
|
+
/:id:
|
|
82
|
+
io:throttle:
|
|
83
|
+
key:
|
|
84
|
+
- segment: id
|
|
85
|
+
requests: 1
|
|
86
|
+
interval: 5
|
|
87
|
+
cooldown: 5
|
|
88
|
+
GET:
|
|
89
|
+
dev:stub:
|
|
90
|
+
hello: true
|
|
91
|
+
"""
|
|
92
|
+
When the following request is received:
|
|
93
|
+
"""
|
|
94
|
+
GET /echo/beacon/1/ HTTP/1.1
|
|
95
|
+
"""
|
|
96
|
+
Then the following reply is sent:
|
|
97
|
+
"""
|
|
98
|
+
200 OK
|
|
99
|
+
"""
|
|
100
|
+
When the following request is received:
|
|
101
|
+
"""
|
|
102
|
+
GET /echo/beacon/1/ HTTP/1.1
|
|
103
|
+
"""
|
|
104
|
+
Then the following reply is sent:
|
|
105
|
+
"""
|
|
106
|
+
429 Too Many Requests
|
|
107
|
+
"""
|
|
108
|
+
# another segment value, another budget
|
|
109
|
+
When the following request is received:
|
|
110
|
+
"""
|
|
111
|
+
GET /echo/beacon/2/ HTTP/1.1
|
|
112
|
+
"""
|
|
113
|
+
Then the following reply is sent:
|
|
114
|
+
"""
|
|
115
|
+
200 OK
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
Scenario: Throttle requests per identity
|
|
119
|
+
Given the `echo.beacon` is running with the following manifest:
|
|
120
|
+
"""yaml
|
|
121
|
+
exposition:
|
|
122
|
+
/:
|
|
123
|
+
auth:anyone: true
|
|
124
|
+
GET:
|
|
125
|
+
# declared after `auth:` on the node, and mandatory families run in
|
|
126
|
+
# their own order regardless: `auth` has to resolve the identity
|
|
127
|
+
# before `io` can key a quota on it
|
|
128
|
+
io:throttle:
|
|
129
|
+
key: [identity]
|
|
130
|
+
requests: 1
|
|
131
|
+
interval: 5
|
|
132
|
+
cooldown: 5
|
|
133
|
+
endpoint: hello
|
|
134
|
+
"""
|
|
135
|
+
And transient identity alice
|
|
136
|
+
And transient identity bob
|
|
137
|
+
When the following request is received:
|
|
138
|
+
"""
|
|
139
|
+
GET /echo/beacon/ HTTP/1.1
|
|
140
|
+
host: nex.toa.io
|
|
141
|
+
authorization: Token ${{ alice.token }}
|
|
142
|
+
"""
|
|
143
|
+
Then the following reply is sent:
|
|
144
|
+
"""
|
|
145
|
+
200 OK
|
|
146
|
+
"""
|
|
147
|
+
When the following request is received:
|
|
148
|
+
"""
|
|
149
|
+
GET /echo/beacon/ HTTP/1.1
|
|
150
|
+
host: nex.toa.io
|
|
151
|
+
authorization: Token ${{ alice.token }}
|
|
152
|
+
"""
|
|
153
|
+
Then the following reply is sent:
|
|
154
|
+
"""
|
|
155
|
+
429 Too Many Requests
|
|
156
|
+
"""
|
|
157
|
+
# bob's own budget, which requires auth to have resolved him before io keyed on him
|
|
158
|
+
When the following request is received:
|
|
159
|
+
"""
|
|
160
|
+
GET /echo/beacon/ HTTP/1.1
|
|
161
|
+
host: nex.toa.io
|
|
162
|
+
authorization: Token ${{ bob.token }}
|
|
163
|
+
"""
|
|
164
|
+
Then the following reply is sent:
|
|
165
|
+
"""
|
|
166
|
+
200 OK
|
|
167
|
+
"""
|
package/features/map.feature
CHANGED
|
@@ -21,9 +21,9 @@ Feature: HTTP context mapping
|
|
|
21
21
|
Then the following reply is sent:
|
|
22
22
|
"""
|
|
23
23
|
200 OK
|
|
24
|
-
content-type: application/yaml
|
|
25
24
|
content-language: <result>
|
|
26
|
-
vary: accept-language, accept
|
|
25
|
+
vary: origin, accept-language, accept
|
|
26
|
+
content-type: application/yaml
|
|
27
27
|
|
|
28
28
|
Hello <result>
|
|
29
29
|
"""
|
|
@@ -84,7 +84,7 @@ Feature: HTTP context mapping
|
|
|
84
84
|
Then the following reply is sent:
|
|
85
85
|
"""
|
|
86
86
|
204 No Content
|
|
87
|
-
access-control-allow-headers: accept, authorization, content-type,
|
|
87
|
+
access-control-allow-headers: accept, accept-language, authorization, content-type, foo, if-match, if-none-match, origin
|
|
88
88
|
"""
|
|
89
89
|
|
|
90
90
|
Scenario: Mapping the value of an arbitrary header
|
|
@@ -108,8 +108,8 @@ Feature: HTTP context mapping
|
|
|
108
108
|
Then the following reply is sent:
|
|
109
109
|
"""
|
|
110
110
|
200 OK
|
|
111
|
+
vary: origin, foo, accept
|
|
111
112
|
content-type: application/yaml
|
|
112
|
-
vary: foo, accept
|
|
113
113
|
|
|
114
114
|
Hello bar
|
|
115
115
|
"""
|
|
@@ -135,8 +135,8 @@ Feature: HTTP context mapping
|
|
|
135
135
|
Then the following reply is sent:
|
|
136
136
|
"""
|
|
137
137
|
200 OK
|
|
138
|
+
vary: origin, accept
|
|
138
139
|
content-type: application/yaml
|
|
139
|
-
vary: accept
|
|
140
140
|
|
|
141
141
|
Hello nex.toa.io
|
|
142
142
|
"""
|
|
@@ -37,8 +37,8 @@ Feature: Octets with Cloudinary storage
|
|
|
37
37
|
content-type: application/yaml
|
|
38
38
|
|
|
39
39
|
id: ${{ id }}
|
|
40
|
-
type: image/png
|
|
41
40
|
size: 473831
|
|
41
|
+
type: image/png
|
|
42
42
|
"""
|
|
43
43
|
When the following request is received:
|
|
44
44
|
"""
|
|
@@ -49,18 +49,20 @@ Feature: Octets with Cloudinary storage
|
|
|
49
49
|
Then the stream equals to `lenna.png` is sent with the following headers:
|
|
50
50
|
"""
|
|
51
51
|
200 OK
|
|
52
|
-
content-type: image/png
|
|
53
|
-
content-length: 473831
|
|
54
52
|
access-control-allow-origin: https://toa.io
|
|
53
|
+
content-length: 473831
|
|
54
|
+
content-type: image/png
|
|
55
55
|
"""
|
|
56
56
|
|
|
57
|
+
# SVG has no binary signature, so the type on upload is whatever the client states;
|
|
58
|
+
# on the way back it is what Cloudinary serves
|
|
57
59
|
Scenario: Upload an svg
|
|
58
60
|
When the stream of `sample.svg` is received with the following headers:
|
|
59
61
|
"""
|
|
60
62
|
POST / HTTP/1.1
|
|
61
63
|
host: nex.toa.io
|
|
62
64
|
accept: application/yaml
|
|
63
|
-
content-type:
|
|
65
|
+
content-type: image/svg+xml
|
|
64
66
|
"""
|
|
65
67
|
Then the following reply is sent:
|
|
66
68
|
"""
|
|
@@ -75,7 +77,8 @@ Feature: Octets with Cloudinary storage
|
|
|
75
77
|
GET /${{ id }} HTTP/1.1
|
|
76
78
|
host: nex.toa.io
|
|
77
79
|
"""
|
|
78
|
-
|
|
80
|
+
# Cloudinary rewrites the SVG it stores, so only the type is asserted
|
|
81
|
+
Then the following reply is sent:
|
|
79
82
|
"""
|
|
80
83
|
200 OK
|
|
81
84
|
content-type: image/svg+xml
|
|
@@ -140,8 +143,8 @@ Feature: Octets with Cloudinary storage
|
|
|
140
143
|
Then the following reply is sent:
|
|
141
144
|
"""
|
|
142
145
|
200 OK
|
|
143
|
-
transfer-encoding: chunked
|
|
144
146
|
content-type: video/mp4
|
|
147
|
+
transfer-encoding: chunked
|
|
145
148
|
"""
|
|
146
149
|
|
|
147
150
|
# after a while, Cloudinary returns a content-length response
|
|
@@ -153,8 +156,8 @@ Feature: Octets with Cloudinary storage
|
|
|
153
156
|
Then the following reply is sent:
|
|
154
157
|
"""
|
|
155
158
|
200 OK
|
|
156
|
-
content-type: video/mp4
|
|
157
159
|
content-length: 175043
|
|
160
|
+
content-type: video/mp4
|
|
158
161
|
"""
|
|
159
162
|
|
|
160
163
|
When the following request is received:
|
|
@@ -165,8 +168,8 @@ Feature: Octets with Cloudinary storage
|
|
|
165
168
|
Then the following reply is sent:
|
|
166
169
|
"""
|
|
167
170
|
200 OK
|
|
168
|
-
transfer-encoding: chunked
|
|
169
171
|
content-type: video/mp4
|
|
172
|
+
transfer-encoding: chunked
|
|
170
173
|
"""
|
|
171
174
|
|
|
172
175
|
When the following request is received:
|
|
@@ -206,8 +209,8 @@ Feature: Octets with Cloudinary storage
|
|
|
206
209
|
Then the following reply is sent:
|
|
207
210
|
"""
|
|
208
211
|
206 Partial Content
|
|
209
|
-
content-type: video/mp4
|
|
210
212
|
content-length: 2
|
|
213
|
+
content-type: video/mp4
|
|
211
214
|
"""
|
|
212
215
|
|
|
213
216
|
# gif
|
|
@@ -32,6 +32,7 @@ Feature: Download external resources
|
|
|
32
32
|
|
|
33
33
|
id: ${{ id }}
|
|
34
34
|
type: image/png
|
|
35
|
+
checksum: ${{ checksum }}
|
|
35
36
|
"""
|
|
36
37
|
|
|
37
38
|
When the following request is received:
|
|
@@ -42,9 +43,9 @@ Feature: Download external resources
|
|
|
42
43
|
Then the following reply is sent:
|
|
43
44
|
"""
|
|
44
45
|
200 OK
|
|
45
|
-
content-type: image/png
|
|
46
46
|
content-length: 1288
|
|
47
|
-
|
|
47
|
+
content-type: image/png
|
|
48
|
+
etag: "${{ checksum }}"
|
|
48
49
|
"""
|
|
49
50
|
|
|
50
51
|
# origin is stored in the entry
|
|
@@ -185,5 +186,5 @@ Feature: Download external resources
|
|
|
185
186
|
Then the following reply is sent:
|
|
186
187
|
"""
|
|
187
188
|
204 No Content
|
|
188
|
-
access-control-allow-headers: accept, authorization, content-
|
|
189
|
+
access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match, origin
|
|
189
190
|
"""
|
|
@@ -56,6 +56,7 @@ Feature: Accessing metadata
|
|
|
56
56
|
host: nex.toa.io
|
|
57
57
|
accept: application/yaml
|
|
58
58
|
content-type: application/octet-stream
|
|
59
|
+
content-id: lenna-ascii
|
|
59
60
|
"""
|
|
60
61
|
And the stream of `lenna.png` is received with the following headers:
|
|
61
62
|
"""
|
|
@@ -63,10 +64,11 @@ Feature: Accessing metadata
|
|
|
63
64
|
host: nex.toa.io
|
|
64
65
|
accept: application/yaml
|
|
65
66
|
content-type: application/octet-stream
|
|
67
|
+
content-id: lenna-png
|
|
66
68
|
"""
|
|
67
69
|
When the following request is received:
|
|
68
70
|
"""
|
|
69
|
-
GET /
|
|
71
|
+
GET /lenna-ascii HTTP/1.1
|
|
70
72
|
host: nex.toa.io
|
|
71
73
|
accept: application/vnd.toa.octets.entry+yaml
|
|
72
74
|
"""
|
|
@@ -75,7 +77,7 @@ Feature: Accessing metadata
|
|
|
75
77
|
200 OK
|
|
76
78
|
content-type: application/yaml
|
|
77
79
|
|
|
78
|
-
id:
|
|
79
|
-
type: application/octet-stream
|
|
80
|
+
id: lenna-ascii
|
|
80
81
|
size: 8169
|
|
82
|
+
type: application/octet-stream
|
|
81
83
|
"""
|
package/features/octets.feature
CHANGED
|
@@ -56,8 +56,8 @@ Feature: Octets directive family
|
|
|
56
56
|
content-type: application/yaml
|
|
57
57
|
|
|
58
58
|
id: ${{ id }}
|
|
59
|
-
type: application/octet-stream
|
|
60
59
|
size: 8169
|
|
60
|
+
type: application/octet-stream
|
|
61
61
|
"""
|
|
62
62
|
When the following request is received:
|
|
63
63
|
"""
|
|
@@ -67,15 +67,15 @@ Feature: Octets directive family
|
|
|
67
67
|
Then the stream equals to `lenna.ascii` is sent with the following headers:
|
|
68
68
|
"""
|
|
69
69
|
200 OK
|
|
70
|
-
content-type: application/octet-stream
|
|
71
70
|
content-length: 8169
|
|
72
|
-
|
|
71
|
+
content-type: application/octet-stream
|
|
72
|
+
etag: "${{ ETAG }}"
|
|
73
73
|
"""
|
|
74
74
|
When the following request is received:
|
|
75
75
|
"""
|
|
76
76
|
GET /${{ id }} HTTP/1.1
|
|
77
77
|
host: nex.toa.io
|
|
78
|
-
if-none-match: ${{ ETAG }}
|
|
78
|
+
if-none-match: "${{ ETAG }}"
|
|
79
79
|
"""
|
|
80
80
|
Then the following reply is sent:
|
|
81
81
|
"""
|
|
@@ -85,8 +85,8 @@ Feature: Octets metadata
|
|
|
85
85
|
200 OK
|
|
86
86
|
|
|
87
87
|
id: ${{ id }}
|
|
88
|
-
type: application/octet-stream
|
|
89
88
|
size: 8169
|
|
89
|
+
type: application/octet-stream
|
|
90
90
|
checksum: 10cf16b458f759e0d617f2f3d83599ff
|
|
91
91
|
attributes:
|
|
92
92
|
foo: 'true'
|
|
@@ -111,6 +111,6 @@ Feature: Octets metadata
|
|
|
111
111
|
Then the following reply is sent:
|
|
112
112
|
"""
|
|
113
113
|
204 No Content
|
|
114
|
+
access-control-allow-headers: accept, authorization, content-attributes, content-location, content-type, if-match, if-none-match, origin
|
|
114
115
|
access-control-allow-origin: https://example.com
|
|
115
|
-
access-control-allow-headers: accept, authorization, content-type, etag, if-match, if-none-match, content-attributes
|
|
116
116
|
"""
|
|
@@ -39,14 +39,14 @@ Feature: Octets storage workflows
|
|
|
39
39
|
--cut
|
|
40
40
|
|
|
41
41
|
step: add-bar
|
|
42
|
+
status: completed
|
|
42
43
|
output:
|
|
43
44
|
bar: baz
|
|
44
|
-
status: completed
|
|
45
45
|
--cut
|
|
46
46
|
|
|
47
47
|
step: add-baz
|
|
48
48
|
status: completed
|
|
49
|
-
output:
|
|
49
|
+
output:
|
|
50
50
|
add-foo:
|
|
51
51
|
foo: bar
|
|
52
52
|
add-bar:
|
|
@@ -115,8 +115,8 @@ Feature: Octets storage workflows
|
|
|
115
115
|
|
|
116
116
|
--cut
|
|
117
117
|
id: ${{ id }}
|
|
118
|
-
type: application/octet-stream
|
|
119
118
|
size: 8169
|
|
119
|
+
type: application/octet-stream
|
|
120
120
|
--cut
|
|
121
121
|
|
|
122
122
|
step: add-foo
|
|
@@ -124,10 +124,10 @@ Feature: Octets storage workflows
|
|
|
124
124
|
--cut
|
|
125
125
|
|
|
126
126
|
step: add-bar
|
|
127
|
+
status: completed
|
|
127
128
|
error:
|
|
128
129
|
code: ERROR
|
|
129
130
|
message: Something went wrong
|
|
130
|
-
status: completed
|
|
131
131
|
--cut--
|
|
132
132
|
"""
|
|
133
133
|
|
|
@@ -281,8 +281,8 @@ Feature: Octets storage workflows
|
|
|
281
281
|
--cut
|
|
282
282
|
|
|
283
283
|
id: ${{ id }}
|
|
284
|
-
type: application/octet-stream
|
|
285
284
|
size: 8169
|
|
285
|
+
type: application/octet-stream
|
|
286
286
|
--cut
|
|
287
287
|
|
|
288
288
|
step: concat
|
|
@@ -320,8 +320,8 @@ Feature: Octets storage workflows
|
|
|
320
320
|
--cut
|
|
321
321
|
|
|
322
322
|
id: ${{ id }}
|
|
323
|
-
type: application/octet-stream
|
|
324
323
|
size: 8169
|
|
324
|
+
type: application/octet-stream
|
|
325
325
|
--cut
|
|
326
326
|
|
|
327
327
|
step: authority
|
|
@@ -361,8 +361,8 @@ Feature: Octets storage workflows
|
|
|
361
361
|
--cut
|
|
362
362
|
|
|
363
363
|
id: ${{ id }}
|
|
364
|
-
type: application/octet-stream
|
|
365
364
|
size: 8169
|
|
365
|
+
type: application/octet-stream
|
|
366
366
|
--cut
|
|
367
367
|
|
|
368
368
|
step: identity
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toa.io/extensions.exposition",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.268",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -17,20 +17,20 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@simplewebauthn/server": "13.3.
|
|
20
|
+
"@simplewebauthn/server": "13.3.3",
|
|
21
21
|
"@toa.io/core": "1.0.0-alpha.266",
|
|
22
22
|
"@toa.io/generic": "1.0.0-alpha.254",
|
|
23
23
|
"@toa.io/schemas": "1.0.0-alpha.263",
|
|
24
24
|
"bcryptjs": "3.0.3",
|
|
25
|
-
"content-type": "2.
|
|
25
|
+
"content-type": "2.1.0",
|
|
26
26
|
"error-value": "0.4.4",
|
|
27
|
-
"jose": "6.2.
|
|
28
|
-
"js-yaml": "5.
|
|
27
|
+
"jose": "6.2.10",
|
|
28
|
+
"js-yaml": "5.4.1",
|
|
29
29
|
"lru-cache": "11.5.2",
|
|
30
30
|
"matchacho": "0.6.0",
|
|
31
31
|
"minimatch": "10.2.6",
|
|
32
|
-
"msgpackr": "2.0
|
|
33
|
-
"negotiator": "1.
|
|
32
|
+
"msgpackr": "2.1.0",
|
|
33
|
+
"negotiator": "1.1.0",
|
|
34
34
|
"openspan": "1.0.0-alpha.264",
|
|
35
35
|
"paseto": "3.1.4"
|
|
36
36
|
},
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
},
|
|
63
63
|
"testEnvironment": "node"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "0c2fe0acb44653a68b12600151d4d25f46a444be"
|
|
66
66
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
definitions:
|
|
2
2
|
component: &component
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
anyOf:
|
|
4
|
+
- type: string
|
|
5
|
+
enum: [ip, path, route, identity]
|
|
6
|
+
- type: object
|
|
7
|
+
properties:
|
|
8
|
+
segment:
|
|
9
|
+
type: string
|
|
10
|
+
required: [segment]
|
|
11
|
+
additionalProperties: false
|
|
5
12
|
condition: &condition
|
|
6
13
|
type: object
|
|
7
14
|
properties:
|