Skip to main content
Version: 3.17

Serverless Functions (serverless)

Description#

The serverless functions consist of two plugins, serverless-pre-function and serverless-post-function. These plugins enable the execution of user-defined logic at the beginning and end of the execution phases the functions hook to.

Attributes#

NameTypeRequiredDefaultValid valuesDescription
phasestringFalse"access"["rewrite", "access", "header_filter", "body_filter", "log", "before_proxy"]Phase before or after which the serverless function is executed.
functionsarray[string]TrueList of functions that are executed sequentially.

Tips for Writing Functions#

Only Lua functions are allowed in the serverless plugins and not other Lua code.

For example, anonymous functions are legal:

return function()
ngx.log(ngx.ERR, 'one')
end

Closures are also legal:

local count = 1
return function()
count = count + 1
ngx.say(count)
end

But code other than functions are illegal:

local count = 1
ngx.say(count)

Examples#

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')

The examples below demonstrate how you can configure the serverless-pre-function and serverless-post-function plugins for different scenarios.

Log Information before and after a Phase#

The example below demonstrates how you can configure the serverless plugins to execute custom logics to log information to error logs before and after the rewrite phase.

Create a Route as such:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "serverless-pre-route",
"uri": "/anything",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions" : [
"return function() ngx.log(ngx.ERR, \"serverless pre function\"); end"
]
},
"serverless-post-function": {
"phase": "rewrite",
"functions" : [
"return function(conf, ctx) ngx.log(ngx.ERR, \"match uri \", ctx.curr_req_matched and ctx.curr_req_matched._path); end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

Send the request to the Route:

curl -i "http://127.0.0.1:9080/anything"

You should receive an HTTP/1.1 200 OK response and see the following entries in the error log:

2024/05/09 15:07:09 [error] 51#51: *3963 [lua] [string "return function() ngx.log(ngx.ERR, "serverles..."]:1: func(): serverless pre function, client: 172.21.0.1, server: _, request: "GET /anything HTTP/1.1", host: "127.0.0.1:9080"
2024/05/09 15:16:58 [error] 50#50: *9343 [lua] [string "return function(conf, ctx) ngx.log(ngx.ERR, "..."]:1: func(): match uri /anything, client: 172.21.0.1, server: _, request: "GET /anything HTTP/1.1", host: "127.0.0.1:9080"

The first entry is added by the pre-function and the second entry is added by the post-function.

Register Custom Variables#

The example below demonstrates how you can register custom built-in variables using the serverless plugins and use the newly created variable in logs.

info

This example cannot be completed with the Ingress Controller because it does not support configuring Route labels.

Start an example rsyslog server:

docker run -d -p 514:514 --name example-rsyslog-server rsyslog/syslog_appliance_alpine

Create a Service with a serverless function to register a custom variable a6_route_labels, enable a logging plugin to later log the custom variable, and configure an upstream:

curl "http://127.0.0.1:9180/apisix/admin/services" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id":"srv_custom_var",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function() local core = require \"apisix.core\" core.ctx.register_var(\"a6_route_labels\", function(ctx) local route = ctx.matched_route and ctx.matched_route.value if route and route.labels then return route.labels end return nil end); end"
]
},
"syslog": {
"host" : "172.0.0.1",
"port" : 514,
"flush_limit" : 1
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
}
}
}'

Next, update the log format for all syslog instances with the new variable by configuring the Plugin metadata:

curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/syslog" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"log_format": {
"host": "$host",
"client_ip": "$remote_addr",
"labels": "$a6_route_labels"
}
}'

Finally, create a Route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id":"route_custom_var",
"uri":"/get",
"service_id": "srv_custom_var",
"labels": {
"key": "test_a6_route_labels"
}
}'

To verify the variable registration, send a request to the Route:

curl "http://127.0.0.1:9080/get"

You should see a log entry in your syslog server similar to the following:

{
"host":"127.0.0.1",
"route_id":"route_custom_var",
"client_ip":"172.19.0.1",
"labels":{
"key":"test_a6_route_labels"
},
"service_id":"srv_custom_var"
}

This verifies the custom variable was registered and it logs the labels information in a Route successfully.

Modify a Specific Field in Response Body#

The example below demonstrates how you can use the serverless plugins to remove a specific field from a JSON response body.

Before proceeding with the removal, first configure a Route as follows to see the unmodified response:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id":"serverless-remove-body-info",
"uri": "/get",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

Send a request to the Route:

curl "http://127.0.0.1:9080/get"

You should see a response similar to the following with your host and proxy's IP information:

{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.4.0",
"X-Amzn-Trace-Id": "Root=1-663db30f-51448a1b635f2f4338a4fcfc",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "172.19.0.1, 43.252.208.84",
"url": "http://127.0.0.1/get"
}

To remove the origin field from the response, update the Route with serverless plugins:

curl "http://127.0.0.1:9180/apisix/admin/routes/serverless-remove-body-info" -X PATCH \
-H "X-API-KEY: ${admin_key}" \
-d '{
"plugins": {
"serverless-pre-function": {
"phase": "header_filter",
"functions" : [
"return function(conf, ctx) local core = require(\"apisix.core\") core.response.clear_header_as_body_modified() end"
]
},
"serverless-post-function": {
"phase": "body_filter",
"functions" : [
"return function(conf, ctx) local cjson = require(\"cjson\") local core = require(\"apisix.core\") local body = core.response.hold_body_chunk(ctx) if not body then return end body = cjson.decode(body) body.origin = nil body = cjson.encode(body) ngx.arg[1] = body end"
]
}
}
}'

The pre-function calls clear_header_as_body_modified to clear body-related response headers such as Content-Length. The post-function collects the response body with hold_body_chunk, decodes the JSON payload, removes the origin field, and writes the updated body back to the response.

Send another request to the Route:

curl "http://127.0.0.1:9080/get"

You should see a response without the origin information:

{
"url":"http://127.0.0.1/get",
"args":{},
"headers":{
"X-Forwarded-Host":"127.0.0.1",
"Host":"127.0.0.1",
"Accept":"*/*",
"User-Agent":"curl/8.4.0",
"X-Amzn-Trace-Id":"Root=1-663db276-1c15276864294d963c6e1755"
}
}

For simpler response modifications, such as modifying HTTP status codes, request headers, or the entire response body, please use the response-rewrite plugin.