1066 lines
45 KiB
Markdown
1066 lines
45 KiB
Markdown
# VD-27: gRPC-Web unary와 server stream
|
|
|
|
- 상태: Accepted design — common coordinator available, wire adapter pending
|
|
- 결정일: 2026-07-28
|
|
- provider-neutral Browser RPC V3 coordinator:
|
|
`AVAILABLE_NOT_COMPOSED`
|
|
- gRPC-Web unary/server-stream reference adapter:
|
|
`DESIGNED_NOT_IMPLEMENTED`
|
|
- product gRPC-Web composition: `NOT_SELECTED`
|
|
- client-streaming/bidirectional-streaming guarantee: `PLATFORM_LIMITED`
|
|
- 관련 결정: VD-13, VD-23, VD-24, VD-25, VD-28, VD-29, VD-30
|
|
- 상세 설계:
|
|
[Protobuf browser transport와 REST Gateway](../protobuf-browser-transport-and-rest-gateway.md)
|
|
- 상위 API 설계:
|
|
[API contract, Schema, Mapper와 Server State](../api-contract-schema-mapper-and-server-state.md)
|
|
|
|
## 배경
|
|
|
|
현재 source에는 provider-neutral operation/profile registry, application port와
|
|
unary/server-stream lifecycle coordinator가 있다. package direct dependency,
|
|
generated output, `.proto`/descriptor, Buf/protoc config, proxy route와 actual
|
|
browser test에는 gRPC-Web capability가 없다.
|
|
|
|
browser는 native gRPC transport를 직접 실행하지 않는다. gRPC-Web 또는 선택한
|
|
Connect protocol을 지원하는 BFF/Envoy/gateway가 필요하다. protobuf backend가
|
|
있다는 이유만으로 browser client에 generated SDK를 노출하지 않는다.
|
|
|
|
gRPC-Web은 unary와 server streaming에 적합할 수 있지만 browser baseline에서
|
|
client streaming과 bidirectional streaming을 일반 gRPC처럼 보장하지 않는다.
|
|
요구 의미에 따라 upload/job은 REST, one-way server event는 SSE, 진짜 duplex는
|
|
WebSocket/WebTransport 또는 별도 protocol로 다시 설계한다.
|
|
|
|
## 결정
|
|
|
|
### 1. Protocol profile을 혼용하지 않는다
|
|
|
|
선택 가능한 provider profile 예:
|
|
|
|
```text
|
|
GRPC_WEB_BINARY
|
|
GRPC_WEB_TEXT
|
|
```
|
|
|
|
`GRPC_WEB_BINARY | GRPC_WEB_TEXT`는 wire transfer/framing profile이고 Protobuf
|
|
binary와 JSON message encoding을 뜻하는 이름이 아니다. Connect protocol
|
|
`CONNECT_V1`은 이 목록에서 선택하지 않으며 VD-29의 Connect state machine과
|
|
registry가 별도로 소유한다.
|
|
|
|
gRPC-Web transport implementation도 별도 discriminant로 고정한다.
|
|
|
|
```text
|
|
GrpcWebTransportRuntimeKind
|
|
= OFFICIAL_GRPC_WEB_XHR
|
|
| CONNECT_WEB_GRPC_WEB_FETCH
|
|
| CUSTOM_FETCH_FRAMED
|
|
|
|
GrpcWebClientApiKind
|
|
= CALLBACK_STREAM
|
|
| PROMISE_UNARY
|
|
| ASYNC_ITERABLE
|
|
|
|
GrpcWebMessageEncoding
|
|
= PROTO
|
|
| JSON
|
|
```
|
|
|
|
한 operation/provider는 하나의 exact profile에 binding한다.
|
|
|
|
- gRPC-Web framing/status와 Connect envelope/status를 같은 decoder로 추측하지 않음
|
|
- content type을 보고 runtime이 vendor를 자동 선택하지 않음
|
|
- `OFFICIAL_GRPC_WEB_XHR`는 pinned official `grpc-web` runtime이 XHR, request
|
|
framing, text base64, response framing과 status 해석을 소유한다. 이를 custom
|
|
Fetch/raw-frame 경로라고 기록하지 않음
|
|
- `CONNECT_WEB_GRPC_WEB_FETCH`는 VD-29가 소유하는 pinned Connect-Web
|
|
`createGrpcWebTransport()`다. Fetch를 사용하지만 request/response gRPC-Web
|
|
binary envelope, JSON/Proto serialization, status와 trailer decode를 selected
|
|
runtime이 소유하므로 `CUSTOM_FETCH_FRAMED` raw decoder로 분류하지 않음
|
|
- `CUSTOM_FETCH_FRAMED`만 browser Fetch `ReadableStream`, `AbortController`와
|
|
adapter-owned raw frame decoder를 사용함
|
|
- official grpc-web runtime의 unary는 binary 또는 text, server-stream은
|
|
`grpcwebtext`만 허용
|
|
- official runtime의 `SERVER_STREAM + GRPC_WEB_BINARY` registry row는 거절
|
|
- Connect-Web gRPC-Web runtime은 binary envelope의 Proto unary/server-stream을
|
|
후보로 허용한다. JSON은 exact provider/runtime fixture가 있을 때만 별도
|
|
`messageEncoding=JSON` row이고 `GRPC_WEB_TEXT`는 항상 거절한다.
|
|
- Connect-Web gRPC-Web server stream은 `ASYNC_ITERABLE` API와 Chromium, Firefox,
|
|
WebKit 및 actual proxy의 incremental-read/cancel/backpressure/trailer evidence가
|
|
있을 때만 허용
|
|
- custom Fetch binary stream은 selected runtime ID, 세 browser와 actual proxy의
|
|
incremental-read/cancel/backpressure evidence가 있을 때만 별도 profile
|
|
- official production composition은 cancel handle과 terminal status event를
|
|
유지하는 `CALLBACK_STREAM`을 기본으로 한다. official `PROMISE_UNARY`는
|
|
`.on(...)` metadata/status event와 direct call handle을 제공하지 않는다. pinned
|
|
runtime/codegen이 `PromiseCallOptions.signal`을 지원하면 cancellation은 그
|
|
`AbortSignal`로 충족할 수 있지만, required status/metadata event는 충족하지
|
|
못하므로 registry에서 거절
|
|
- text mode의 base64 overhead를 message/total budget에 반영
|
|
|
|
이 ADR의 state machine은 gRPC-Web을 기준으로 한다. Connect-Web runtime이
|
|
`CONNECT_V1`을 선택하는 경우는 이 ADR의 runtime row가 아니라 VD-29의 별도
|
|
protocol/profile/fixture를 사용하고 gRPC-Web frame을 재사용하지 않는다.
|
|
|
|
### 2. Application 경계
|
|
|
|
```text
|
|
feature gateway
|
|
-> registered semantic operation
|
|
-> GrpcWebOperationDefinition
|
|
-> generated request mapping
|
|
-> selected gRPC-Web transport runtime
|
|
-> runtime-owned OR custom adapter-owned frame/status/trailer validation
|
|
-> selected runtime/generated response decode
|
|
-> semantic runtime validation
|
|
-> boundary mapper
|
|
-> application result or mapped stream event
|
|
```
|
|
|
|
application/domain/presentation/query cache에 노출하지 않는다.
|
|
|
|
- generated service client
|
|
- generated protobuf message
|
|
- service/method string
|
|
- endpoint/metadata
|
|
- raw frame/trailer/status details
|
|
- protobuf bigint/bytes representation
|
|
|
|
### 3. Operation definition
|
|
|
|
```text
|
|
GrpcWebOperationV1
|
|
protocol = GRPC_WEB_V1
|
|
semanticOperationId
|
|
providerId
|
|
clientRuntimeId
|
|
transportRuntimeKind
|
|
clientApiKind
|
|
grpcWebWireSpecRevision
|
|
transportProfile
|
|
messageEncoding = PROTO | JSON
|
|
fullyQualifiedService
|
|
method
|
|
rpcKind = UNARY | SERVER_STREAM
|
|
requestMessageId
|
|
responseMessageId
|
|
descriptorArtifactId
|
|
descriptorDigest
|
|
requestSemanticSchemaId
|
|
responseSemanticSchemaId
|
|
mapperId
|
|
statusProfileId
|
|
responseHttpStatusProfileId
|
|
authProfileId
|
|
csrfProfileId
|
|
replayPolicy
|
|
deadlineProfileId
|
|
retryProfileId
|
|
retryOwner = FRONTEND_ADAPTER | ENVOY | NONE
|
|
proxyProfileId
|
|
headerBudgetProfileId
|
|
serverStateProfileId | null
|
|
maxRequestMessageBytes
|
|
maxResponseMessageBytes
|
|
maxResponseMessages
|
|
maxTotalResponseBytes
|
|
idleDeadlineMs
|
|
totalDeadlineMs
|
|
compressionProfile
|
|
owner
|
|
```
|
|
|
|
registry가 거절:
|
|
|
|
- client/bidi method
|
|
- selected `(rpcKind, transportProfile, clientRuntimeId, browser/proxy profile)`이
|
|
capability matrix에 없음
|
|
- operation/provider의 runtime kind 또는 client API kind가 다름
|
|
- official runtime인데 custom Fetch/raw-frame/AbortSignal capability를 요구하거나
|
|
custom runtime인데 adapter-owned framing/status decoder가 없음
|
|
- official runtime인데 `messageEncoding=JSON`이거나 Connect-Web runtime인데
|
|
`transportProfile=GRPC_WEB_TEXT`임
|
|
- Connect-Web JSON row에 exact provider media/status/generated-code fixture가 없거나
|
|
Connect-Web server stream이 `clientApiKind=ASYNC_ITERABLE`이 아님
|
|
- official `PROMISE_UNARY`인데 terminal metadata/status event가 operation
|
|
requirement이거나 pinned runtime/codegen에 `PromiseCallOptions.signal`
|
|
capability가 없는데 explicit cancellation이 requirement임
|
|
- unknown service/method/message/descriptor
|
|
- descriptor digest mismatch
|
|
- unary인데 response max count가 1이 아님
|
|
- server stream인데 queue/idle/total/message/count budget 없음
|
|
- non-replayable command에 retry
|
|
- frontend/Envoy/backend 중 retry owner가 둘 이상이거나 selected owner의 exact
|
|
attempt/backoff/deadline policy가 없음
|
|
- executable proxy/header budget profile이 없음
|
|
- long-running stream에 ordinary Query profile
|
|
- caller-provided endpoint/metadata
|
|
|
|
### 4. Protobuf source와 codegen
|
|
|
|
```text
|
|
authenticated proto/Buf module
|
|
-> immutable source/descriptor set
|
|
-> provenance + digest
|
|
-> lint
|
|
-> breaking comparison against production baseline
|
|
-> pinned deterministic generation
|
|
-> adapter-private client/messages
|
|
-> semantic validators + mapper
|
|
```
|
|
|
|
governance:
|
|
|
|
- field number 재사용 금지
|
|
- removed field number/name reserve
|
|
- package/service/method full name 안정성
|
|
- proto3 presence/optional 의미
|
|
- enum zero/unknown numeric value
|
|
- oneof absence/unknown case
|
|
- map ordering을 application identity로 사용하지 않음
|
|
- bytes/message nesting/repeated count ceiling
|
|
- Timestamp/Duration valid range/nanos
|
|
- int64/uint64 projection policy
|
|
- unknown fields library behavior와 mapper projection
|
|
|
|
generated decoder success는 semantic domain proof가 아니다. VD-24 runtime semantic
|
|
validator가 range, presence, enum/oneof와 collection budget을 확인한다.
|
|
|
|
CI:
|
|
|
|
- pinned Buf/protoc/plugin/runtime/Node versions
|
|
- source/descriptor/generated digest
|
|
- clean regenerate diff 0
|
|
- Buf lint/breaking
|
|
- generated import boundary
|
|
- N/N-1 descriptor fixture
|
|
- generated code dependency/SBOM/removal
|
|
|
|
### 5. Provider endpoint와 proxy
|
|
|
|
```text
|
|
GrpcWebProviderProfile
|
|
providerId
|
|
fixedHttpsOrigin
|
|
pathPrefix
|
|
clientRuntimeId
|
|
transportRuntimeKind
|
|
clientApiKind
|
|
grpcWebWireSpecRevision
|
|
transportProfile
|
|
messageEncoding
|
|
browserCapabilityProfileId
|
|
proxyCapabilityProfileId
|
|
proxyProfileId
|
|
headerBudgetProfileId
|
|
responseHttpStatusProfileId
|
|
credentialsMode
|
|
corsProfileId
|
|
referrerPolicy
|
|
redirect = ERROR
|
|
maximumEnvelopeBytes
|
|
```
|
|
|
|
operation과 provider의 runtime kind/client API/runtime version/wire spec/transport/
|
|
message encoding/proxy/header tuple은 exact match해야 한다. moving `latest` wire
|
|
spec이나 library auto-detection을 compatibility proof로 사용하지 않는다.
|
|
|
|
```text
|
|
GrpcWebHttpStatusProfile
|
|
allowedGrpcMediaHttpStatuses[]
|
|
missingGrpcStatusMap = CANONICAL_HTTP_TO_GRPC_V1
|
|
emitSyntheticGrpcStatusWireHeader = false
|
|
httpGrpcConsistencyFixtureId
|
|
```
|
|
|
|
status/media/source matrix:
|
|
|
|
- valid `grpc-status`가 있으면 HTTP status와 무관하게 그 값이 authoritative다.
|
|
accepted gRPC-Web media와 terminal source가 있으면 bounded decode한 뒤 selected
|
|
provider의 HTTP↔gRPC consistency fixture를 별도로 검사한다.
|
|
- `grpc-status`가 없으면 client는 wire response를 변조하지 않고 아래 canonical
|
|
HTTP→gRPC mapping으로 **internal normalized status**를 만든다. synthetic response
|
|
header를 생성하거나 이 표를 server-side gRPC→HTTP 역매핑으로 사용하지 않는다.
|
|
|
|
| HTTP status | missing `grpc-status`의 normalized gRPC status |
|
|
| --- | --- |
|
|
| `400` | `INTERNAL` |
|
|
| `401` | `UNAUTHENTICATED` |
|
|
| `403` | `PERMISSION_DENIED` |
|
|
| `404` | `UNIMPLEMENTED` |
|
|
| `429`, `502`, `503`, `504` | `UNAVAILABLE` |
|
|
| 그 밖의 모든 status | `UNKNOWN` |
|
|
|
|
- invalid/missing gRPC media, missing terminal source와 malformed body는 위 normalized
|
|
status와 별개로 `protocolViolation=true`인 transport/contract failure로 기록한다.
|
|
HTTP 2xx라도 missing `grpc-status`를 success로 만들지 않는다.
|
|
- `429`를 domain `RATE_LIMITED`로 곧바로 해석하지 않는다. canonical
|
|
`UNAVAILABLE` normalization 뒤 selected application status mapper가 별도 증거가
|
|
있을 때만 rate-limit 의미를 부여한다.
|
|
- operation/provider `responseHttpStatusProfileId`가 다르면 composition을
|
|
거절한다.
|
|
|
|
same-origin BFF/Envoy gateway를 권장한다.
|
|
|
|
```text
|
|
GrpcWebCorsProfile
|
|
topology = SAME_ORIGIN | CROSS_ORIGIN
|
|
allowedOrigins[]
|
|
allowCredentials
|
|
allowMethods = [POST, OPTIONS]
|
|
allowHeaders[]
|
|
exposeHeaders[]
|
|
forwardNotMatchingPreflights = false
|
|
clientSuppressCorsPreflight = false
|
|
maxAgeSeconds
|
|
```
|
|
|
|
cross-origin profile과 actual provider evidence:
|
|
|
|
- exact allow-origin, wildcard credential 금지
|
|
- `POST`, `OPTIONS` 외 method 금지
|
|
- 최소 `content-type`, `x-grpc-web`, `x-user-agent`, selected deadline의
|
|
`grpc-timeout`과 exact auth/CSRF/idempotency/trace header만 allowlist
|
|
- trailers-only response header를 위해 `grpc-status`, `grpc-message`, rich details를
|
|
선택한 profile은 `grpc-status-details-bin`까지 expose
|
|
- official runtime의 preflight 억제/query-parameter metadata 광고를 사용하지
|
|
않는다. credential/metadata가 URL, browser history, access log로 이동하지 않게
|
|
client option `suppressCorsPreflight=false`로 고정
|
|
- Envoy CORS filter는 non-matching preflight를 upstream으로 전달하지 않고
|
|
fail-closed한다.
|
|
- TLS
|
|
- redirect 없음
|
|
- stream buffering 없음
|
|
|
|
same-origin profile도 CSRF/Fetch Metadata/Origin 검증을 생략한다는 뜻이 아니다.
|
|
|
|
```text
|
|
GrpcWebHeaderBudgetProfile
|
|
maxRequestHeaderListBytes
|
|
maxResponseHeaderListBytes
|
|
maxTrailerListBytes
|
|
maxHeaderFieldCount
|
|
maxBinaryMetadataDecodedBytes
|
|
```
|
|
|
|
- 각 값은 양의 유한값이며 operation/provider/proxy에서 exact match한다. native
|
|
gRPC protocol이 제안하는 header-list별 8 KiB를 initial ceiling으로 검토하되
|
|
제품이 다른 값을 택하면 actual runtime/proxy fixture와 함께 고정한다.
|
|
- gateway는 browser/runtime allocation 전에 request/response header hard cap을
|
|
집행하고 custom trailer parser는 field count, encoded/decoded bytes cap을
|
|
집행한다. official runtime은 raw frame 이전 cap을 application code가 소유한다고
|
|
주장하지 않는다.
|
|
- registered `-bin` metadata만 허용한다. padded/unpadded base64를 처리하고,
|
|
transport가 duplicate binary header를 comma-join했다면 각 value를 먼저 분리한
|
|
뒤 개별 decode한다. unknown metadata는 application에 projection하지 않는다.
|
|
|
|
```text
|
|
GrpcWebProxyProfile
|
|
gatewayKind = ENVOY_GRPC_WEB | SELECTED_BFF
|
|
gatewayVersion
|
|
immutableConfigDigest
|
|
downstreamTlsProfileId
|
|
upstreamProtocol = HTTP2
|
|
filterOrder = [grpc_web, cors, router]
|
|
routeTimeoutMs = DISABLED | positive-integer
|
|
hcmStreamIdleTimeoutMs
|
|
routeIdleTimeoutMs
|
|
maxStreamDurationMs
|
|
grpcTimeoutHeaderMaxMs
|
|
grpcTimeoutHeaderOffsetMs
|
|
buffering = DISABLED
|
|
flushProfileId
|
|
localReplyProfileId
|
|
retryOwner
|
|
headerBudgetProfileId
|
|
```
|
|
|
|
Envoy profile은 개념 필드로만 끝내지 않고 pinned v3 config artifact의 다음 위치로
|
|
render한다.
|
|
|
|
| 요구 | Envoy v3 config 위치 |
|
|
| --- | --- |
|
|
| bridge/filter order | HCM `http_filters`의 `envoy.filters.http.grpc_web`, `envoy.filters.http.cors`, `envoy.filters.http.router` 순서 |
|
|
| exact operation route | `Route.match`의 registered service/method path와 `RouteAction.cluster` |
|
|
| upstream HTTP/2 | cluster `typed_extension_protocol_options`의 `envoy.extensions.upstreams.http.v3.HttpProtocolOptions.explicit_http_config.http2_protocol_options` |
|
|
| total/idle/max duration | `RouteAction.timeout`, `RouteAction.idle_timeout`, `RouteAction.max_stream_duration` 및 HCM `stream_idle_timeout` |
|
|
| deadline clamp | `RouteAction.max_stream_duration.grpc_timeout_header_max`와 `grpc_timeout_header_offset` |
|
|
| CORS | route/virtual-host `typed_per_filter_config[envoy.filters.http.cors]`; deprecated `cors` shortcut을 새 profile에 사용하지 않음 |
|
|
| buffer | `envoy.filters.http.buffer`를 설치하지 않거나 selected route의 `BufferPerRoute`를 disabled |
|
|
| retry | `RouteAction.retry_policy`; owner가 Envoy가 아니면 absent, hedge policy absent |
|
|
|
|
Envoy grpc_web filter 또는 selected gateway가:
|
|
|
|
- pinned version/config digest와 exact route/service/method allowlist
|
|
- Envoy일 때 `grpc_web -> cors -> router` filter 순서와 upstream HTTP/2
|
|
- exact content type/framing, local reply와 missing-status fixture
|
|
- message/body/header/deadline cap
|
|
- terminal trailers/status preservation
|
|
- server stream flush/backpressure
|
|
- auth/CSRF/Origin
|
|
- retry owner와 upstream status mapping
|
|
|
|
을 staging에서 증명해야 한다. Envoy route timeout 기본값에 기대지 않는다.
|
|
server stream route는 `timeout: 0s` 또는 application total deadline보다 긴 exact
|
|
finite value를 선택하고, 별도 finite stream-idle/max-stream-duration cap을 둔다.
|
|
`grpc_timeout_header_max`와 offset은 proxy가 browser deadline을 늘리지 않고
|
|
필요하면 browser보다 먼저 종료하도록 맞춘다. route/HCM idle과 buffer/flush도
|
|
명시하며 selected route에서 complete-body buffer filter를 비활성화한다. fake
|
|
fetch나 15초 미만 fixture만으로 provider conformant가 아니다.
|
|
|
|
### 6. Request
|
|
|
|
```text
|
|
validated application input
|
|
-> generated request message
|
|
-> semantic validation
|
|
-> bounded selected Proto/JSON encode
|
|
-> transport-owned metadata/auth/CSRF
|
|
-> final invariant
|
|
-> selected transport runtime
|
|
-> OFFICIAL_GRPC_WEB_XHR: runtime-owned frame/base64 + XHR
|
|
-> CONNECT_WEB_GRPC_WEB_FETCH: runtime-owned frame/status + Fetch
|
|
-> CUSTOM_FETCH_FRAMED: adapter-owned frame/base64 + Fetch
|
|
```
|
|
|
|
- service/method path는 registry가 만든다.
|
|
- wire method는 `POST`로 고정한다.
|
|
- binary Proto baseline은 `Content-Type: application/grpc-web+proto`,
|
|
Connect-Web JSON provider row는 `Content-Type: application/grpc-web+json`, text
|
|
baseline은
|
|
`Content-Type: application/grpc-web-text`와
|
|
`Accept: application/grpc-web-text`를 사용한다. exact selected media type은
|
|
transport profile에 고정하고 response media를 검증한다.
|
|
- `X-Grpc-Web: 1`과 `X-User-Agent`를 selected runtime이 소유한다. browser
|
|
`User-Agent`를 대신 설정하거나 caller가 이 fixed header를 override하지 못한다.
|
|
- caller가 endpoint, metadata, `grpc-timeout` 또는 transport header를 제출하지
|
|
않는다.
|
|
- request message byte cap을 selected Proto/JSON encode 전후 검사한다.
|
|
- official runtime은 request framing/base64를 소유하고 generated client에 validated
|
|
message만 넘긴다. Connect-Web runtime은 selected Proto/JSON serialization과
|
|
binary envelope를 소유하고 VD-29의 bounded `Transport`/Fetch evidence를
|
|
충족한다. custom runtime만 encoded Protobuf를 bounded gRPC-Web data frame으로
|
|
직접 감싼다.
|
|
- official grpc-web baseline의 message compression flag는 disabled이며 set bit를
|
|
거절한다. unary full-body HTTP `Content-Encoding`은 별도 provider profile과
|
|
decoded-body ceiling으로 다룬다. custom message compression은 exact
|
|
runtime/proxy fixture 없이는 승인하지 않는다.
|
|
- credential owner가 URL/path/method/body digest를 바꿀 수 없다.
|
|
- auth-required operation은 unavailable/unauthenticated에서 fetch 0회다.
|
|
- cookie profile은 POST/custom content type만으로 CSRF가 해결됐다고 보지 않고
|
|
exact Origin/Fetch Metadata/CSRF proof를 요구한다.
|
|
|
|
### 7. Frame decoder
|
|
|
|
raw frame pipeline은 `CUSTOM_FETCH_FRAMED`에만 적용한다. gRPC-Web binary response를
|
|
whole `Uint8Array`로 무제한 materialize하지 않는다.
|
|
|
|
```text
|
|
Fetch ReadableStream
|
|
-> bounded incremental frame prefix
|
|
-> validate flag/type/compression
|
|
-> validate declared frame length
|
|
-> bounded exact frame payload
|
|
-> data message OR trailer block
|
|
```
|
|
|
|
`OFFICIAL_GRPC_WEB_XHR`에서는 pinned official runtime이 XHR body, binary/text
|
|
framing, base64와 terminal status parsing을 소유한다. adapter가 이미 decoded된
|
|
message를 raw frame처럼 다시 parse하지 않는다. wire/header/message hard cap은
|
|
gateway/upstream이 allocation 전에 집행하고, adapter는 runtime output의
|
|
message-count/semantic/collection/total budget을 다시 검사한다. official runtime이
|
|
내부에서 거절해야 하는 malformed framing/status는 pinned-version conformance
|
|
fixture로 증명한다.
|
|
|
|
`CONNECT_WEB_GRPC_WEB_FETCH`도 selected Connect-Web `Transport`가 Fetch, binary
|
|
envelope, Proto/JSON decode와 terminal status를 소유한다. stock transport 앞의
|
|
bounded Fetch wrapper 또는 verified upstream hook이 response byte ceiling을
|
|
집행할 수는 있지만 gRPC-Web frame을 별도 custom decoder로 다시 추측하지 않는다.
|
|
required pre-decode cap/terminal invariant hook과 malformed corpus를 exact package
|
|
version에서 증명하지 못하면 VD-29와 이 ADR 모두 `DESIGNED_NOT_IMPLEMENTED`를
|
|
유지한다.
|
|
|
|
검증:
|
|
|
|
- truncated prefix/payload
|
|
- unknown/reserved flag
|
|
- negative/overflow/over-limit length
|
|
- disallowed compression flag
|
|
- decompressed message cap
|
|
- trailer before/after allowed data count
|
|
- duplicate body trailer 또는 body/header terminal source 충돌
|
|
- data after terminal trailer
|
|
- extra bytes after terminal
|
|
- total message/frame/byte ceiling
|
|
- idle and total deadline
|
|
|
|
reader는 failure/cancel/limit/terminal 뒤 cancel/release한다.
|
|
|
|
text mode:
|
|
|
|
- `CONNECT_WEB_GRPC_WEB_FETCH`는 이 branch에 들어오지 않고 build/boot 전에
|
|
거절한다.
|
|
- response 전체가 단일 valid base64 entity라고 가정하지 않는다. runtime flush로
|
|
padding이 중간에 나타난 뒤 다음 base64 entity가 이어질 수 있으므로
|
|
**concatenated padded/unpadded base64 entities**를 수용한다.
|
|
- custom runtime은 stateful incremental decoder를 사용한다. padding은 해당 entity를
|
|
끝내고 decoder quantum을 reset하지만 padding 또는 새 entity가 browser chunk,
|
|
gRPC-Web frame과 일치한다고 추정하지 않는다.
|
|
- invalid alphabet/padding/whitespace policy
|
|
- encoded와 decoded byte cap
|
|
- browser chunk가 base64 quantum/frame prefix와 일치한다고 가정하지 않음
|
|
- decoded byte stream만 공통 bounded frame decoder에 전달하고
|
|
`Content-Transfer-Encoding`에 의존하지 않음
|
|
- binary와 별도 conformance fixture
|
|
|
|
### 8. Trailer와 status
|
|
|
|
success는 HTTP success만으로 결정하지 않는다.
|
|
|
|
```text
|
|
HTTP admission
|
|
+ valid gRPC-Web frame sequence
|
|
+ exactly one TerminalStatusSource
|
|
+ exactly one valid grpc-status
|
|
+ grpc-status = 0
|
|
|
|
TerminalStatusSource
|
|
= BODY_TRAILER_FRAME
|
|
| ZERO_BODY_TRAILERS_ONLY_RESPONSE_HEADERS
|
|
```
|
|
|
|
custom runtime은 두 source를 raw parser에서 직접 판정한다. official runtime은
|
|
pinned runtime이 같은 protocol rule을 집행하고 `CALLBACK_STREAM`의 normalized
|
|
status event를 adapter가 소비한다. Connect-Web runtime은 VD-29의 bounded
|
|
`Transport`와 generated Promise/`AsyncIterable` error/terminal contract로 같은
|
|
gRPC-Web authority를 보존한다. 두 runtime 모두 raw source를 application code가
|
|
직접 관찰했다고 거짓으로 기록하지 않고 malformed/missing/duplicate source fixture를
|
|
selected runtime conformance에서 통과시킨다. missing `grpc-status`로 section 5의
|
|
HTTP mapping을 실행한 결과는 valid terminal source나 success가 아니다.
|
|
|
|
`BODY_TRAILER_FRAME`과 response-header `grpc-status`가 동시에 있거나 값이
|
|
충돌하면 contract failure다. trailers-only header source는 body/data frame이
|
|
0개일 때만 허용한다. 이후 unary/server-stream cardinality를 별도로 판정한다.
|
|
|
|
trailer/status validator:
|
|
|
|
- bounded ASCII/header parser
|
|
- body trailer name은 lowercase만 허용하고 모든 source는 case-normalized
|
|
duplicate detection
|
|
- allowed name/value syntax와 `GrpcWebHeaderBudgetProfile`의 bounded field
|
|
count/encoded/decoded bytes
|
|
- `grpc-status` exactly once, canonical decimal `0..16`
|
|
- percent-decoded grpc-message byte cap
|
|
- malformed `grpc-message`는 raw 노출 없이 safe replacement/omission하되 valid
|
|
authoritative status 자체를 잃지 않음
|
|
- unknown/raw trailer 폐기
|
|
- `grpc-status-details-bin`은 count/decoded byte cap과 allowlisted protobuf
|
|
`Any` type만 허용
|
|
- decoded outer `google.rpc.Status.code`가 `grpc-status`와 exact match
|
|
- rich details는 non-OK status에서만 허용
|
|
|
|
raw `grpc-message`, status details와 metadata를 application/log에 보내지 않는다.
|
|
|
|
### 9. Unary state machine
|
|
|
|
순서가 있는 판정:
|
|
|
|
1. network/CORS/final URL 실패처럼 HTTP response 자체가 없으면 exact transport
|
|
failure mapping이며 gRPC status가 있다고 가장하지 않는다.
|
|
2. invalid/unaccepted gRPC-Web media, malformed local reply 또는 redirect는 body를
|
|
gRPC frame으로 추측하지 않고 contract/transport failure로 닫는다. valid exposed
|
|
status가 없으면 section 5의 canonical HTTP→gRPC normalization도 함께 기록한다.
|
|
3. accepted gRPC-Web media는 profile이 허용한 HTTP status에서 frame과 terminal
|
|
source를 bounded decode한다. invalid/truncated/oversize frame은
|
|
contract failure.
|
|
4. accepted media인데 valid terminal source 또는 `grpc-status`가 없으면 canonical
|
|
HTTP→gRPC normalized status와 `protocolViolation=true`로 실패한다. 모든 2xx와
|
|
mapping 표에 없는 status는 `UNKNOWN`이며 cache/data success는 0이다.
|
|
5. HTTP status와 terminal source/status matrix mismatch:
|
|
contract failure.
|
|
6. non-OK terminal status:
|
|
status mapping, data/cache success 0.
|
|
7. OK status + data message 0개:
|
|
contract failure. `google.protobuf.Empty`도 payload length가 0인 data frame
|
|
**하나**다.
|
|
8. OK status + data message 정확히 1개:
|
|
generated decode → semantic schema → mapper → generation fence → success.
|
|
9. OK status + data message 2개 이상:
|
|
contract failure.
|
|
|
|
trailers-only non-OK response는 safe failure가 될 수 있다. trailers-only OK +
|
|
unary zero message는 contract failure다. non-OK에서 받은 data message를 partial
|
|
success로 사용하지 않는다. server-stream의 zero-event + OK는 stream profile이
|
|
허용할 수 있다.
|
|
|
|
### 10. Status mapping
|
|
|
|
operation status profile이 exact allowlist/mapping을 소유한다.
|
|
|
|
| gRPC status | 기본 safe mapping |
|
|
| --- | --- |
|
|
| `OK` | success state machine 계속 |
|
|
| `CANCELLED` | local signal이면 `REQUEST_ABORTED`, 아니면 provider failure |
|
|
| `DEADLINE_EXCEEDED` | `REQUEST_TIMEOUT` |
|
|
| `UNAUTHENTICATED` | `AUTH_REQUIRED` |
|
|
| `PERMISSION_DENIED` | `FORBIDDEN` |
|
|
| `NOT_FOUND` | `NOT_FOUND` 또는 existence-hiding |
|
|
| `ALREADY_EXISTS` | `CONFLICT` |
|
|
| `ABORTED` | `CONFLICT`/optimistic concurrency |
|
|
| `FAILED_PRECONDITION` | typed precondition/contract profile |
|
|
| `INVALID_ARGUMENT`, `OUT_OF_RANGE` | `VALIDATION_REJECTED` |
|
|
| `RESOURCE_EXHAUSTED` | `RATE_LIMITED` 또는 bounded resource failure |
|
|
| `UNAVAILABLE` | retry-eligible server failure |
|
|
| `UNIMPLEMENTED` | provider/contract incompatible |
|
|
| `INTERNAL`, `UNKNOWN` | safe server failure |
|
|
| `DATA_LOSS` | integrity/contract failure, retry default off |
|
|
|
|
validation details는 allowlisted `google.rpc.BadRequest` 같은 registered message
|
|
type과 bounded field path/code만 projection한다. arbitrary `Any` type URL/message를
|
|
decode하거나 application에 전달하지 않는다.
|
|
|
|
### 11. Deadline과 cancellation
|
|
|
|
```text
|
|
total logical deadline
|
|
-> local scheduler
|
|
-> remaining attempt deadline
|
|
-> OFFICIAL_GRPC_WEB_XHR
|
|
-> absolute Unix epoch-ms `deadline` metadata
|
|
-> official runtime-owned grpc-timeout/XHR timer
|
|
-> returned call.cancel()
|
|
| pinned PromiseCallOptions.signal
|
|
-> CONNECT_WEB_GRPC_WEB_FETCH
|
|
-> positive remaining timeoutMs
|
|
-> runtime-owned grpc-timeout/Fetch timer
|
|
-> AbortSignal + AsyncIterator close
|
|
-> CUSTOM_FETCH_FRAMED
|
|
-> bounded grpc-timeout wire metadata
|
|
-> Fetch AbortController
|
|
-> response reader/idle timer
|
|
```
|
|
|
|
deadline은 auth attach, network, retry/backoff, frame read, protobuf decode,
|
|
semantic validation과 mapper를 포함한다.
|
|
|
|
- official runtime public API에는 현재 시각 기준 남은 duration이 아니라 absolute
|
|
Unix timestamp milliseconds인 `deadline` metadata를 전달한다. pinned runtime이
|
|
이를 wire `grpc-timeout`과 XHR timer로 변환한다.
|
|
- Connect-Web runtime에는 VD-29의 positive remaining `timeoutMs`와 local
|
|
`AbortSignal`을 전달하고 runtime이 wire `Grpc-Timeout`을 소유한다. `timeoutMs`
|
|
0 이하를 timeout 없음으로 해석할 수 있으므로 remaining이 0 이하이면 runtime을
|
|
호출하지 않는다.
|
|
- custom runtime만 remaining duration을 `grpc-timeout`으로 encode한다. 값은 최대
|
|
8자리 양의 정수와 `H | M | S | m | u | n` unit grammar를 지키고 local remaining
|
|
deadline을 늘리지 않게 clamp한다.
|
|
- retry/auth recovery의 각 physical attempt 직전에 elapsed time을 차감해 absolute
|
|
deadline 또는 `grpc-timeout`을 다시 계산한다. 최초 attempt 값을 재사용하지
|
|
않는다.
|
|
- attempt timer는 remaining total보다 길지 않음
|
|
- server stream은 idle + total deadline 둘 다
|
|
- Envoy의 route/max-stream/`grpc_timeout_header_max`/offset은 frontend deadline을
|
|
늘리지 않는다. proxy가 먼저 끊는 profile이면 그 차이를 명시하고 normalized
|
|
terminal reason을 검증한다.
|
|
- official callback unary/server-stream은 보관한 returned call의 `cancel()`,
|
|
Promise unary는 pinned runtime/codegen이 지원하는
|
|
`PromiseCallOptions.signal`로 중단한다. Promise API에 direct call handle이나
|
|
`.on(...)` status/metadata event가 있다고 가장하지 않는다. Connect-Web call은
|
|
`AbortSignal`과 server-stream `AsyncIterator` close로 중단한다. custom call은
|
|
`AbortController.abort()`와 reader cancel/release로 중단한다.
|
|
- caller/navigation/logout/runtime close를 별도 cancellation reason으로 유지
|
|
- local abort가 server command rollback을 보장하지 않음
|
|
- gateway는 downstream disconnect를 upstream cancel로 전달하고 backend handler는
|
|
cancellation을 cooperative하게 관찰하며 downstream RPC에도 remaining deadline과
|
|
cancel을 전파함
|
|
- timer/listener/reader를 모든 terminal path에서 정리
|
|
|
|
### 12. Replay와 retry
|
|
|
|
모든 gRPC method가 HTTP POST여도 semantic safety는 method registry가 결정한다.
|
|
|
|
```text
|
|
SAFE
|
|
IDEMPOTENT
|
|
KEYED_COMMAND
|
|
NON_REPLAYABLE
|
|
```
|
|
|
|
```text
|
|
GrpcWebRetryProfile
|
|
retryOwner = FRONTEND_ADAPTER | ENVOY | NONE
|
|
maxPhysicalAttempts
|
|
maxSleepMs
|
|
maxElapsedMs
|
|
initialBackoffMs
|
|
maxBackoffMs
|
|
backoffMultiplier
|
|
jitterProfile
|
|
retryableTransportOutcomes[]
|
|
retryableGrpcStatuses[]
|
|
retryPushbackProfile = UNSUPPORTED | SELECTED_EXTENSION
|
|
hedging = DISABLED
|
|
```
|
|
|
|
gRPC-Web protocol 자체는 browser retry를 규정하지 않고 official JS runtime은 native
|
|
gRPC service-config retry를 제공하지 않는다. 따라서:
|
|
|
|
- `FRONTEND_ADAPTER`는 application/provider extension retry다. Envoy/BFF/Query/
|
|
downstream service의 같은 logical operation retry를 끄고 physical attempt를
|
|
adapter가 계수한다.
|
|
- `ENVOY`를 선택하면 frontend와 Query retry를 끈다. exact route retry policy,
|
|
per-try timeout과 total route/deadline budget을 고정한다. Envoy가
|
|
`grpc-status` response header에 대해서만 gRPC status retry를 할 수 있고 response
|
|
trailers의 status로는 retry하지 못한다는 제약을 capability matrix에 기록한다.
|
|
server stream route retry/hedging은 금지한다.
|
|
- `NONE`이면 모든 layer의 automatic retry를 끈다.
|
|
- caller-provided `x-envoy-*` retry/timeout header는 gateway에서 제거한다.
|
|
- `grpc-retry-pushback-ms` 같은 metadata는 기본 `UNSUPPORTED`다. exact
|
|
runtime/provider/proxy fixture와 bounded parser를 가진 selected extension일 때만
|
|
사용하며 native gRPC transparent retry라고 표시하지 않는다.
|
|
- route timeout은 모든 Envoy attempt를 포함한다. per-try와 backoff가 logical total
|
|
deadline을 넘어가지 않으며 frontend/Envoy/backend의 retry multiplication을
|
|
fault injection으로 거절한다.
|
|
|
|
unary:
|
|
|
|
- safe/idempotent 또는 backend-proven keyed operation만 retry
|
|
- selected transient network/`UNAVAILABLE`/approved resource condition만
|
|
- deadline/schema/status/frame/mapper failure retry 금지
|
|
- total attempt/sleep/elapsed ceiling
|
|
- hedging은 모든 production profile에서 off
|
|
|
|
auth recovery:
|
|
|
|
- `UNAUTHENTICATED`에서 replay-safe operation만 one-time single-flight refresh
|
|
- same request semantic input/idempotency/deadline
|
|
|
|
keyed command:
|
|
|
|
- logical command key + principal + service/method + deterministic request digest
|
|
- backend atomic dedupe/status/TTL evidence
|
|
- ambiguous outcome은 new key retry가 아니라 reconcile
|
|
|
|
server stream:
|
|
|
|
- Envoy retry는 off다. 첫 application event 전달 전 frontend opening retry는
|
|
`retryOwner=FRONTEND_ADAPTER`인 `SAFE | IDEMPOTENT` operation만 original total
|
|
deadline, monotonic physical-attempt와 sleep cap 안에서 가능
|
|
- `KEYED_COMMAND | NON_REPLAYABLE` server stream은 explicit backend
|
|
resume/reconcile/dedupe profile이 없으면 registry에서 거절
|
|
- event 전달 뒤 transport-only automatic replay 금지
|
|
- resume하려면 backend sequence/snapshot/resume token protocol 필요
|
|
|
|
### 13. Server-stream state machine
|
|
|
|
```text
|
|
OPENING
|
|
-> STREAMING
|
|
-> COMPLETING
|
|
-> COMPLETED
|
|
|
|
OPENING | STREAMING
|
|
-> FAILED | CANCELLED | GAP | OVERFLOW
|
|
```
|
|
|
|
```text
|
|
ServerStreamPort<Event>
|
|
open(bound request, signal)
|
|
-> AsyncIterable<Result<Event>>
|
|
-> close()
|
|
```
|
|
|
|
각 data frame:
|
|
|
|
1. message/frame/total budget
|
|
2. generated protobuf decode
|
|
3. semantic schema
|
|
4. boundary mapper
|
|
5. scope/runtime generation
|
|
6. sequence/snapshot protocol
|
|
7. bounded queue admission
|
|
|
|
`ServerStreamPort`는 use case가 명시적으로 연 operation-bound outbound result로
|
|
남을 수 있다. 오직 제품이 runtime-wide external notification projection을
|
|
선택한 branch에서만 mapper 이후 event를 VD-28의 common scope/generation/
|
|
dedupe/gap/resync coordinator 또는 `FeatureEventInput`에 전달한다. protobuf
|
|
frame/message를 `REALTIME_EVENT_V1` JSON으로 다시 감싸지 않으며 reconnect는 위
|
|
opening/resume 규칙이 계속 소유한다.
|
|
|
|
stream policy:
|
|
|
|
- max messages/bytes/duration
|
|
- idle deadline
|
|
- consumer backpressure
|
|
- bounded queue/high-water mark
|
|
- overflow outcome
|
|
- duplicate/gap/order
|
|
- resume token expiry
|
|
- terminal status source
|
|
- cancellation/unsubscribe
|
|
|
|
worker/timer keepalive로 infinite correctness를 주장하지 않는다.
|
|
|
|
### 14. Query cache integration
|
|
|
|
unary:
|
|
|
|
- mapped application result만 VD-25 TanStack query에 admission
|
|
- service/method/descriptor/protobuf bytes를 query key/value에 넣지 않음
|
|
- `retryOwner` 값과 무관하게 Query retry는 false다. `NONE`을 Query가 암묵적으로
|
|
대체하지 않으며 selected gRPC-Web retry owner만 physical attempt를 만든다.
|
|
|
|
finite server-stream aggregate:
|
|
|
|
```text
|
|
mapped events
|
|
-> bounded staging reducer
|
|
-> valid terminal OK status
|
|
-> completeness/integrity/schema
|
|
-> generation fence
|
|
-> immutable aggregate
|
|
-> atomic Query commit
|
|
```
|
|
|
|
long-running stream:
|
|
|
|
- ordinary queryFn 아님
|
|
- registered reducer가 bounded snapshot을 만들거나 invalidation hint 발행
|
|
- event history 무한 cache 금지
|
|
- partial/truncated/gap stream을 complete query success로 쓰지 않음
|
|
- reconnect를 Query retry로 하지 않음
|
|
|
|
### 15. Protobuf mapping
|
|
|
|
VD-24 scalar policy를 사용한다.
|
|
|
|
- int64/uint64를 JS number로 자동 변환하지 않음
|
|
- safe range를 증명하거나 decimal string/application value로 mapping
|
|
- Timestamp/Duration range/nanos 확인
|
|
- bytes copy/size/classification
|
|
- map ordering 독립
|
|
- enum zero/unknown numeric
|
|
- oneof presence
|
|
- wrappers/optional/default distinction
|
|
- unknown field를 domain에 passthrough하지 않음
|
|
- generated message mutation/reuse 금지, immutable application projection 생성
|
|
|
|
### 16. Client/bidi streaming
|
|
|
|
primary status는 `PLATFORM_LIMITED`.
|
|
|
|
다음으로 fallback/re-design한다.
|
|
|
|
| 요구 | 대안 |
|
|
| --- | --- |
|
|
| large upload stream | REST/presigned multipart upload |
|
|
| browser event duplex | WebSocket/WebTransport 또는 별도 duplex protocol |
|
|
| server→client one-way + 분리 가능한 command | SSE + registered HTTP command |
|
|
| command sequence | server-owned job/session API |
|
|
| telemetry batch | bounded unary/REST batch |
|
|
|
|
generated service가 client/bidi RPC를 가진다는 이유로 browser에서 unary loop로
|
|
흉내 내지 않는다. ordering/backpressure/half-close semantics가 달라진다.
|
|
|
|
### 17. Security와 privacy
|
|
|
|
- fixed endpoint/service/method
|
|
- caller metadata/header 금지
|
|
- `x-envoy-*`, transport/deadline header caller override 제거
|
|
- preflight를 피하려 metadata/credential을 query parameter로 이동 금지
|
|
- credential/CSRF adapter confinement/final invariant
|
|
- request/response header, trailer, frame, message, decompressed cap
|
|
- rich status details allowlist
|
|
- raw grpc-message/payload/metadata/trailer log 금지
|
|
- generated debug JSON/stringifier를 production logging에 사용 금지
|
|
- server/proxy authorization와 method-level resource authorization
|
|
- frontend protobuf validation을 authorization으로 간주 금지
|
|
- query/cache에 generated message/token/metadata 없음
|
|
|
|
### 18. Observability
|
|
|
|
허용:
|
|
|
|
- semantic operation ID
|
|
- allowlisted service/method profile ID
|
|
- gRPC status bucket + HTTP status group
|
|
- logical/physical attempt, auth recovery, deadline bucket
|
|
- request/response message/total bytes/count bucket
|
|
- stream terminal reason/idle/gap/overflow bucket
|
|
- descriptor/provider/browser version low-cardinality bucket
|
|
- cache admission/invalidation outcome
|
|
|
|
금지:
|
|
|
|
- message/proto JSON
|
|
- metadata/trailer raw value
|
|
- grpc-message/status-details bytes
|
|
- resource/account/request ID actual value
|
|
- resume token/sequence high-cardinality value
|
|
|
|
### 19. Testing
|
|
|
|
protobuf/codegen:
|
|
|
|
- source provenance/digest
|
|
- Buf lint/breaking
|
|
- field reserve/presence/enum/oneof/int64/time fixture
|
|
- clean generation
|
|
- generated import boundary/removal
|
|
|
|
frame/status:
|
|
|
|
- official XHR, Connect-Web runtime-owned Fetch와 custom raw-frame Fetch runtime의
|
|
framing/status 책임이 섞이지 않음
|
|
- Connect-Web Proto/JSON binary-envelope row와 `grpc-web-text` rejection
|
|
- partial prefix/payload
|
|
- unknown flag/over-limit length
|
|
- compressed/decompression overflow
|
|
- body trailer vs trailers-only header source, missing/duplicate/conflicting status
|
|
- missing `grpc-status`의 `400/401/403/404/429/502/503/504/other` canonical mapping,
|
|
valid status 존재 시 HTTP mapping 미적용, server-side 역매핑 금지
|
|
- status canonical `0..16`, body lowercase names, rich-details outer-code mismatch
|
|
- data after trailer/extra bytes
|
|
- unary 0/1/2 messages including one zero-length `Empty` frame
|
|
- non-OK with data
|
|
- text base64 quantum/padding을 browser chunk 여러 개로 분할
|
|
- padded entity 뒤 추가 data/trailer entity, 연속 padded/unpadded entity와 invalid
|
|
alphabet/padding/whitespace
|
|
- header/trailer field-count/byte overflow, padded/unpadded `-bin`, comma-joined duplicate
|
|
binary metadata
|
|
- rich details allowlist/redaction
|
|
|
|
deadline/retry/auth:
|
|
|
|
- official absolute epoch-ms `deadline` metadata, returned call `cancel()`과 pinned
|
|
Promise `AbortSignal`
|
|
- Connect-Web positive remaining `timeoutMs`, AbortSignal과 AsyncIterator close
|
|
- custom 8-digit/unit `grpc-timeout`, Fetch abort와 reader cancel
|
|
- headers 전/후와 첫 stream event 전/후 caller cancel, attempt/total/idle timeout
|
|
- retry/auth recovery마다 remaining deadline 재계산, Envoy timeout max/offset ordering
|
|
- reader/timer/listener cleanup
|
|
- UNAUTHENTICATED single-flight
|
|
- SAFE/IDEMPOTENT/KEYED_COMMAND/NON_REPLAYABLE
|
|
- `FRONTEND_ADAPTER | ENVOY | NONE` exact-one owner와 Query retry off
|
|
- Envoy response-header `grpc-status` retry와 trailer-status no-retry, route total budget
|
|
- transient status exact retry, pushback unsupported default, `x-envoy-*` caller strip
|
|
- frontend/Envoy/backend retry amplification과 hedging 거절
|
|
- ambiguous command reconcile
|
|
|
|
server stream:
|
|
|
|
- zero/many events
|
|
- backpressure/high-water/overflow
|
|
- duplicate/order/gap/resume
|
|
- terminal/non-terminal/truncated
|
|
- account/logout/generation
|
|
- finite aggregate atomic cache commit
|
|
|
|
provider/browser:
|
|
|
|
- runtime kind/client API/spec/rpcKind/wire-profile capability matrix와 official Promise
|
|
status-event rejection/`PromiseCallOptions.signal` version capability
|
|
- Connect-Web unary Promise/server-stream `ASYNC_ITERABLE`, Proto/JSON exact media와
|
|
`GRPC_WEB_TEXT` rejection
|
|
- exact POST/media/Accept/`X-Grpc-Web`/`X-User-Agent` wire header
|
|
- pinned actual Envoy/BFF version/config digest, upstream HTTP/2와
|
|
`grpc_web -> cors -> router` order
|
|
- exact-origin CORS positive/negative preflight, credential/CSRF, exposed status headers,
|
|
non-matching preflight fail-closed와 preflight suppression 금지
|
|
- local reply/missing-status, compression/message/header/deadline cap
|
|
- stream이 15초를 넘는 fixture, explicit route/idle/max-stream timeout과 no
|
|
buffering/flush
|
|
- Chromium/Firefox/WebKit cancellation/status/trailer/backpressure
|
|
- Safari/enterprise proxy buffering and selected text fallback
|
|
|
|
MSW/memory frame fixture는 actual gateway evidence가 아니다.
|
|
|
|
### 20. Rollout
|
|
|
|
1. product가 protobuf-first operation family와 gateway owner를 선택한다.
|
|
2. proto/descriptor/proxy/auth/status/deadline contract를 확정한다.
|
|
3. selected runtime별 official-XHR, Connect-Web runtime-owned Fetch 또는 custom
|
|
raw-frame Fetch transport adapter와 generated boundary를 구현한다. 세 runtime의
|
|
framing/cancel 책임을 합치지 않는다.
|
|
4. unary read operation을 REST shadow와 비교하되 secondary cache/UI write 금지.
|
|
5. actual proxy/browser conformance 통과.
|
|
6. reference runtime을 `AVAILABLE_NOT_COMPOSED`로 판정.
|
|
7. product composition 뒤 `COMPOSED`,
|
|
`TrafficAdmission=DISABLED`로 시작.
|
|
8. unary safe read internal canary.
|
|
9. keyed command는 backend dedupe/reconcile 뒤 별도 canary.
|
|
10. finite server stream은 unary와 별도 evidence/traffic gate.
|
|
11. client/bidi는 계속 `PLATFORM_LIMITED`.
|
|
|
|
rollback:
|
|
|
|
- 신규 operation/stream admission 중지
|
|
- read/stream cancel, command effect reconcile
|
|
- current scope gRPC-mapped cache clear/invalidate
|
|
- coherent frontend/generated descriptor/proxy/backend rollback
|
|
- approved REST read fallback은 새 logical query로만 실행
|
|
|
|
### 21. Removal
|
|
|
|
1. operation traffic과 backend method retirement window 시작
|
|
2. unary/stream cancel, command reconcile
|
|
3. Query cache/reducer/invalidation listener clear
|
|
4. operation/schema/mapper/query profile 제거
|
|
5. generated files, proto/descriptor, generator/runtime dependency 제거
|
|
6. proxy route/CORS/config 제거
|
|
7. backend method는 N/N-1 client window 뒤 제거/reserve
|
|
8. production module/dependency/SBOM/removal test 통과
|
|
|
|
## 규범 기준
|
|
|
|
아래 링크는 upstream 기준 위치다. production profile과 evidence는 `master`/`latest`
|
|
문자열이 아니라 검증한 immutable commit, package version과 Envoy release를
|
|
별도로 기록한다.
|
|
|
|
- [gRPC-Web protocol delta](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md)
|
|
- [Official grpc-web runtime support matrix](https://github.com/grpc/grpc-web)
|
|
- [Official grpc-web browser features와 CORS](https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md)
|
|
- [Official grpc-web XHR/deadline implementation](https://github.com/grpc/grpc-web/blob/master/javascript/net/grpc/web/grpcwebclientbase.js)
|
|
- [Official grpc-web cancellation handle](https://github.com/grpc/grpc-web/blob/master/javascript/net/grpc/web/clientreadablestream.js)
|
|
- [VD-29 Connect-Web과 browser Protobuf runtime](./VD-29-connect-web-and-browser-protobuf-runtime.md)
|
|
- [Connect-Web gRPC-Web transport source](https://github.com/connectrpc/connect-es/blob/main/packages/connect-web/src/grpc-web-transport.ts)
|
|
- [gRPC HTTP/2 protocol와 timeout/header 규칙](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md)
|
|
- [Canonical HTTP→gRPC status mapping](https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md)
|
|
- [gRPC deadline guide](https://grpc.io/docs/guides/deadlines/)
|
|
- [gRPC cancellation guide](https://grpc.io/docs/guides/cancellation/)
|
|
- [gRPC retry guide](https://grpc.io/docs/guides/retry/)
|
|
- [gRFC A6 client retries](https://github.com/grpc/proposal/blob/master/A6-client-retries.md)
|
|
- [Envoy gRPC-Web filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_web_filter)
|
|
- [Official grpc-web Envoy example](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/envoy.yaml)
|
|
- [Envoy timeout guidance](https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/timeouts.html)
|
|
- [Envoy route timeout API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto.html)
|
|
- [Envoy router retry constraints](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter)
|
|
- [Envoy CORS filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/cors_filter)
|
|
- [Envoy CORS API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/cors/v3/cors.proto.html)
|
|
- [Envoy buffer filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/buffer_filter)
|
|
- [Buf breaking-change policy](https://buf.build/docs/breaking/)
|
|
|
|
## 완료 기준
|
|
|
|
- exact provider/profile/service/method/descriptor에 operation이 binding된다.
|
|
- official XHR/Connect-Web runtime-owned Fetch/custom raw-frame Fetch runtime kind,
|
|
client API, runtime version과 gRPC-Web wire-spec revision이 고정되고 책임이
|
|
섞이지 않는다. official runtime의 binary server stream, Connect-Web의 text와
|
|
evidence 없는 JSON/server-stream 조합이 거절된다.
|
|
- missing `grpc-status`가 canonical HTTP→gRPC 표로 failure normalization되고 wire
|
|
header 합성이나 reverse mapping을 하지 않는다.
|
|
- pinned executable Envoy/BFF profile이 upstream HTTP/2, filter order, CORS,
|
|
route/idle/max-stream/deadline, buffering, header budget과 config digest를 닫는다.
|
|
- deadline/cancel dialect가 runtime별로 고정되고 retry owner가 frontend/Envoy/none
|
|
중 정확히 하나이며 Query/backend와 amplification을 만들지 않는다.
|
|
- text runtime은 concatenated padded/unpadded base64 entity와 arbitrary browser
|
|
chunk 경계를 처리한다.
|
|
- protobuf generated type이 adapter 밖으로 나오지 않는다.
|
|
- frame/message/trailer/status/deadline/cancel/retry가 closed state machine이다.
|
|
- unary success가 exact one message + body terminal trailer의 유일한 OK status로
|
|
증명되고, trailers-only header status는 zero-message unary를 success로 만들지
|
|
않는다.
|
|
- stream이 bounded backpressure/sequence/gap/resume/terminal 계약을 가진다.
|
|
- int64/time/enum/oneof/bytes semantic mapper가 닫힌다.
|
|
- actual proxy와 Chromium/Firefox/WebKit evidence가 유효하다.
|
|
- client/bidi를 지원한다고 표시하지 않는다.
|
|
- cache에 raw/generated/partial stream state가 없다.
|
|
- kill switch, coherent rollback과 dependency/proxy/generated removal drill이 통과한다.
|