This is an automated email from the ASF dual-hosted git repository.

membphis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 240942e  feature: implemented `request-id` plugin to uniquely track 
requests in APISIX (#2026)
240942e is described below

commit 240942e9416ef0cd771e8cd6f2f7336553b45de2
Author: Nirojan Selvanathan <sshn...@gmail.com>
AuthorDate: Wed Aug 12 17:09:39 2020 +0200

    feature: implemented `request-id` plugin to uniquely track requests in 
APISIX (#2026)
    
    fix #2022
---
 apisix/plugins/request-id.lua |  68 ++++++++
 conf/config.yaml              |   1 +
 doc/plugins/request-id.md     |  90 ++++++++++
 t/admin/plugins.t             |   2 +-
 t/debug/debug-mode.t          |   1 +
 t/plugin/request-id.t         | 376 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 537 insertions(+), 1 deletion(-)

diff --git a/apisix/plugins/request-id.lua b/apisix/plugins/request-id.lua
new file mode 100644
index 0000000..0de07fa
--- /dev/null
+++ b/apisix/plugins/request-id.lua
@@ -0,0 +1,68 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+local core          = require("apisix.core")
+local plugin_name   = "request-id"
+local ngx           = ngx
+local uuid          = require("resty.jit-uuid")
+
+local schema = {
+    type = "object",
+    properties = {
+        header_name = {type = "string", default = "X-Request-Id"},
+        include_in_response = {type = "boolean", default = true}
+    }
+}
+
+
+local _M = {
+    version = 0.1,
+    priority = 11010,
+    name = plugin_name,
+    schema = schema,
+}
+
+
+function _M.check_schema(conf)
+    return core.schema.check(schema, conf)
+end
+
+
+function _M.rewrite(conf, ctx)
+    local headers = ngx.req.get_headers()
+    local uuid_val = uuid()
+    if not headers[conf.header_name] then
+        core.request.set_header(conf.header_name, uuid_val)
+    end
+
+    if conf.include_in_response then
+        ctx.x_request_id = uuid_val
+    end
+end
+
+
+function _M.header_filter(conf, ctx)
+    if not conf.include_in_response then
+        return
+    end
+
+    local headers = ngx.resp.get_headers()
+    if not headers[conf.header_name] then
+        core.response.set_header(conf.header_name, ctx.x_request_id)
+    end
+end
+
+return _M
diff --git a/conf/config.yaml b/conf/config.yaml
index aa0361f..047f775 100644
--- a/conf/config.yaml
+++ b/conf/config.yaml
@@ -178,6 +178,7 @@ plugins:                          # plugin list
   - request-validation
   - proxy-cache
   - proxy-mirror
+  - request-id
 
 stream_plugins:
   - mqtt-proxy
diff --git a/doc/plugins/request-id.md b/doc/plugins/request-id.md
new file mode 100644
index 0000000..0857e0e
--- /dev/null
+++ b/doc/plugins/request-id.md
@@ -0,0 +1,90 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+[Chinese](../zh-cn/plugins/request-validation.md)
+
+# Summary
+- [**Name**](#name)
+- [**Attributes**](#attributes)
+- [**How To Enable**](#how-to-enable)
+- [**Test Plugin**](#test-plugin)
+- [**Disable Plugin**](#disable-plugin)
+- [**Examples**](#examples)
+
+
+## Name
+
+`request-id` plugin adds a unique ID (UUID) to each request proxied through 
APISIX. This plugin can be used to track an
+API request. The plugin will not add a request id if the `header_name` is 
already present in the request.
+
+## Attributes
+
+|Name           |Requirement    |Description|
+|---------      |--------       |-----------|
+| header_name   |optional       |Request ID header name (default: 
X-Request-Id)|
+| include_in_response   |optional       |Option to include the unique request 
ID in the response header (default: true)|
+
+## How To Enable
+
+Create a route and enable the request-id plugin on the route:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/5 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/get",
+    "plugins": {
+        "request-id": {
+            "include_in_response": true
+        }
+    },
+    "upstream": {
+       "type": "roundrobin",
+       "nodes": {
+               "127.0.0.1:8080": 1
+       }
+    }
+}
+```
+
+## Test Plugin
+
+```shell
+$ curl -i http://127.0.0.1:9080/hello
+HTTP/1.1 200 OK
+```
+
+## Disable Plugin
+
+Remove the corresponding json configuration in the plugin configuration to 
disable the `request-validation`.
+APISIX plugins are hot-reloaded, therefore no need to restart APISIX.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/5 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/get",
+    "plugins": {
+    },
+    "upstream": {
+       "type": "roundrobin",
+       "nodes": {
+               "127.0.0.1:8080": 1
+       }
+    }
+}
+```
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 9d96556..d750390 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -30,7 +30,7 @@ __DATA__
 --- request
 GET /apisix/admin/plugins/list
 --- response_body_like eval
-qr/\["fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","limit-conn","limit-count","limit-req","node-status","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","tcp-logger","kafka-logger","syslog","udp-logger","zipkin","skywalking","serverless-
 [...]
+qr/\["request-id","fault-injection","serverless-pre-function","batch-requests","cors","ip-restriction","uri-blocker","request-validation","openid-connect","wolf-rbac","basic-auth","jwt-auth","key-auth","consumer-restriction","authz-keycloak","proxy-mirror","proxy-cache","proxy-rewrite","limit-conn","limit-count","limit-req","node-status","redirect","response-rewrite","grpc-transcode","prometheus","echo","http-logger","tcp-logger","kafka-logger","syslog","udp-logger","zipkin","skywalking"
 [...]
 --- no_error_log
 [error]
 
diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t
index 0b81bad..9f4f1a9 100644
--- a/t/debug/debug-mode.t
+++ b/t/debug/debug-mode.t
@@ -53,6 +53,7 @@ done
 --- grep_error_log eval
 qr/loaded plugin and sort by priority: [-\d]+ name: [\w-]+/
 --- grep_error_log_out
+loaded plugin and sort by priority: 11010 name: request-id
 loaded plugin and sort by priority: 11000 name: fault-injection
 loaded plugin and sort by priority: 10000 name: serverless-pre-function
 loaded plugin and sort by priority: 4010 name: batch-requests
diff --git a/t/plugin/request-id.t b/t/plugin/request-id.t
new file mode 100644
index 0000000..3fef3d8
--- /dev/null
+++ b/t/plugin/request-id.t
@@ -0,0 +1,376 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+log_level('debug');
+repeat_each(1);
+no_long_string();
+no_root_location();
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.request-id")
+            local ok, err = plugin.check_schema({})
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: wrong type
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.request-id")
+            local ok, err = plugin.check_schema({include_in_response = 
"bad_type"})
+            if not ok then
+                ngx.say(err)
+            end
+
+            ngx.say("done")
+        }
+    }
+--- request
+GET /t
+--- response_body
+property "include_in_response" validation failed: wrong type: expected 
boolean, got string
+done
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: add plugin with include_in_response true (default true)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "request-id": {
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                            "request-id": {
+                            }
+                        },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1982": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: check for request id in response header (default header name)
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local httpc = http.new()
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port .. 
"/opentracing"
+            local res, err = httpc:request_uri(uri,
+                {
+                    method = "GET",
+                    headers = {
+                        ["Content-Type"] = "application/json",
+                    }
+                })
+
+            if res.headers["X-Request-Id"] then
+                ngx.say("request header present")
+            else
+                ngx.say("failed")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+request header present
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: check for unique id
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local httpc = http.new()
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port .. 
"/opentracing"
+            local res1, err1 = httpc:request_uri(uri,
+                {
+                    method = "GET",
+                    headers = {
+                        ["Content-Type"] = "application/json",
+                    }
+                }
+            )
+            local res2, err2 = httpc:request_uri(uri,
+                {
+                    method = "GET",
+                    headers = {
+                        ["Content-Type"] = "application/json",
+                    }
+                }
+            )
+
+            -- ngx.say("res1: ", res1.headers["X-Request-Id"])
+            -- ngx.say("res2: ", res2.headers["X-Request-Id"])
+            if res1.headers["X-Request-Id"] == res2.headers["X-Request-Id"] 
then
+                ngx.say("ids not unique")
+            else
+                ngx.say("true")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+true
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: add plugin with custom header name
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "request-id": {
+                                "header_name": "Custom-Header-Name"
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                            "request-id": {
+                                "header_name": "Custom-Header-Name",
+                                "include_in_response": true
+                            }
+                        },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1982": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: check for request id in response header (custom header name)
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local httpc = http.new()
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port .. 
"/opentracing"
+            local res, err = httpc:request_uri(uri,
+                {
+                    method = "GET",
+                    headers = {
+                        ["Content-Type"] = "application/json",
+                    }
+                })
+
+            if res.headers["Custom-Header-Name"] then
+                ngx.say("request header present")
+            else
+                ngx.say("failed")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+request header present
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: add plugin with include_in_response false (default true)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "request-id": {
+                                "include_in_response": false
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "plugins": {
+                            "request-id": {
+                                "include_in_response": false
+                            }
+                        },
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:1982": 1
+                                },
+                                "type": "roundrobin"
+                            },
+                            "uri": "/opentracing"
+                        },
+                        "key": "/apisix/routes/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: check for request id is not present in the response header
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local httpc = http.new()
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port .. 
"/opentracing"
+            local res, err = httpc:request_uri(uri,
+                {
+                    method = "GET",
+                    headers = {
+                        ["Content-Type"] = "application/json",
+                    }
+                })
+
+            if not res.headers["X-Request-Id"] then
+                ngx.say("request header not present")
+            else
+                ngx.say("failed")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+request header not present
+--- no_error_log
+[error]

Reply via email to