exit-transformer
The exit-transformer plugin supports the customization of gateway responses based on the status codes, headers, and bodies returned from API7 plugins. When configured as a global plugin, it also supports the response customization when a route that does not exist is requested.
The transformation logics are defined in the plugin using Lua functions, following the syntax:
return (function(code, body, header) if {{ condition }} then return {{ modified_resp }} end return code, body, header end)(...)
Examples
The examples below demonstrate how you can use exit-transformer for different scenarios.
Modify 404 Route Not Found Response
The following example demonstrates how you can use the plugin to update the 404 Not Found response code and header when the route does not exist. In this case, the plugin needs to be configured as a global rule plugin.
Create a global rule with the exit-transformer plugin, in which the function updates the response status code to 405 and adds a custom X-Custom-Header header if the original status code was 404:
- Admin API
- ADC
- Ingress Controller
curl -i "http://127.0.0.1:9180/apisix/admin/global_rules" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "transform-404-not-found",
"plugins": {
"exit-transformer": {
"functions": ["return (function(code, body, header) header = header or {} if code == 404 then header[\"X-Custom-Header\"] = \"Modified\" return 405, body, header end return code, body, header end)(...)"]
}
}
}'
global_rules:
- id: transform-404-not-found
plugins:
exit-transformer:
functions:
- "return (function(code, body, header) header = header or {} if code == 404 then header[\"X-Custom-Header\"] = \"Modified\" return 405, body, header end return code, body, header end)(...)"
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
namespace: aic
name: apisix-config
spec:
provider:
type: ControlPlane
controlPlane:
# add your control plane connection configuration here
# ....
plugins:
- name: exit-transformer
enabled: true
config:
functions:
- "return (function(code, body, header) header = header or {} if code == 404 then header[\"X-Custom-Header\"] = \"Modified\" return 405, body, header end return code, body, header end)(...)"
apiVersion: apisix.apache.org/v2
kind: ApisixGlobalRule
metadata:
namespace: aic
name: transform-404-not-found
spec:
ingressClassName: apisix
plugins:
- name: exit-transformer
enable: true
config:
functions:
- "return (function(code, body, header) header = header or {} if code == 404 then header[\"X-Custom-Header\"] = \"Modified\" return 405, body, header end return code, body, header end)(...)"
Apply the configuration:
kubectl apply -f exit-transformer-ic.yaml
Send a request to a route that does not exist:
curl -i "http://127.0.0.1:9080/non-existent"
You should receive an HTTP/1.1 405 Not Allowed response and observe the X-Custom-Header: Modified header.
Modify 401 Unauthorized Response for Failed Authentication
The following example demonstrates how you can use the plugin to update the 401 Unauthorized response when authentication fails.
- Admin API
- ADC
- Ingress Controller
Create a route with the exit-transformer plugin, in which the function updates the response status code to 402 if the original status code was 401; and enable key-auth:
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "transform-auth-route",
"uri": "/get",
"plugins": {
"exit-transformer": {
"functions": ["return (function(code, body, header) if code == 401 then return 402, body, header end return code, body, header end)(...)"]
},
"key-auth":{}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create a consumer john:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john"
}'
Configure key-auth credential for the consumer:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
Create a consumer with key-auth credential and a route with exit-transformer and key-auth plugins configured as such:
consumers:
- username: john
credentials:
- name: key-auth
type: key-auth
config:
key: john-key
services:
- name: httpbin
routes:
- name: transform-auth-route
uris:
- /get
plugins:
exit-transformer:
functions:
- "return (function(code, body, header) if code == 401 then return 402, body, header end return code, body, header end)(...)"
key-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
Create a consumer with key-auth credential and a route with exit-transformer and key-auth plugins configured as such:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: john
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: primary-cred
config:
key: john-key
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: exit-transformer-plugin-config
spec:
plugins:
- name: exit-transformer
config:
functions:
- "return (function(code, body, header) if code == 401 then return 402, body, header end return code, body, header end)(...)"
- name: key-auth
config:
_meta:
disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: transform-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /get
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: exit-transformer-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: john-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: transform-auth-route
spec:
ingressClassName: apisix
http:
- name: transform-auth-route
match:
paths:
- /get
methods:
- GET
upstreams:
- name: httpbin-external-domain
plugins:
- name: exit-transformer
enable: true
config:
functions:
- "return (function(code, body, header) if code == 401 then return 402, body, header end return code, body, header end)(...)"
- name: key-auth
enable: true
Apply the configuration:
kubectl apply -f exit-transformer-ic.yaml
Send a request to the route without the credential:
curl -i "http://127.0.0.1:9080/get"
You should receive an HTTP/1.1 402 Payment Required response for unauthorized access, where the response status code has been modified.
Modify Response Conditionally on Request Headers
The following example demonstrates how you can use the plugin to conditionally modify responses based on request headers.
Create a route with the exit-transformer plugin, in which the function updates the response status code by Content-Type header. If the header value is application/json and the original status code is 404, update the response status code to 405. Print warning messages inside and outside the condition evaluation for demonstration purposes.
- Admin API
- ADC
- Ingress Controller
curl -i "http://127.0.0.1:9180/apisix/admin/global_rules" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "transform-by-header-condition",
"plugins": {
"exit-transformer": {
"functions": [
"return
(function(code, body, header, ctx)
local core = require(\"apisix.core\")
local ct = core.request.headers(ctx)[\"Content-Type\"]
core.log.warn(\"exit transformer logics running outside the condition\")
if ct == \"application/json\" and code == 404 then
core.log.warn(\"exit transformer logics running inside the condition\")
return 405
end
return code, body, header
end)
(...)"
]
}
}
}'
global_rules:
- id: transform-by-header-condition
plugins:
exit-transformer:
functions:
- "return (function(code, body, header, ctx) local core = require(\"apisix.core\") local ct = core.request.headers(ctx)[\"Content-Type\"] core.log.warn(\"exit transformer logics running outside the condition\") if ct == \"application/json\" and code == 404 then core.log.warn(\"exit transformer logics running inside the condition\") return 405 end return code, body, header end)(...)"
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: GatewayProxy
metadata:
namespace: aic
name: apisix-config
spec:
provider:
type: ControlPlane
controlPlane:
# add your control plane connection configuration here
# ....
plugins:
- name: exit-transformer
config:
functions:
- "return (function(code, body, header, ctx) local core = require(\"apisix.core\") local ct = core.request.headers(ctx)[\"Content-Type\"] core.log.warn(\"exit transformer logics running outside the condition\") if ct == \"application/json\" and code == 404 then core.log.warn(\"exit transformer logics running inside the condition\") return 405 end return code, body, header end)(...)"
apiVersion: apisix.apache.org/v2
kind: ApisixGlobalRule
metadata:
namespace: aic
name: transform-by-header-condition
spec:
ingressClassName: apisix
plugins:
- name: exit-transformer
enable: true
config:
functions:
- "return (function(code, body, header, ctx) local core = require(\"apisix.core\") local ct = core.request.headers(ctx)[\"Content-Type\"] core.log.warn(\"exit transformer logics running outside the condition\") if ct == \"application/json\" and code == 404 then core.log.warn(\"exit transformer logics running inside the condition\") return 405 end return code, body, header end)(...)"
Apply the configuration:
kubectl apply -f exit-transformer-ic.yaml
Send a request to a route that does not exist, without any header:
curl -i "http://127.0.0.1:9080/non-existent"
You should receive an HTTP/1.1 404 Not Found response and see the following message in the log:
exit transformer logics running outside the condition
Send a request to the non-existent route with the JSON Content-Type header:
curl -i "http://127.0.0.1:9080/non-existent" -H "Content-Type: application/json"
You should receive an HTTP/1.1 405 Not Allowed response, where the response status code has been modified, and see the following message in the log:
exit transformer logics running outside the condition
exit transformer logics running inside the condition