# Exemples : TLS, remplacement, quotas partagés, gRPC, cache, corps, appel externe

> Hors maillage seulement : dans un maillage, le sidecar le fait déjà. Les fichiers doivent exister au chargement - c'est pourquoi ce cas n'a pas de fichier…

Exemple - Depuis la version 1.0.0 - Flexway v1.6.3 - https://flexway.io/docs/operations-examples-gateway/

> Guide d'exploitation : configurer, déployer et observer la passerelle. Ce que fait la capacité côté produit : [guide produit](https://flexway.io/docs/product-overview/) ; l'appeler : [guide développeur](https://flexway.io/docs/developer-overview/).

## mTLS vers un amont

Hors maillage seulement : dans un maillage, le sidecar le fait déjà. Les fichiers doivent exister
au chargement - c'est pourquoi ce cas n'a pas de fichier d'exemple validé en CI.

```yaml
upstreams:
  payments:
    url: https://payments.partner.example:8443   # TLS exige https://
    tls:
      ca_file: /etc/flexway/tls/partner-ca.pem
      cert_file: /etc/flexway/tls/client.crt  # relu à sa rotation, sans redémarrage
      key_file: /etc/flexway/tls/client.key
      server_name: payments.partner.example
```

## Écouteur TLS de la passerelle

Sans edge qui termine le TLS : la passerelle vérifie elle-même le certificat client, sur
`FLEXWAY_TLS_ADDR` (`:8443` par défaut). Les clients se déclarent comme en 12.15 ; les fichiers
doivent exister au chargement, d'où l'absence de fichier d'exemple validé en CI.

```yaml
server:
  tls:
    cert_file: /etc/flexway/tls/tls.crt              # relu à sa rotation, sans redémarrage
    key_file: /etc/flexway/tls/tls.key
    client_ca_file: /etc/flexway/tls/clients-ca.pem  # autorité des certificats clients, relue aussi
auth:
  mtls:
    clients:
      - { id: bank-settler, uri: "spiffe://bank.example/settler", tenant: bank, operations: [settle] }
```

## Remplacer une passerelle en place

Venir d'une passerelle existante : l'import de sa configuration écrit le point de départ - amonts, émetteurs, débits, coupe-circuits, extensions du contrat - et le rapport de ce qui ne passe pas ([Reprendre une passerelle existante](https://flexway.io/docs/operations-import/)). Ce qui suit est ce qu'on y ajoute le premier jour.

<!-- example: examples/replace-gateway/flexway.yaml -->

```yaml
# What a team replacing its gateway usually needs on day one (1.0.0): anonymous
# users behind a BFF counted apart, a bucket per operation as most gateways count, errors typed under
# its own portal with field codes in its own words, the body left to the service, tokens of an old
# and a new audience accepted while the audience is renamed.
version: 1
contracts:
  openapi: ./openapi.yaml
upstreams:
  default:
    url: http://shop.internal:8080
hosts:
  api.example.com: { default_upstream: default, validation: params }   # the services validate their bodies
server:
  trusted_proxies: [local]                       # the mesh proxy: the pod's own addresses (not the loopback)
  peer_identity_header: l5d-client-id            # the mTLS identity that proxy verified
errors:
  type_base: https://docs.example.com/errors/    # type = https://docs.example.com/errors/<code>
  field_codes: { minLength: too_short, maxLength: too_long, minimum: out_of_range, maximum: out_of_range }
auth:
  issuers:
    - name: customers
      discovery: https://login.example.com/.well-known/openid-configuration
      audiences: [shop-api, api]                 # the old name and the new, during the rename
policies:
  definitions:
    per-user:
      type: rate-limit
      config:
        key: header                              # anonymous: X-Client-Key, if the BFF set it
        header: X-Client-Key
        trusted: [web-bff.web.serviceaccount.identity.linkerd.cluster.local]
        scope: operation                         # a bucket per operation and user, not one per class
        classes:
          read:  { rps: 20, burst: 40 }
          write: { rps: 5,  burst: 10 }
  chains:
    global: [per-user]
```

```yaml
# extrait de examples/replace-gateway/openapi.yaml
  /orders:
    post:
      operationId: createOrder
      x-flexway-validation: full                 # this one is checked here too, body included
```

## Débit partagé, forfaits, délestage

<!-- example: examples/shared-quotas/flexway.yaml -->

```yaml
# Several replicas sharing one rate, a plan per tenant, and load shed when the gateway saturates
# (1.0.0).
version: 1
contracts:
  openapi: ./openapi.yaml
upstreams:
  default:
    url: http://reports.internal:8080
server:
  counters: { store: "${COUNTERS_URL}", sync: 1s }   # Redis, Dragonfly or Valkey, shared by the replicas
  shedding: { target: 20ms }                         # p99 scheduling latency above which load is shed
auth:
  issuers:
    - name: customers
      discovery: https://login.example.com/.well-known/openid-configuration
      audience: api
      tenant_claim: org                              # what a quota counts by
policies:
  definitions:
    per-caller:
      type: rate-limit
      config:
        classes:
          read:  { rps: 50, burst: 100 }
          write: { rps: 10, burst: 20 }
    plans:
      type: quota
      config: { limits: { day: 100000, month: 2000000 } }
  chains:
    global: [per-caller, plans]
```

```yaml
# extrait de examples/shared-quotas/openapi.yaml
  /reports:
    post:
      operationId: buildReport
      x-flexway-priority: sheddable              # shed first when the gateway saturates
  /payments/{id}:
    get:
      operationId: getPayment
      x-flexway-priority: critical               # never shed
```

## Amont gRPC en gRPC, gRPC-Web et REST

<!-- example: examples/grpc-upstream/flexway.yaml -->

```yaml
# A gRPC upstream served three ways from its own .proto (1.1.0): to gRPC clients (h2c on the
# traffic listener), to browsers in gRPC-Web, and in REST/JSON at the routes its google.api.http
# annotations declare. One operation per method - shop.v1.Orders/GetOrder - whatever the protocol:
# the same admission, rate limits, quotas and audit.
#
# orders.binpb is the descriptor set built from orders.proto (`buf build -o orders.binpb`): Flexway
# reads it, never the .proto, and needs no generated code. Rebuild it when the .proto changes.
version: 1
contracts:
  protobuf: [./orders.binpb]
upstreams:
  # Named by the methods' option (upstream: "orders"), as x-flexway-upstream names one.
  orders:
    # Inside a mesh the sidecar does mTLS, so the gateway speaks HTTP/2 in clear (h2c).
    url: http://orders.internal:9000
    protocol: grpc
auth:
  issuers:
    - { name: staff, discovery: "https://id.example.com/realms/staff/.well-known/openid-configuration", audience: orders-api }
```

```proto
// extrait de examples/grpc-upstream/orders.proto
service Orders {
  rpc GetOrder(GetOrderRequest) returns (Order) {
    option (google.api.http) = { get: "/v1/shop/orders/{id}" };          // the REST route
    option (flexway.v1.operation) = {                                    // what x-flexway-* says in OpenAPI
      upstream: "orders"
      security: [ { scopes: ["orders:read"] } ]
      timeout: "2s"
    };
  }
  rpc CreateOrder(CreateOrderRequest) returns (Order) {
    option (google.api.http) = { post: "/v1/shop/orders" body: "order" };
    option (flexway.v1.operation) = { upstream: "orders" security: [ { anonymous: true } ] };
  }
  rpc WatchOrder(GetOrderRequest) returns (stream Order) {               // an event stream in REST
    option (google.api.http) = { get: "/v1/shop/orders/{id}/watch" };
    option (flexway.v1.operation) = { upstream: "orders" security: [ { anonymous: true } ] };
  }
}
```

## Cache de réponses

<!-- example: examples/response-cache/flexway.yaml -->

```yaml
# Responses kept by the gateway (1.2.0): opt-in, per operation in the contract
# (x-flexway-cache) or by a cache policy over routes - the operation's own wins. Public by default: an
# identified caller is never served from, nor stored into, a public entry; per tenant and subject only
# when the operation declares it. The upstream's Cache-Control only shortens the ttl; no-store,
# private, Set-Cookie and streams are never kept.
version: 1
contracts:
  openapi: ./openapi.yaml
upstreams:
  default:
    url: http://catalog.internal:8080
server:
  cache:
    max_bytes: 64MiB                             # per replica; past it, the least recently used goes
    max_entry_bytes: 512KiB                      # a larger response is relayed, never kept
auth:
  issuers:
    - name: customers
      discovery: https://login.example.com/.well-known/openid-configuration
      audience: api
      tenant_claim: org                          # a cache per subject keys by tenant and subject
policies:
  definitions:
    catalog-cache:
      type: cache
      config: { ttl: 5m }                        # public, always: a policy never caches per subject
  chains:
    routes:
      "/products/**": [catalog-cache]            # the reads under it; the writes are left alone
```

```yaml
# extrait de examples/response-cache/openapi.yaml
  /products:
    get:
      operationId: listProducts
      x-flexway-cache: { ttl: 60s }              # its own, over the route's policy
  /me/cart:
    get:
      operationId: getCart
      security: [ { customers: [] } ]
      x-flexway-cache: { ttl: 30s, scope: subject }   # each subject of each tenant, its own entry
```

## Corps et statut de réponse

<!-- example: examples/body-transforms/flexway.yaml -->

```yaml
# Response bodies shaped by the gateway (1.2.0): a closed list of fields served, personal
# data masked, a legacy upstream's statuses turned into semantic ones in RFC 9457. Only the operations
# that declare one of these policies are read whole - every other keeps the direct relay. A stream, a
# response that is not JSON, or a projection that drops a field the contract requires refuses the
# start.
version: 1
contracts:
  openapi: ./openapi.yaml
upstreams:
  default:
    url: http://orders.internal:8080
server:
  body:
    max_bytes: 2MiB                              # past it: 502 response_too_large, never a cut body
    validate_sample: 0.05                        # one served body in twenty checked against its schema
policies:
  definitions:
    public-order:
      type: transform-body
      config: { project: [id, status, total, items, customer], on_missing: fail }
    no-pii:
      type: redact
      config: { fields: [body.customer.email, body.customer.phone], with: "***" }
    legacy-errors:
      type: map-status
      config:
        - { from: 200, when: "has(body.error)", to: 422, code: legacy_rejected }
        - { from: 520, to: 502 }                 # a CDN's private status: the upstream failing
  chains:
    routes:
      "/orders/**": [legacy-errors]              # every order read; /health keeps the direct relay
```

```yaml
# extrait de examples/body-transforms/openapi.yaml
  /orders/{id}:
    get:
      operationId: getOrder
      x-flexway-policies: [public-order, no-pii]   # what is not listed is not served; the PII masked
  /health:
    get:
      operationId: health                          # no body policy: relayed without a buffer
```

## Appel externe

<!-- example: examples/external-call/flexway.yaml -->

```yaml
# A decision that belongs to the client's own system (1.2.0): a risk service asked
# before an order reaches its upstream, a loyalty service asked once a profile is answered. The
# service is an upstream declared like any other - its pool, bulkhead and breaker are its own -,
# never a URL in a policy. The timeout and what a failed call does are declared, never defaulted;
# the call consumes the operation's budget.
version: 1
contracts:
  openapi: ./openapi.yaml
auth:
  issuers:
    - name: customers
      discovery: https://id.example.com/.well-known/openid-configuration
      audience: api
      tenant_claim: org
  tenant_header: X-Tenant                        # sent to the risk service when named, never set by it
upstreams:
  default:
    url: http://orders.internal:8080
  risk:
    url: http://risk.internal:8080               # the external service: an upstream like any other
    max_in_flight: 200                           # its bulkhead
    circuit_breaker: { min_requests: 20, failure_ratio: 0.5, cooldown: 10s }
  loyalty:
    url: http://loyalty.internal:8080
policies:
  definitions:
    risk-check:
      type: external-call
      phase: request
      when: "request.method == 'POST'"
      config:
        upstream: risk
        path: /score
        timeout: 150ms                           # required, within the operation's budget
        on_failure: fail-closed                  # an authorization: no answer, no order
        send: { headers: [X-Tenant], body: true }
        deny: { when: "response.status == 403", status: 403, code: risk_refused }
        set_headers: { X-Risk-Score: "response.body.score" }
    loyalty-tier:
      type: external-call
      phase: response
      config:
        upstream: loyalty
        path: /tier
        timeout: 80ms
        on_failure: fail-open                    # an enrichment: no answer, no header
        send: { headers: [X-Tenant] }
        set_headers: { X-Loyalty-Tier: "response.body.tier" }
```

```yaml
# extrait de examples/external-call/openapi.yaml
  /orders:
    post:
      operationId: createOrder
      x-flexway-timeout: 2s                        # the budget the risk call consumes
      x-flexway-policies: [risk-check]
  /health:
    get:
      operationId: health                          # no external call: nothing asked of anyone
```

## Pas encore construit

Écrit ici pour que la forme cible soit connue. D'ici sa version, une clé nouvelle de
`flexway.yaml` est **refusée au chargement**, un type de politique à venir est accepté avec un
avertissement et **jamais appliqué**, et une extension de contrat à venir est **refusée au
chargement** avec la version qui l'apporte - le détail, relevé avec `flexway validate`, et un exemple
complet et commenté par cas d'usage sont en [Configurations cibles des prochaines versions](https://flexway.io/docs/operations-planned-configurations/). La syntaxe est
celle prévue, pas une promesse de détail.

| Cas | Forme cible | Version |
| --- | --- | --- |
| Appareils et industrie : écouteur MQTT des appareils (X.509, jeton ou clé en mot de passe), topic lié au tenant et au certificat, Sparkplug B, OPC UA PubSub JSON | `listeners.mqtt`, `x-flexway-device`, `x-flexway-sparkplug`, `x-flexway-bind: "cert:<champ>"` | `1.7.0` |
| Plans et budgets : limites par plan (claim vérifiée ou attribut de clé, plan par défaut), budgets d'inférence par utilisateur et par cas d'usage | `plans`, `issuers[].plan_claim`, `ai.budget.per_subject`, `ai.use_cases`, `ai.aliases`, `x-flexway-use-case` | `1.8.0` |
| Webhooks entrants signés, listes d'adresses : HMAC générique, Standard Webhooks, préréglages de fournisseurs ; listes blanche et noire, PROXY protocol | `x-flexway-webhook`, `webhooks.secrets`, `server.ip_deny`, `server.proxy_protocol`, politique `ip-filter` | `1.8.0` |
| Compression négociée, coalescence des lectures en vol | Politique `compress` (gzip, brotli, zstd), `x-flexway-coalesce` | `1.8.0` |
| Cartes A2A amont vérifiées, carte publiée re-signée | `agents.a2a.signers`, `agents.a2a.signing`, `agents.a2a.cards`, `x-flexway-a2a.signer` | `1.9.0` |
| Délégation d'agent à agent, registre | Bornes resserrées par agent sur deux sauts A2A, verdicts dans `/config` | `1.9.0` |
| Licence : sans licence valide, Flexway refuse de démarrer ; clé Développeur gratuite hors production (90 jours, en-tête `Flexway-License: developer`, 100 requêtes par seconde et par réplique), Business et Enterprise avec 30 jours de grâce ; état dans le journal, la métrique `flexway.license.state`, `/config` et la console ; jamais d'arrêt d'un processus en service | Fichier de licence monté comme un secret, son chemin dans `flexway.yaml` (nom de clé encore à fixer) ; `flexway validate` reste utilisable sans licence | À l'ouverture publique, à dater |
