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

spacewander 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 8f0b066  feat: faas plugin refactoring with url path forwarding (#5616)
8f0b066 is described below

commit 8f0b066c86257ad6af19f9b3b7e209ece95d17c9
Author: Bisakh <bisakhmonda...@gmail.com>
AuthorDate: Mon Nov 29 11:58:49 2021 +0530

    feat: faas plugin refactoring with url path forwarding (#5616)
---
 apisix/plugins/azure-functions.lua             |  98 ++----------------
 apisix/plugins/serverless/generic-upstream.lua | 135 +++++++++++++++++++++++++
 t/plugin/azure-functions.t                     | 115 +++++++++++++++++++++
 3 files changed, 261 insertions(+), 87 deletions(-)

diff --git a/apisix/plugins/azure-functions.lua 
b/apisix/plugins/azure-functions.lua
index 1597f2a..0b0e64d 100644
--- a/apisix/plugins/azure-functions.lua
+++ b/apisix/plugins/azure-functions.lua
@@ -14,30 +14,15 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
-local core = require("apisix.core")
-local http = require("resty.http")
 local plugin = require("apisix.plugin")
-local ngx  = ngx
-local plugin_name = "azure-functions"
+local plugin_name, plugin_version, priority = "azure-functions", 0.1, -1900
 
-local schema = {
+local azure_authz_schema = {
     type = "object",
     properties = {
-        function_uri = {type = "string"},
-        authorization = {
-            type = "object",
-            properties = {
-                apikey = {type = "string"},
-                clientid = {type = "string"}
-            }
-        },
-        timeout = {type = "integer", minimum = 100, default = 3000},
-        ssl_verify = {type = "boolean", default = true},
-        keepalive = {type = "boolean", default = true},
-        keepalive_timeout = {type = "integer", minimum = 1000, default = 
60000},
-        keepalive_pool = {type = "integer", minimum = 1, default = 5}
-    },
-    required = {"function_uri"}
+        apikey = {type = "string"},
+        clientid = {type = "string"}
+    }
 }
 
 local metadata_schema = {
@@ -48,31 +33,8 @@ local metadata_schema = {
     }
 }
 
-local _M = {
-    version = 0.1,
-    priority = -1900,
-    name = plugin_name,
-    schema = schema,
-    metadata_schema = metadata_schema
-}
-
-function _M.check_schema(conf, schema_type)
-    if schema_type == core.schema.TYPE_METADATA then
-        return core.schema.check(metadata_schema, conf)
-    end
-    return core.schema.check(schema, conf)
-end
-
-function _M.access(conf, ctx)
-    local uri_args = core.request.get_uri_args(ctx)
-    local headers = core.request.headers(ctx) or {}
-    local req_body, err = core.request.get_body()
-
-    if err then
-        core.log.error("error while reading request body: ", err)
-        return 400
-    end
-
+local function request_processor(conf, ctx, params)
+    local headers = params.headers or {}
     -- set authorization headers if not already set by the client
     -- we are following not to overwrite the authz keys
     if not headers["x-functions-key"] and
@@ -91,47 +53,9 @@ function _M.access(conf, ctx)
         end
     end
 
-    headers["host"] = nil
-    local params = {
-        method = ngx.req.get_method(),
-        body = req_body,
-        query = uri_args,
-        headers = headers,
-        keepalive = conf.keepalive,
-        ssl_verify = conf.ssl_verify
-    }
-
-    -- Keepalive options
-    if conf.keepalive then
-        params.keepalive_timeout = conf.keepalive_timeout
-        params.keepalive_pool = conf.keepalive_pool
-    end
-
-    local httpc = http.new()
-    httpc:set_timeout(conf.timeout)
-
-    local res, err = httpc:request_uri(conf.function_uri, params)
-
-    if not res or err then
-        core.log.error("failed to process azure function, err: ", err)
-        return 503
-    end
-
-    -- According to RFC7540 
https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2, endpoint
-    -- must not generate any connection specific headers for HTTP/2 requests.
-    local response_headers = res.headers
-    if ngx.var.http2 then
-        response_headers["Connection"] = nil
-        response_headers["Keep-Alive"] = nil
-        response_headers["Proxy-Connection"] = nil
-        response_headers["Upgrade"] = nil
-        response_headers["Transfer-Encoding"] = nil
-    end
-
-    -- setting response headers
-    core.response.set_header(response_headers)
-
-    return res.status, res.body
+    params.headers = headers
 end
 
-return _M
+
+return require("apisix.plugins.serverless.generic-upstream")(plugin_name,
+        plugin_version, priority, request_processor, azure_authz_schema, 
metadata_schema)
diff --git a/apisix/plugins/serverless/generic-upstream.lua 
b/apisix/plugins/serverless/generic-upstream.lua
new file mode 100644
index 0000000..0ae59b6
--- /dev/null
+++ b/apisix/plugins/serverless/generic-upstream.lua
@@ -0,0 +1,135 @@
+--
+-- 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 ngx  = ngx
+local require = require
+local type = type
+local string = string
+
+return function(plugin_name, version, priority, request_processor, 
authz_schema, metadata_schema)
+    local core = require("apisix.core")
+    local http = require("resty.http")
+    local url = require("net.url")
+
+    if request_processor and type(request_processor) ~= "function" then
+        return "Failed to generate plugin due to invalid header processor 
type, " ..
+                    "expected: function, received: " .. type(request_processor)
+    end
+
+    local schema = {
+        type = "object",
+        properties = {
+            function_uri = {type = "string"},
+            authorization = authz_schema,
+            timeout = {type = "integer", minimum = 100, default = 3000},
+            ssl_verify = {type = "boolean", default = true},
+            keepalive = {type = "boolean", default = true},
+            keepalive_timeout = {type = "integer", minimum = 1000, default = 
60000},
+            keepalive_pool = {type = "integer", minimum = 1, default = 5}
+        },
+        required = {"function_uri"}
+    }
+
+    local _M = {
+        version = version,
+        priority = priority,
+        name = plugin_name,
+        schema = schema,
+        metadata_schema = metadata_schema
+    }
+
+    function _M.check_schema(conf, schema_type)
+        if schema_type == core.schema.TYPE_METADATA then
+            return core.schema.check(metadata_schema, conf)
+        end
+        return core.schema.check(schema, conf)
+    end
+
+    function _M.access(conf, ctx)
+        local uri_args = core.request.get_uri_args(ctx)
+        local headers = core.request.headers(ctx) or {}
+
+        local req_body, err = core.request.get_body()
+
+        if err then
+            core.log.error("error while reading request body: ", err)
+            return 400
+        end
+
+        -- forward the url path came through the matched uri
+        local url_decoded = url.parse(conf.function_uri)
+        local path = url_decoded.path or "/"
+
+        if ctx.curr_req_matched and ctx.curr_req_matched[":ext"] then
+            local end_path = ctx.curr_req_matched[":ext"]
+
+            if path:byte(-1) == string.byte("/") or end_path:byte(1) == 
string.byte("/") then
+                path = path .. end_path
+            else
+                path = path .. "/" .. end_path
+            end
+        end
+
+
+        headers["host"] = url_decoded.host
+        local params = {
+            method = ngx.req.get_method(),
+            body = req_body,
+            query = uri_args,
+            headers = headers,
+            path = path,
+            keepalive = conf.keepalive,
+            ssl_verify = conf.ssl_verify
+        }
+
+        -- Keepalive options
+        if conf.keepalive then
+            params.keepalive_timeout = conf.keepalive_timeout
+            params.keepalive_pool = conf.keepalive_pool
+        end
+
+        -- modify request info (if required)
+        request_processor(conf, ctx, params)
+
+        local httpc = http.new()
+        httpc:set_timeout(conf.timeout)
+
+        local res, err = httpc:request_uri(conf.function_uri, params)
+
+        if not res or err then
+            core.log.error("failed to process ", plugin_name, ", err: ", err)
+            return 503
+        end
+
+        -- According to RFC7540 
https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2,
+        -- endpoint must not generate any connection specific headers for 
HTTP/2 requests.
+        local response_headers = res.headers
+        if ngx.var.http2 then
+            response_headers["Connection"] = nil
+            response_headers["Keep-Alive"] = nil
+            response_headers["Proxy-Connection"] = nil
+            response_headers["Upgrade"] = nil
+            response_headers["Transfer-Encoding"] = nil
+        end
+
+        -- setting response headers
+        core.response.set_header(response_headers)
+
+        return res.status, res.body
+    end
+
+    return _M
+end
diff --git a/t/plugin/azure-functions.t b/t/plugin/azure-functions.t
index ea4d064..af58913 100644
--- a/t/plugin/azure-functions.t
+++ b/t/plugin/azure-functions.t
@@ -42,6 +42,24 @@ add_block_preprocessor(sub {
             }
         }
 
+        location  /api {
+           content_by_lua_block {
+                ngx.say("invocation /api successful")
+            }
+        }
+
+        location /api/httptrigger {
+           content_by_lua_block {
+                ngx.say("invocation /api/httptrigger successful")
+            }
+        }
+
+        location /api/http/trigger {
+           content_by_lua_block {
+                ngx.say("invocation /api/http/trigger successful")
+            }
+        }
+
         location /azure-demo {
             content_by_lua_block {
                 $inside_lua_block
@@ -375,3 +393,100 @@ ngx.say("Authz-Header - " .. headers["x-functions-key"] 
or "")
 passed
 passed
 Authz-Header - metadata_key
+
+
+
+=== TEST 10: check if url path being forwarded correctly by creating a semi 
correct path uri
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            -- creating a semi path route
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "plugins": {
+                            "azure-functions": {
+                                "function_uri": "http://localhost:8765/api";
+                            }
+                        },
+                        "uri": "/azure/*"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say("fail")
+                return
+            end
+
+            ngx.say(body)
+
+            local code, _, body = t("/azure/httptrigger", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- response_body
+passed
+invocation /api/httptrigger successful
+
+
+
+=== TEST 11: check multilevel url path forwarding
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, _, body = t("/azure/http/trigger", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- response_body
+invocation /api/http/trigger successful
+
+
+
+=== TEST 12: check url path forwarding containing multiple slashes
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, _, body = t("/azure///http////trigger", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- response_body
+invocation /api/http/trigger successful
+
+
+
+=== TEST 13: check url path forwarding with no excess path
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, _, body = t("/azure/", "GET")
+             if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+            ngx.print(body)
+        }
+    }
+--- response_body
+invocation /api successful

Reply via email to