Skip to content

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Buffering

Page as Markdown

    

Buffer request and response bodies so that filters can inspect and transform them safely.

Fine-tune connection speeds for read and write operations by setting a connection buffer limit.

About read and write buffer limits

By default, kgateway is set up with 1MiB of request read and write buffer for each gateway. For large requests that must be buffered and that exceed the default buffer limit, kgateway either disconnects the connection to the downstream service if headers were already sent, or returns a 413 HTTP response code. To make sure that large requests can be sent and received, you can specify the maximum number of bytes that can be buffered between the gateway and the downstream service. Alternatively, when using kgateway as an edge proxy, configuring the buffer limit can be important when dealing with untrusted downstreams. By setting the limit to a small number, such as 32KiB, you can better guard against potential attacks or misconfigured downstreams that could excessively use the proxy’s resources.

The connection buffer limit can be configured on the Gateway level or on an individual route.

Considerations when using httpbin

When you use the httpbin sample app, keep in mind that httpbin limits the maximum body size to 1 mebibyte (1Mi). If you send a request to httpbin with a body size that is larger than that, httpbin automatically rejects the request with a 400 HTTP response code.

Before you begin

  1. Follow the Get started guide to install kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. Get the external address of the gateway and save it in an environment variable.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    echo $INGRESS_GW_ADDRESS  

Set up buffer limits per gateway

Use an annotation to set a per-connection buffer limit on your Gateway, which applies the buffer limit to all routes served by the Gateway.

  1. Create a TrafficPolicy called transformation-buffer-body that forces buffering by transforming the response from the httpbin sample app.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: transformation-buffer-body
      namespace: httpbin
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: httpbin
      transformation:
        response:
          body:
            parseAs: AsString
            value: '{{ body() }}'
    EOF
  2. Annotate the http Gateway resource to set a buffer limit of 1 kilobytes.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: ListenerPolicy
    metadata:
      name: bufferlimits
      namespace: kgateway-system
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: http
      default:
        perConnectionBufferLimitBytes: 1024
    EOF
  3. To test the buffer limit, create a payload in a temp file that exceeds the 1Ki buffer limit.

    dd if=/dev/zero bs=2048 count=1 | base64 -w 0 > /tmp/large_payload_2k.txt
  4. Send a request to the /anything httpbin path with the large payload. Verify that the request fails with a connection error or timeout, indicating that the buffer limit was exceeded.

    curl -vik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
    -H "host: www.example.com:8080" \
    -H "Content-Type: text/plain" \
    -d "{\"payload\": \"$(< /tmp/large_payload_2k.txt)\"}"

    Example output:

    * upload completely sent off: 2747 bytes
    < HTTP/1.1 413 Payload Too Large
    HTTP/1.1 413 Payload Too Large
    < access-control-allow-credentials: true
    access-control-allow-credentials: true
    < access-control-allow-origin: *
    access-control-allow-origin: *
    < x-envoy-upstream-service-time: 1
    x-envoy-upstream-service-time: 1
    < content-length: 17
    content-length: 17
    < server: envoy
    server: envoy
  5. Test the buffer limit again by sending a request with a small payload, "hello world". This request succeeds with a normal response from httpbin because the payload size is within the 1Ki limit.

    curl -vik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
       -H "host: www.example.com:8080" \
       -H "Content-Type: application/json" \
       -d "{\"payload\":  \"hello world\"}" 

    Example output:

    * upload completely sent off: 27 bytes
    < HTTP/1.1 200 OK
    HTTP/1.1 200 OK
    ...
      "url": "http://www.example.com:8080/anything",
      "data": "{\"payload\":  \"hello world\"}",
      "files": null,
      "form": null,
      "json": {
        "payload": "hello world"
      }
    }

Set up buffer limits per route

