skills/moonming/a6/a6-plugin-response-rewrite

a6-plugin-response-rewrite

SKILL.md

a6-plugin-response-rewrite

Overview

The response-rewrite plugin rewrites response attributes before APISIX returns the response to the client. You can change the HTTP status code, response headers, and response body — either unconditionally or based on matching conditions. It runs in the header_filter and body_filter phases, so it executes even if earlier plugins (like auth) call ngx.exit.

When to Use

  • Override the HTTP status code returned to clients
  • Add, set, or remove response headers (e.g., security headers, CORS)
  • Replace the entire response body (static content, error messages)
  • Use regex filters to modify parts of the response body
  • Apply response changes conditionally (e.g., only for certain status codes)
  • Serve base64-decoded binary content (images, protobuf)

Plugin Configuration Reference (Route/Service)

Field Type Required Default Description
status_code integer No New HTTP status code (200–598). If unset, original status is used.
body string No New response body. Content-Length is automatically reset. Cannot be used with filters.
body_base64 boolean No false Decode body from base64 before sending. Only decodes plugin-configured body, not upstream response.
headers object No Header manipulation with set, add, and remove fields.
headers.set object No Set (overwrite) response headers. Key-value pairs. Supports Nginx variables.
headers.add array[string] No Append response headers. Format: ["Name: value", ...]. Adds even if header exists.
headers.remove array[string] No Remove response headers. List of header names to strip.
vars array[array] No Conditional matching using lua-resty-expr syntax. Plugin only executes when conditions match.
filters array[object] No Regex filters to modify response body. Cannot be used with body.
filters[].regex string Yes Regex pattern to match in response body.
filters[].replace string Yes Replacement content.
filters[].scope string No "once" "once" = first match only. "global" = all matches.
filters[].options string No "jo" Regex options. See ngx.re.match.

Mutual exclusion: body and filters cannot be used together.

Step-by-Step: Enable response-rewrite on a Route

1. Add security response headers

a6 route create -f - <<'EOF'
{
  "id": "security-headers",
  "uri": "/api/*",
  "plugins": {
    "response-rewrite": {
      "headers": {
        "set": {
          "X-Content-Type-Options": "nosniff",
          "X-Frame-Options": "DENY",
          "Strict-Transport-Security": "max-age=31536000; includeSubDomains"
        },
        "remove": ["Server", "X-Powered-By"]
      }
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "backend:8080": 1
    }
  }
}
EOF

2. Custom error response body

a6 route create -f - <<'EOF'
{
  "id": "custom-error",
  "uri": "/maintenance/*",
  "plugins": {
    "response-rewrite": {
      "status_code": 503,
      "body": "{\"error\": \"Service under maintenance\", \"retry_after\": 300}",
      "headers": {
        "set": {
          "Content-Type": "application/json",
          "Retry-After": "300"
        }
      }
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "backend:8080": 1
    }
  }
}
EOF

3. Conditional rewrite (only for 200 responses)

a6 route create -f - <<'EOF'
{
  "id": "conditional-rewrite",
  "uri": "/api/*",
  "plugins": {
    "response-rewrite": {
      "headers": {
        "set": {
          "Cache-Control": "public, max-age=3600"
        }
      },
      "vars": [["status", "==", 200]]
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "backend:8080": 1
    }
  }
}
EOF

Common Patterns

Regex body filter (replace text globally)

Replace internal hostnames in response body with public URLs:

{
  "plugins": {
    "response-rewrite": {
      "filters": [
        {
          "regex": "http://internal\\.service\\.local",
          "scope": "global",
          "replace": "https://api.example.com"
        }
      ]
    }
  }
}

Multiple regex filters

{
  "plugins": {
    "response-rewrite": {
      "filters": [
        {
          "regex": "X-Amzn-Trace-Id",
          "scope": "global",
          "replace": "X-Trace-Id"
        },
        {
          "regex": "\"debug\":\\s*true",
          "scope": "global",
          "replace": "\"debug\": false"
        }
      ]
    }
  }
}

Base64 body (serve binary content)

{
  "plugins": {
    "response-rewrite": {
      "status_code": 200,
      "body": "SGVsbG8gV29ybGQ=",
      "body_base64": true,
      "headers": {
        "set": {
          "Content-Type": "text/plain"
        }
      }
    }
  }
}

Returns decoded body: Hello World

Add dynamic server info headers

{
  "plugins": {
    "response-rewrite": {
      "headers": {
        "set": {
          "X-Served-By": "$balancer_ip:$balancer_port",
          "X-Request-Id": "$request_id"
        }
      }
    }
  }
}

Conditional: only rewrite 5xx errors

{
  "plugins": {
    "response-rewrite": {
      "body": "{\"error\": \"internal server error\", \"code\": 500}",
      "headers": {
        "set": {
          "Content-Type": "application/json"
        }
      },
      "vars": [["status", ">=", 500]]
    }
  }
}

Important Notes

  • Execution phase: Runs in header_filter and body_filter phases, which means it executes even if earlier plugins (auth, rate-limiting) reject the request via ngx.exit.
  • Header manipulation order: addremoveset (same as proxy-rewrite).
  • Body and filters are mutually exclusive: Cannot set both body and filters.
  • base64 decoding: Only applies to the plugin-configured body field, NOT to the upstream response body.

Troubleshooting

Symptom Cause Fix
Body not changed body and filters both set Use only one: body for full replacement, filters for partial
Status code unchanged status_code not in valid range Must be 200–598
Regex filter not matching Pattern syntax or escaping issue Test regex; use "jo" options for UTF-8 support
Headers still present after remove Header name case mismatch Header names are case-insensitive; check exact spelling
Vars condition not working Incorrect operator or type Use lua-resty-expr syntax: ["status", "==", 200] (integer, not string)
Rewrite runs on auth failures Expected behavior Plugin runs in filter phases regardless of earlier ngx.exit calls
Content-Length mismatch Manual Content-Length header Don't set Content-Length manually — plugin resets it automatically

Config Sync Example

version: "1"
routes:
  - id: response-transform
    uri: /api/*
    plugins:
      response-rewrite:
        headers:
          set:
            X-Content-Type-Options: "nosniff"
            X-Frame-Options: "DENY"
          remove:
            - Server
            - X-Powered-By
        vars:
          - ["status", "==", 200]
    upstream_id: api-backend
upstreams:
  - id: api-backend
    type: roundrobin
    nodes:
      "backend:8080": 1
Weekly Installs
1
Repository
moonming/a6
First Seen
7 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1