@toa.io/extensions.exposition 1.0.0-alpha.274 → 1.0.0-alpha.276
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 +12 -0
- package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
- package/documentation/protocol.md +24 -0
- package/features/octets.cloudinary.feature +8 -2
- package/features/octets.download.feature +3 -1
- package/features/probes.feature +8 -4
- package/features/steps/Common.ts +24 -1
- package/features/steps/Gateway.ts +9 -1
- package/features/steps/Parameters.ts +4 -0
- package/features/steps/Probe.ts +34 -0
- package/package.json +3 -2
- package/readme.md +7 -13
- package/schemas/annotation.cos.yaml +11 -0
- package/source/Annotation.ts +11 -0
- package/source/HTTP/Context.ts +1 -6
- package/source/HTTP/Probe.ts +112 -0
- package/source/HTTP/Server.ts +123 -74
- package/source/HTTP/Timing.ts +1 -1
- package/source/HTTP/index.ts +2 -0
- package/source/HTTP/messages.test.ts +2 -5
- package/source/HTTP/messages.ts +38 -42
- package/source/HTTP/types.ts +39 -0
- package/source/deployment.ts +12 -4
- package/source/directives/octets/Get.ts +3 -3
- package/transpiled/Annotation.d.ts +10 -0
- package/transpiled/HTTP/Context.d.ts +1 -6
- package/transpiled/HTTP/Context.js.map +1 -1
- package/transpiled/HTTP/Probe.d.ts +36 -0
- package/transpiled/HTTP/Probe.js +117 -0
- package/transpiled/HTTP/Probe.js.map +1 -0
- package/transpiled/HTTP/Server.d.ts +11 -7
- package/transpiled/HTTP/Server.js +92 -58
- package/transpiled/HTTP/Server.js.map +1 -1
- package/transpiled/HTTP/Timing.d.ts +1 -2
- package/transpiled/HTTP/index.d.ts +2 -0
- package/transpiled/HTTP/index.js +2 -0
- package/transpiled/HTTP/index.js.map +1 -1
- package/transpiled/HTTP/messages.d.ts +8 -3
- package/transpiled/HTTP/messages.js +31 -37
- package/transpiled/HTTP/messages.js.map +1 -1
- package/transpiled/HTTP/types.d.ts +40 -0
- package/transpiled/HTTP/types.js +3 -0
- package/transpiled/HTTP/types.js.map +1 -0
- package/transpiled/deployment.js +8 -2
- package/transpiled/deployment.js.map +1 -1
- package/transpiled/directives/octets/Get.js +3 -3
- package/transpiled/directives/octets/Get.js.map +1 -1
- package/transpiled/tsconfig.tsbuildinfo +1 -1
|
@@ -69,6 +69,30 @@ See also:
|
|
|
69
69
|
- [Content-Type: multipart](https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa493937(v=exchg.140))
|
|
70
70
|
at Microsoft
|
|
71
71
|
|
|
72
|
+
## HTTP version
|
|
73
|
+
|
|
74
|
+
The gateway serves HTTP/1.1 by default. `protocol: h2c` serves cleartext HTTP/2 instead.
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
exposition:
|
|
78
|
+
protocol: h2c
|
|
79
|
+
service:
|
|
80
|
+
annotations:
|
|
81
|
+
projectcontour.io/upstream-protocol.h2c: "8000"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`h2c` requires an ingress controller that proxies cleartext HTTP/2 upstream, and the
|
|
85
|
+
annotation that controller reads to do so. Contour, Traefik and Envoy-based controllers
|
|
86
|
+
proxy it; ingress-nginx does not. A cleartext HTTP/2 server answers nothing else on the
|
|
87
|
+
same port: there is no ALPN to negotiate with, so a controller that speaks HTTP/1.1 upstream
|
|
88
|
+
reaches nothing.
|
|
89
|
+
|
|
90
|
+
Under `h2c` a request refused before its body is read is answered and then cancelled with
|
|
91
|
+
`RST_STREAM(NO_ERROR)`. The reply arrives; the upload stops where it is. A client streaming
|
|
92
|
+
a request body sees that body cancelled — in Node, `ERR_STREAM_PREMATURE_CLOSE` on the stream
|
|
93
|
+
it passed, which throws if nothing handles it. A real failure shows in the reply, or in the
|
|
94
|
+
request rejecting because no reply came. A buffered body is unaffected.
|
|
95
|
+
|
|
72
96
|
## CORS
|
|
73
97
|
|
|
74
98
|
[CORS](https://www.w3.org/TR/2020/SPSD-cors-20200602/) is supported,
|
|
@@ -144,7 +144,10 @@ Feature: Octets with Cloudinary storage
|
|
|
144
144
|
"""
|
|
145
145
|
200 OK
|
|
146
146
|
content-type: video/mp4
|
|
147
|
-
|
|
147
|
+
"""
|
|
148
|
+
And the reply does not contain:
|
|
149
|
+
"""
|
|
150
|
+
content-length
|
|
148
151
|
"""
|
|
149
152
|
|
|
150
153
|
# after a while, Cloudinary returns a content-length response
|
|
@@ -169,7 +172,10 @@ Feature: Octets with Cloudinary storage
|
|
|
169
172
|
"""
|
|
170
173
|
200 OK
|
|
171
174
|
content-type: video/mp4
|
|
172
|
-
|
|
175
|
+
"""
|
|
176
|
+
And the reply does not contain:
|
|
177
|
+
"""
|
|
178
|
+
content-length
|
|
173
179
|
"""
|
|
174
180
|
|
|
175
181
|
When the following request is received:
|
|
@@ -18,6 +18,8 @@ Feature: Download external resources
|
|
|
18
18
|
meta: true
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
+
Given the length of `https://avatars.githubusercontent.com/u/92763022?s=48&v=4` is captured as `length`
|
|
22
|
+
|
|
21
23
|
When the following request is received:
|
|
22
24
|
"""
|
|
23
25
|
POST / HTTP/1.1
|
|
@@ -43,7 +45,7 @@ Feature: Download external resources
|
|
|
43
45
|
Then the following reply is sent:
|
|
44
46
|
"""
|
|
45
47
|
200 OK
|
|
46
|
-
content-length:
|
|
48
|
+
content-length: ${{ length }}
|
|
47
49
|
content-type: image/png
|
|
48
50
|
etag: "${{ checksum }}"
|
|
49
51
|
"""
|
package/features/probes.feature
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
Feature: Probes
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# The probe is on a port of its own: kubelet speaks HTTP/1.1, and the gateway may serve h2c.
|
|
4
|
+
Scenario: The gateway answers its readiness probe
|
|
5
|
+
Given the Gateway is running
|
|
6
|
+
When the ready probe is requested
|
|
7
|
+
Then the ready probe answers 200
|
|
8
|
+
|
|
9
|
+
Scenario: The traffic port does not serve the probe
|
|
4
10
|
Given the Gateway is running
|
|
5
|
-
And after 15 seconds
|
|
6
11
|
When the following request is received:
|
|
7
12
|
"""
|
|
8
13
|
GET /.ready HTTP/1.1
|
|
9
14
|
"""
|
|
10
15
|
Then the following reply is sent:
|
|
11
16
|
"""
|
|
12
|
-
|
|
13
|
-
cache-control: no-store
|
|
17
|
+
404 Not Found
|
|
14
18
|
"""
|
package/features/steps/Common.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import * as assert from 'node:assert'
|
|
1
2
|
import { binding, given } from 'cucumber-tsflow'
|
|
2
3
|
import { timeout } from '@toa.io/generic'
|
|
4
|
+
import { Captures } from './Captures'
|
|
3
5
|
|
|
4
|
-
@binding()
|
|
6
|
+
@binding([Captures])
|
|
5
7
|
export class Common {
|
|
8
|
+
private readonly captures: Captures
|
|
9
|
+
|
|
10
|
+
public constructor (captures: Captures) {
|
|
11
|
+
this.captures = captures
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
@given('after {float} second(s)')
|
|
7
15
|
public async timeout (interval: number): Promise<void> {
|
|
8
16
|
await timeout(interval * 1000)
|
|
@@ -11,4 +19,19 @@ export class Common {
|
|
|
11
19
|
@given('the process is running')
|
|
12
20
|
public async noop (): Promise<void> {
|
|
13
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A remote resource is whatever it is today. A scenario downloading one compares the reply
|
|
25
|
+
* against what the origin actually serves, not against a literal that goes stale when it changes.
|
|
26
|
+
*/
|
|
27
|
+
@given('the length of `{}` is captured as `{word}`')
|
|
28
|
+
public async length (url: string, name: string): Promise<void> {
|
|
29
|
+
const response = await fetch(url)
|
|
30
|
+
|
|
31
|
+
assert.equal(response.status, 200, `${url} answered ${response.status}`)
|
|
32
|
+
|
|
33
|
+
const body = await response.arrayBuffer()
|
|
34
|
+
|
|
35
|
+
this.captures.set(name, String(body.byteLength))
|
|
36
|
+
}
|
|
14
37
|
}
|
|
@@ -83,6 +83,9 @@ export class Gateway {
|
|
|
83
83
|
const patch = parse(yaml) as object
|
|
84
84
|
const configuration = Object.assign({}, def, patch)
|
|
85
85
|
|
|
86
|
+
// scenario-scoped, as the secrets it may refer to are: a configuration left behind
|
|
87
|
+
// outlives the secrets it points at, and the next scenario cannot resolve them
|
|
88
|
+
this.written.push(key)
|
|
86
89
|
process.env[key] = JSON.stringify(configuration)
|
|
87
90
|
|
|
88
91
|
await Gateway.stop()
|
|
@@ -191,12 +194,17 @@ const DEFAULT_TREE = JSON.stringify({
|
|
|
191
194
|
const DEFAULT_PROPERTIES: Partial<http.Options> = {
|
|
192
195
|
authorities: {
|
|
193
196
|
nex: 'nex.toa.io'
|
|
194
|
-
}
|
|
197
|
+
},
|
|
198
|
+
// `npm run features:h2c` runs the whole suite over cleartext HTTP/2
|
|
199
|
+
protocol: process.env.TOA_EXPOSITION_PROTOCOL === 'h2c' ? 'h2c' : 'h1'
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
// the identity components boot inside the gateway, and without a variable each would wait
|
|
198
203
|
// for the values service, which these features do not run
|
|
199
204
|
const DEFAULT_CONFIGURATION: Record<string, object> = {
|
|
205
|
+
// a component declaring configuration waits for `configuration.values`, which these
|
|
206
|
+
// features do not run; the variable is the local override that stands in for it
|
|
207
|
+
'realtime.streams': {},
|
|
200
208
|
'identity.basic': {},
|
|
201
209
|
'identity.federation': {},
|
|
202
210
|
'identity.otp': {},
|
|
@@ -16,6 +16,10 @@ setDefaultTimeout(60 * 1000)
|
|
|
16
16
|
|
|
17
17
|
process.env.TOA_DEV = '1'
|
|
18
18
|
|
|
19
|
+
// the gateway answers for itself, as it does in a deployment: telemetry's probe tracks the
|
|
20
|
+
// nested composition, which connects before route discovery settles
|
|
21
|
+
process.env.TOA_TELEMETRY_READY ??= JSON.stringify(false)
|
|
22
|
+
|
|
19
23
|
// export traces to the local Tempo (`docker compose up tempo grafana`),
|
|
20
24
|
// unavailability of the endpoint is harmless
|
|
21
25
|
process.env.TOA_TELEMETRY_TRACES ??= JSON.stringify({
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { Agent, request } from 'undici'
|
|
3
|
+
import { after, binding, then, when } from 'cucumber-tsflow'
|
|
4
|
+
import { PATH, PROBE } from '../../source/HTTP'
|
|
5
|
+
|
|
6
|
+
@binding()
|
|
7
|
+
export class Probe {
|
|
8
|
+
private readonly agent = new Agent()
|
|
9
|
+
private status: number | null = null
|
|
10
|
+
private headers: Record<string, string> = {}
|
|
11
|
+
|
|
12
|
+
@when('the ready probe is requested')
|
|
13
|
+
public async request (): Promise<void> {
|
|
14
|
+
const response = await request(`http://127.0.0.1:${PROBE}${PATH}`, { dispatcher: this.agent })
|
|
15
|
+
|
|
16
|
+
this.status = response.statusCode
|
|
17
|
+
this.headers = response.headers as Record<string, string>
|
|
18
|
+
|
|
19
|
+
await response.body.dump()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@then('the ready probe answers {int}')
|
|
23
|
+
public answers (status: number): void {
|
|
24
|
+
assert.equal(this.status, status)
|
|
25
|
+
|
|
26
|
+
if (status === 200)
|
|
27
|
+
assert.equal(this.headers['cache-control'], 'no-store')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@after()
|
|
31
|
+
public async close (): Promise<void> {
|
|
32
|
+
await this.agent.close()
|
|
33
|
+
}
|
|
34
|
+
}
|
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.276",
|
|
4
4
|
"description": "Toa Exposition",
|
|
5
5
|
"author": "temich <tema.gurtovoy@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/toa-io/toa#readme",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"transpile:passkeys": "tsc -p ./components/identity.passkeys",
|
|
47
47
|
"transpile:otp": "tsc -p ./components/identity.otp",
|
|
48
48
|
"features": "cucumber-js",
|
|
49
|
+
"features:h2c": "TOA_EXPOSITION_PROTOCOL=h2c TOA_AGENT_PROTOCOL=h2c cucumber-js",
|
|
49
50
|
"features:security": "cucumber-js --tags @security",
|
|
50
51
|
"features:octets": "cucumber-js features/octets.*"
|
|
51
52
|
},
|
|
@@ -61,5 +62,5 @@
|
|
|
61
62
|
},
|
|
62
63
|
"testEnvironment": "node"
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "6f81af829c7bf8a2db5a409f3d60ea40d15583ef"
|
|
65
66
|
}
|
package/readme.md
CHANGED
|
@@ -188,21 +188,15 @@ exposition:
|
|
|
188
188
|
|
|
189
189
|
## Ready probe
|
|
190
190
|
|
|
191
|
-
The gateway serves `GET /.ready` on port `
|
|
191
|
+
The gateway serves `GET /.ready` on port `8004`, not on the port it serves traffic on, and
|
|
192
|
+
telemetry's probe is disabled in its process.
|
|
192
193
|
|
|
193
|
-
It
|
|
194
|
+
It answers `200` only after the in-process identity composition has connected, initial route
|
|
195
|
+
discovery has settled, and the HTTP server is listening. Before that it answers `503` with a
|
|
196
|
+
`retry-after`. When ready, the process also sends `process.send('ready')` for PM2 `wait_ready`.
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
3. The HTTP server is listening
|
|
198
|
-
|
|
199
|
-
Before listen, probes get connection refused; after listen but before ready, `503`.
|
|
200
|
-
When ready, the process also sends `process.send('ready')` (for PM2 `wait_ready`).
|
|
201
|
-
|
|
202
|
-
The nested identity composition shares this process. Deployment disables telemetry's port-`8001`
|
|
203
|
-
probe (`TOA_TELEMETRY_READY=false`) so only the gateway `/.ready` is used.
|
|
204
|
-
|
|
205
|
-
See also [telemetry ready probe](../telemetry/readme.md#ready-probe).
|
|
198
|
+
See also [telemetry ready probe](../telemetry/readme.md#ready-probe) and
|
|
199
|
+
[reserved ports](../../documentation/ports.md).
|
|
206
200
|
|
|
207
201
|
## See Also
|
|
208
202
|
|
|
@@ -24,8 +24,19 @@ properties:
|
|
|
24
24
|
patternProperties:
|
|
25
25
|
^.+$:
|
|
26
26
|
type: string
|
|
27
|
+
service:
|
|
28
|
+
type: object
|
|
29
|
+
properties:
|
|
30
|
+
annotations:
|
|
31
|
+
type: object
|
|
32
|
+
patternProperties:
|
|
33
|
+
^.+$:
|
|
34
|
+
type: string
|
|
27
35
|
debug:
|
|
28
36
|
type: boolean
|
|
37
|
+
protocol:
|
|
38
|
+
type: string
|
|
39
|
+
enum: [h1, h2c]
|
|
29
40
|
/: {}
|
|
30
41
|
authorities:
|
|
31
42
|
type: object
|
package/source/Annotation.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import type { Resources } from '@toa.io/operations'
|
|
2
|
+
import type { Protocol } from './HTTP'
|
|
2
3
|
|
|
3
4
|
export interface Annotation {
|
|
4
5
|
authorities: Record<string, string>
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `h2c` requires an ingress controller that proxies cleartext HTTP/2 upstream.
|
|
9
|
+
* See `documentation/protocol.md`.
|
|
10
|
+
*/
|
|
11
|
+
protocol?: Protocol
|
|
5
12
|
class?: string
|
|
6
13
|
resources?: Resources
|
|
7
14
|
annotations?: Record<string, string>
|
|
15
|
+
|
|
16
|
+
/** The Service, as opposed to the Ingress that `annotations` above describes. */
|
|
17
|
+
service?: { annotations?: Record<string, string> }
|
|
18
|
+
|
|
8
19
|
debug?: boolean
|
|
9
20
|
'/'?: object // parsed and validated by RTD.syntax.parse
|
|
10
21
|
}
|
package/source/HTTP/Context.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { Timing } from './Timing'
|
|
|
6
6
|
import { type Format, formats, types } from './formats'
|
|
7
7
|
import { read } from './messages'
|
|
8
8
|
import type { OutgoingMessage } from './messages'
|
|
9
|
-
import type
|
|
9
|
+
import type { IncomingMessage } from './types'
|
|
10
10
|
|
|
11
11
|
export class Context {
|
|
12
12
|
public readonly id: string
|
|
@@ -87,11 +87,6 @@ export class Context {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
export interface IncomingMessage extends http.IncomingMessage {
|
|
91
|
-
url: string
|
|
92
|
-
method: string
|
|
93
|
-
}
|
|
94
|
-
|
|
95
90
|
interface Pipelines {
|
|
96
91
|
body: Array<(input: unknown) => unknown>
|
|
97
92
|
response: Array<(output: OutgoingMessage) => void | Promise<void>>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as http from 'node:http'
|
|
2
|
+
import { console } from 'openspan'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The readiness probe, on a port of its own.
|
|
6
|
+
*
|
|
7
|
+
* It cannot share the port the gateway serves traffic on: kubelet speaks HTTP/1.1 only, and a
|
|
8
|
+
* cleartext HTTP/2 server answers nothing else on its port — there is no ALPN to negotiate with.
|
|
9
|
+
*
|
|
10
|
+
* It is the gateway's own readiness, not the process's. The identity composition nested in this
|
|
11
|
+
* process connects before route discovery has settled, so a probe tied to that composition would
|
|
12
|
+
* report ready while the gateway still had no routes and no open port.
|
|
13
|
+
*/
|
|
14
|
+
export class Probe {
|
|
15
|
+
private readonly server = http.createServer()
|
|
16
|
+
private readonly port: number
|
|
17
|
+
private readonly path: string
|
|
18
|
+
|
|
19
|
+
private ready = false
|
|
20
|
+
private startedAt = 0
|
|
21
|
+
private listening = false
|
|
22
|
+
private skipped = false
|
|
23
|
+
|
|
24
|
+
public constructor (port: number, path: string = PATH) {
|
|
25
|
+
this.port = port
|
|
26
|
+
this.path = path
|
|
27
|
+
|
|
28
|
+
this.server.on('request', (request, response) => this.listener(request, response))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async listen (): Promise<void> {
|
|
32
|
+
this.startedAt = Date.now()
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await new Promise<void>((resolve, reject) => {
|
|
36
|
+
this.server.once('error', reject)
|
|
37
|
+
this.server.listen(this.port, () => {
|
|
38
|
+
this.server.removeListener('error', reject)
|
|
39
|
+
resolve()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
// processes sharing a host share the port; in a pod nothing else claims it
|
|
44
|
+
if (error?.code === 'EADDRINUSE') {
|
|
45
|
+
this.skipped = true
|
|
46
|
+
|
|
47
|
+
console.warn('Ready probe port already in use, skipping', { port: this.port })
|
|
48
|
+
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
throw error
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.listening = true
|
|
56
|
+
|
|
57
|
+
// a probe answers while the process runs; it must never be the reason it keeps running
|
|
58
|
+
this.server.unref()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The gateway is listening and has routes: answer `200`. */
|
|
62
|
+
public complete (): void {
|
|
63
|
+
this.ready = true
|
|
64
|
+
|
|
65
|
+
// the IPC signal is not tied to the probe: a process that gave up the shared port is
|
|
66
|
+
// still ready, and pm2 `wait_ready` would otherwise block until `listen_timeout`
|
|
67
|
+
process.send?.('ready')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public async close (): Promise<void> {
|
|
71
|
+
this.ready = false
|
|
72
|
+
|
|
73
|
+
if (!this.listening || this.skipped)
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
this.listening = false
|
|
77
|
+
|
|
78
|
+
// keep-alive connections would otherwise hold the server handle, delaying the exit
|
|
79
|
+
this.server.closeAllConnections()
|
|
80
|
+
|
|
81
|
+
await new Promise<void>((resolve) => this.server.close(() => resolve()))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private listener (request: http.IncomingMessage, response: http.ServerResponse): void {
|
|
85
|
+
if (request.url !== this.path) {
|
|
86
|
+
response.writeHead(404).end()
|
|
87
|
+
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.ready)
|
|
92
|
+
response.writeHead(200, { 'cache-control': 'no-store' }).end()
|
|
93
|
+
else {
|
|
94
|
+
const remaining = Math.ceil((Date.now() - this.startedAt) / 1000).toString()
|
|
95
|
+
|
|
96
|
+
response.writeHead(503, { 'retry-after': remaining }).end()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Reserved for this probe. `8001` is the Telemetry readiness probe's, and `toa export` refuses a
|
|
103
|
+
* port claimed twice — `toa mono` and a local run put every service in one process.
|
|
104
|
+
*/
|
|
105
|
+
export const PROBE = 8004
|
|
106
|
+
export const PATH = '/.ready'
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The initial delay of the readiness probe. The server does not sleep for it: whoever
|
|
110
|
+
* probes is the one that waits, and doing it here as well only delayed the process twice.
|
|
111
|
+
*/
|
|
112
|
+
export const DELAY = 3 // seconds
|