You can configure connection buffer limits using a TrafficPolicy to control how much data can be buffered per connection at the level of individual routes. This can provide more fine-grained control than applying the buffer limit at the Gateway, or can provide a method of overriding a buffer limit at the level of the Gateway.

  1. If you did not already, create a TrafficPolicy called transformation-buffer-body that forces buffering by transforming the response from the httpbin sample app.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: transformation-buffer-body
      namespace: httpbin
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: httpbin
      transformation:
        response:
          body:
            parseAs: AsString
            value: '{{ body() }}'
    EOF
  2. If you previously created the ListenerPolicy, remove it.

    kubectl delete listenerpolicy bufferlimits -n kgateway-system 
  3. In a separate TrafficPolicy, apply a buffer limit of maxRequestSize: '1024' to the httpbin app. This setting limits the request payload to 1024 bytes.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: transformation-buffer-limit
      namespace: httpbin
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: httpbin
      buffer:
        maxRequestSize: '1024'
    EOF
  4. To test the buffer limit, create a payload in a temp file that exceeds the 1Ki buffer limit.

    dd if=/dev/zero bs=2048 count=1 | base64 -w 0 > /tmp/large_payload_2k.txt
  5. Send a request to the /anything httpbin path with the large payload. Verify that the request fails with a connection error or timeout, indicating that the buffer limit was exceeded.

    curl -vik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
    -H "host: www.example.com:8080" \
    -H "Content-Type: text/plain" \
    -d "{\"payload\": \"$(< /tmp/large_payload_2k.txt)\"}"

  6. Test the buffer limit again by sending a request with a small payload, "hello world". This request succeeds with a normal response from httpbin because the payload size is within the 2Ki limit.

    curl -vik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
       -H "host: www.example.com:8080" \
       -H "Content-Type: application/json" \
       -d "{\"payload\":  \"hello world\"}" 

    Example output:

    {
      "args": {},
      "data": "{\"payload\": \"hello world\"}",
      "files": {},
      "form": {},
      "headers": {
        ...
      },
      "json": {
        "payload": "hello world"
      },
      "method": "POST",
      "origin": "...",
      "url": "https://$INGRESS_GW_ADDRESS:8080/anything"
    }

Move the buffer filter before body-reading filters

By default, the buffer filter runs at a fixed position after authentication, authorization, and rate limiting, but before routing. This default position is not one of the values that you can set in the buffer.filterStage.stage and buffer.filterStage.predicate fields. To keep the buffer filter at the default position, omit the buffer.filterStage block entirely.

The following stages and predicates are supported to determine the position of the buffer filter in the Envoy filter chain.

buffer.filterStage.stage buffer.filterStage.predicate
  • Fault: Earliest stage. The buffer filter runs before fault injection. Placing the buffer filter here also moves request decompression ahead of fault injection, CORS, and any ExtProc filter staged at Fault, for every route on the listener.
  • AuthN: Authentication stage.
  • AuthZ: Authorization stage.
  • RateLimit: Rate limiting stage.
  • Route: Final processing stage before the request leaves the gateway proxy.
  • Before: Runs the buffer filter before the selected stage.
  • During: Runs the buffer filter during the selected stage. This setting is the default when the predicate field is not set.
  • After: Runs the buffer filter after the selected stage.

Note

Do not set buffer.filterStage together with buffer.disable. The API rejects a buffer policy that sets both fields.

Do not set buffer.filterStage.weight to a nonzero value. The field defaults to 0. A filter chain has only one buffer filter. If you have multiple TrafficPolicy resources that request different stages, the earliest stage is configured in the buffer filter. All other stages are ignored. Because the weight field cannot break a tie in such cases, the API rejects a nonzero value.

Some filters in the filter chain read or hold the request body before the buffer filter ever sees it, such as external auth with request body checks, ExtProc, or a request transformation. When one of these filters reads the body, the maxRequestSize setting on the kgateway resource cannot be enforced, because the buffer filter never receives the body to measure it. You can set the buffer.filterStage field to move the buffer filter to an earlier position in the filter chain to place it ahead of the filter that reads the body. This way, you can reject oversized messages before they reach the extauth service.

Because kgateway installs one buffer filter per filter chain, this placement applies to every route on the listener that the TrafficPolicy targets, not only the route that is named in the targetRefs block. If TrafficPolicy resources on the same filter chain ask for different stages, the earliest requested stage wins for the whole chain. If routes on the same listener need different buffer filter placements, serve them from separate listeners.

