🤔 Introducing APISIX AI Gateway – Built for LLMs and AI workloads. Learn More

request-decompress

Description

The request-decompress Plugin decompresses a request body sent with a Content-Encoding of gzip or deflate, which RFC 9110 allows, and rewrites the request with the plain body. Every Plugin that runs after it reads the decompressed content, so oas-validator, request-validation, body-transformer, chaitin-waf and the logging Plugins work on a compressed request the same way they do on a plain one, unless forward_compressed is enabled.

A request without a Content-Encoding header passes through untouched and its body is never read, so the same Route serves both compressed and uncompressed clients.

A gzip body may carry several members concatenated, which RFC 1952 section 2.2 allows. Every member is decompressed and max_req_body_size bounds their total.

By default the Upstream also receives the decompressed body and the Content-Encoding header is removed. Set forward_compressed to true to send a compressed body Upstream instead, while the Plugins that run before the request is proxied still read the plain one.

Attributes

NameTypeRequiredDefaultValid valuesDescription
max_req_body_sizeintegerFalse1048576>= 1Maximum size in bytes of the request body, applied both to the body as it arrives and to the decompressed result. A body beyond this size is rejected with a 413. The limit is applied while decompressing, so an oversized payload is never buffered in full.
forward_compressedbooleanFalsefalseIf true, the Upstream receives a compressed body instead of the decompressed one.

Rejections

ConditionStatusNotes
A coding other than gzip, deflate or identity, such as br415The response carries an Accept-Encoding: gzip, deflate header, as RFC 9110 recommends.
A malformed compressed body, or bytes trailing the last gzip member400
A body beyond max_req_body_size, as received or once decompressed413

Examples

The examples below demonstrate how you can configure request-decompress for different scenarios.

NOTE

You can fetch the admin_key from config.yaml and save to an environment variable with the following command:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

Decompress a request body

The following example demonstrates how to decompress a gzip request body and forward the plain body to the Upstream.

Create a Route with the request-decompress Plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "request-decompress-route",
    "uri": "/anything",
    "plugins": {
      "request-decompress": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Send a gzip compressed body to the Route:

echo -n '{"name":"world"}' | gzip | curl "http://127.0.0.1:9080/anything" -X POST \
  -H "Content-Type: application/json" \
  -H "Content-Encoding: gzip" \
  --data-binary @-

You should see a response showing that the Upstream received the plain body and no Content-Encoding header:

{
  "data": "{\"name\":\"world\"}",
  "headers": {
    "Content-Type": "application/json",
    ...
  },
  ...
}

Validate a compressed request body against an OpenAPI specification

The following example demonstrates how to validate the decompressed body with the oas-validator Plugin, so that an invalid request is rejected before it reaches the Upstream.

Create a Route with both Plugins:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "request-decompress-route",
    "uri": "/anything",
    "plugins": {
      "request-decompress": {},
      "oas-validator": {
        "spec": "{\"openapi\":\"3.0.2\",\"info\":{\"title\":\"test\",\"version\":\"1.0\"},\"paths\":{\"/anything\":{\"post\":{\"requestBody\":{\"required\":true,\"content\":{\"application/json\":{\"schema\":{\"type\":\"object\",\"required\":[\"name\"],\"properties\":{\"name\":{\"type\":\"string\"}}}}}},\"responses\":{\"200\":{\"description\":\"ok\"}}}}}}"
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Send a gzip compressed body that does not match the specification:

echo -n '{"nickname":"world"}' | gzip | curl -i "http://127.0.0.1:9080/anything" -X POST \
  -H "Content-Type: application/json" \
  -H "Content-Encoding: gzip" \
  --data-binary @-

You should receive an HTTP/1.1 400 Bad Request response, showing that the specification was checked against the decompressed body.

Forward a compressed body to the Upstream

The following example demonstrates how to keep the Upstream receiving a compressed body while the Plugins on the Route still read the plain one.

Create a Route with forward_compressed enabled:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "request-decompress-route",
    "uri": "/anything",
    "plugins": {
      "request-decompress": {
        "forward_compressed": true
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

Send a gzip compressed body to the Route:

echo -n '{"name":"world"}' | gzip | curl "http://127.0.0.1:9080/anything" -X POST \
  -H "Content-Type: application/json" \
  -H "Content-Encoding: gzip" \
  --data-binary @-

The Upstream receives the body still compressed, with the Content-Encoding header restored. When no Plugin modified the body, the bytes are forwarded exactly as the client sent them. When a Plugin such as body-transformer rewrote the body, it is compressed again with the coding the client used, and the header names the coding that was applied.

Plugin order

request-decompress runs in the rewrite phase with a priority of 2850. That places it after the Plugins that reject a request cheaply, so a blocked request is never decompressed, and before every Plugin that reads the request body.

A Route that configures the Plugin takes precedence over a Global Rule that also configures it, so the request body is handled once with the Route’s settings.

Two consequences are worth knowing, and both can be changed with _meta.priority:

  • hmac-auth with validate_request_body enabled hashes the decompressed body on a Route that also enables this Plugin. A client that signs the compressed bytes needs hmac-auth configured with a priority above 2850.
  • Decompression happens before authentication, so an unauthenticated client can cause decompression work. It is bounded by max_req_body_size and by client_max_body_size.

With forward_compressed enabled, the compressed body is restored just before the request is proxied, so the Plugins that read the body in the log phase see the compressed bytes. include_req_body and a request_body entry in a custom log format record them as they went Upstream.