The following example uses a separate route and hostname so that the buffer and transformation policies from the earlier sections do not interfere with it.

  1. Deploy an external authorization service, a Service, and a GatewayExtension in the httpbin namespace, so that no cross-namespace ReferenceGrant is required. This example reuses the sample service from Bring your own external authorization service, which allows any request that carries the x-ext-authz: allow header.

    kubectl apply -f- <<EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ext-authz
      namespace: httpbin
      labels:
        app: ext-authz
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: ext-authz
      template:
        metadata:
          labels:
            app: ext-authz
        spec:
          containers:
          - image: gcr.io/istio-testing/ext-authz:1.25-dev
            name: ext-authz
            ports:
            - containerPort: 9000
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: ext-authz
      namespace: httpbin
      labels:
        app: ext-authz
    spec:
      ports:
      - port: 4444
        targetPort: 9000
        protocol: TCP
        appProtocol: kubernetes.io/h2c
      selector:
        app: ext-authz
    ---
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: GatewayExtension
    metadata:
      name: basic-ext-auth-buffer
      namespace: httpbin
    spec:
      type: ExtAuth
      extAuth:
        grpcService:
          backendRef:
            name: ext-authz
            port: 4444
    EOF
  2. Create an HTTPRoute with its own hostname, and a TrafficPolicy that pairs extAuth.withRequestBody with buffer.maxRequestSize. Do not set buffer.filterStage yet.

    kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: bufferedroute
      namespace: httpbin
    spec:
      parentRefs:
      - name: http
        namespace: kgateway-system
      hostnames:
      - "bufferedroute.com"
      rules:
      - backendRefs:
        - name: httpbin
          port: 8000
    ---
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: bufferedroute-policy
      namespace: httpbin
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: bufferedroute
      extAuth:
        extensionRef:
          name: basic-ext-auth-buffer
        withRequestBody:
          maxRequestBytes: 8192
      buffer:
        maxRequestSize: "1024"
    EOF
  3. Send an oversized request. The external authorization service allows any request that carries the x-ext-authz: allow header, so a rejection can only come from the buffer filter. Verify that the request succeeds even though the body is larger than the configured maxRequestSize. The external authorization service buffers the body ahead of the buffer filter’s default placement in the filter chain, so the configured maxRequestSize never gets a chance to reject it.

    curl -sik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
    -H "host: bufferedroute.com:8080" \
    -H "x-ext-authz: allow" \
    --data-binary @/tmp/large_payload_2k.txt

    Example output:

    HTTP/1.1 200 OK
    access-control-allow-credentials: true
    access-control-allow-origin: *
    content-type: application/json; encoding=utf-8
    
  4. Add the buffer.filterStage field to the same TrafficPolicy to move the buffer filter before the AuthN stage, ahead of external auth.

    kubectl apply -f- <<EOF
    apiVersion: gateway.kgateway.dev/v1alpha1
    kind: TrafficPolicy
    metadata:
      name: bufferedroute-policy
      namespace: httpbin
    spec:
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: bufferedroute
      extAuth:
        extensionRef:
          name: basic-ext-auth-buffer
        withRequestBody:
          maxRequestBytes: 8192
      buffer:
        maxRequestSize: "1024"
        filterStage:
          stage: AuthN
          predicate: Before
    EOF
  5. Send the same oversized request again. The buffer filter now runs before external auth, so it sees the body first and rejects it.

    curl -sik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
    -H "host: bufferedroute.com:8080" \
    -H "x-ext-authz: allow" \
    --data-binary @/tmp/large_payload_2k.txt

    Example output:

    HTTP/1.1 413 Payload Too Large
    content-length: 17
    content-type: text/plain
  6. Send a small request, "hello world", to confirm that the route still accepts requests within the limit.

    curl -sik -X POST http://$INGRESS_GW_ADDRESS:8080/anything \
       -H "host: bufferedroute.com:8080" \
       -H "x-ext-authz: allow" \
       -d "{\"payload\": \"hello world\"}"

    Example output:

    HTTP/1.1 200 OK
    ...
      "json": {
        "payload": "hello world"
      }
    

Cleanup

You can remove the resources that you created in this guide.
kubectl delete TrafficPolicy transformation-buffer-body -n httpbin --ignore-not-found
kubectl delete TrafficPolicy transformation-buffer-limit -n httpbin --ignore-not-found
kubectl delete TrafficPolicy bufferedroute-policy -n httpbin --ignore-not-found
kubectl delete httproute/bufferedroute gatewayextension/basic-ext-auth-buffer deployment/ext-authz service/ext-authz -n httpbin --ignore-not-found
kubectl delete listenerpolicy bufferlimits -n kgateway-system --ignore-not-found
Was this page helpful?