Support IP Hash LB policy 23/33323/1
authorHuabingZhao <zhao.huabing@zte.com.cn>
Wed, 28 Feb 2018 03:10:50 +0000 (11:10 +0800)
committerHuabingZhao <zhao.huabing@zte.com.cn>
Wed, 28 Feb 2018 03:10:56 +0000 (11:10 +0800)
Issue-ID: MSB-154
Change-Id: I11b8e3a314c6045183971bf2207b9ccee7df10c2
Signed-off-by: HuabingZhao <zhao.huabing@zte.com.cn>
32 files changed:
openresty-ext/src/assembly/resources/openresty/nginx/conf/nginx.conf
openresty-ext/src/assembly/resources/openresty/nginx/luaext/conf/msbinit.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/error_handler.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/peerwatcher.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/router.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/dao/dao.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/lib/utils/dns_util.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/lib/utils/str_util.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/lib/utils/svc_util.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/balancer.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/baseupstream.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/monitor/stats.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/msb.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/plugins/config_default.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/plugins/redirect-transformer/handler.lua
openresty-ext/src/assembly/resources/openresty/nginx/luaext/plugins/redirect-transformer/url_matcher.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/List.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/class.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/compat.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/stringx.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/tablex.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/types.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/utils.lua [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/msb-enabled/location-default/msblocations.conf
openresty-ext/src/assembly/resources/openresty/nginx/msb-enabled/location-ext/monitor.conf [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/nginx/msb-enabled/msb.conf
openresty-ext/src/assembly/resources/openresty/nginx/msb-enabled/msbhttps.conf
openresty-ext/src/assembly/resources/openresty/nginx/sites-enabled/cosSample [new file with mode: 0644]
openresty-ext/src/assembly/resources/openresty/reload.sh
redis-ext/src/assembly/resources/linux/redis/run.sh
redis-ext/src/assembly/resources/linux/redis/stop.sh

index c51d10a..743eb31 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -59,8 +59,10 @@ http {
     #open_file_cache_errors on;
        
        gzip  on;
-       gzip_min_length 1000;
-       gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml;
+       gzip_min_length 1k;
+        gzip_buffers 4 16k;
+        gzip_comp_level 2;
+        gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml;
        
        include ../msb-enabled/*.conf;
        include ../sites-enabled/*.conf;
index bc8a13f..ec7d7b0 100644 (file)
@@ -1,6 +1,6 @@
 --[[
 
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+    Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -55,4 +55,10 @@ _M.server = {
        ["fail_timeout"]  = 10,
        ["max_fails"]     = 1
 }
-return _M
\ No newline at end of file
+_M.dns = {
+       ["servers"]  = mark_empty_as_nil(os.getenv("UPSTREAM_DNS_SERVERS")),
+       ["cache_positive_ttl"] = 180, --shcache use,in seconds
+       ["cache_negative_ttl"] = 2,  --shcache use,in seconds
+       ["cache_actualize_ttl"] = 120, --shcache use,in seconds
+}
+return _M
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/error_handler.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/error_handler.lua
new file mode 100644 (file)
index 0000000..f21f10a
--- /dev/null
@@ -0,0 +1,61 @@
+--[[\r
+\r
+    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+\r
+    Licensed under the Apache License, Version 2.0 (the "License");\r
+    you may not use this file except in compliance with the License.\r
+    You may obtain a copy of the License at\r
+\r
+            http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+    Unless required by applicable law or agreed to in writing, software\r
+    distributed under the License is distributed on an "AS IS" BASIS,\r
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+    See the License for the specific language governing permissions and\r
+    limitations under the License.\r
+\r
+--]]\r
+\r
+local _M = {}\r
+_M._VERSION = '1.0.0'\r
+local msbConf   =  require('conf.msbinit')\r
+local enablefullsearch = msbConf.systemConf.enablefullsearch\r
+local ngx_var = ngx.var\r
+local error_page_head = '<html><head><title>502 Bad Gateway</title></head><body bgcolor="white"><center><h1>502 Bad Gateway</h1></center><center>error message:'\r
+local error_page_foot = '</center><hr><center>nginx</center></body></html>'\r
+local upstream_not_found_err = "service info is incorrect:using own upstream flag is on but upstream name is empty"\r
+\r
+function _M.svc_not_found(err_info,detail_info)\r
+       ngx.log(ngx.WARN, ngx.var.request_id.." "..(err_info or "").." detail_info:"..(detail_info or ""))\r
+       if enablefullsearch and ngx_var.svc_type ~= "custom" then\r
+               -- test against the custom services after the commonrewrite phase\r
+               --ngx.status = ngx.HTTP_GONE\r
+               return ngx.exec("@commonnotfound");\r
+       else\r
+               ngx.status = ngx.HTTP_BAD_GATEWAY\r
+               ngx.print(error_page_head..err_info..error_page_foot)\r
+       end\r
+       return ngx.exit(ngx.status)\r
+end\r
+\r
+function _M.svc_not_allow_access(err_info,detail_info)\r
+       ngx.log(ngx.WARN, ngx.var.request_id.." "..(err_info or "").." detail_info:"..(detail_info or ""))\r
+       ngx.status = ngx.HTTP_FORBIDDEN\r
+       return ngx.exit(ngx.status)\r
+end\r
+\r
+function _M.upstream_not_found()\r
+       ngx.log(ngx.WARN, ngx.var.request_id.." "..upstream_not_found_err)\r
+       ngx.status = ngx.HTTP_BAD_GATEWAY\r
+       ngx.print(error_page_head..upstream_not_found_err..error_page_foot)\r
+       return ngx.exit(ngx.status)\r
+end\r
+\r
+function _M.no_server_available(err_info,detail_info)\r
+       ngx.log(ngx.WARN, ngx.var.request_id.." "..(err_info or "").." detail_info:"..(detail_info or ""))\r
+       ngx.status = ngx.HTTP_BAD_GATEWAY\r
+       ngx.print(error_page_head..err_info..error_page_foot)\r
+       return ngx.exit(ngx.status)\r
+end\r
+\r
+return _M
\ No newline at end of file
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/peerwatcher.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/peerwatcher.lua
new file mode 100644 (file)
index 0000000..6b7e522
--- /dev/null
@@ -0,0 +1,107 @@
+--[[\r
+\r
+    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+\r
+    Licensed under the Apache License, Version 2.0 (the "License");\r
+    you may not use this file except in compliance with the License.\r
+    You may obtain a copy of the License at\r
+\r
+            http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+    Unless required by applicable law or agreed to in writing, software\r
+    distributed under the License is distributed on an "AS IS" BASIS,\r
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+    See the License for the specific language governing permissions and\r
+    limitations under the License.\r
+\r
+--]]\r
+\r
+local _M = {\r
+       _VERSION = '1.0.0',\r
+       STATUS_OK = 0, STATUS_UNSTABLE = 1, STATUS_ERR = 2\r
+}\r
+local msbConf   =  require('conf.msbinit')\r
+local str_format    = string.format\r
+local now           = ngx.now\r
+local fail_timeout = msbConf.server.fail_timeout or 10\r
+local max_fails = msbConf.server.max_fails  or 1\r
+\r
+local cluster_status = {}\r
+_M.cluster_status = cluster_status\r
+\r
+function _M.is_server_ok(skey, srv)\r
+       return _M.get_srv_status(skey, srv)==_M.STATUS_OK\r
+end\r
+\r
+function _M.get_srv_status(skey, srv)\r
+       local server_status = cluster_status[skey]\r
+       if not server_status then\r
+               return _M.STATUS_OK\r
+       end\r
+\r
+       local srv_key = str_format("%s:%d", srv.ip, srv.port)\r
+       local srv_status = server_status[srv_key]\r
+\r
+       if srv_status and srv_status.lastmodify + fail_timeout > now() then\r
+               return srv_status.status\r
+       end\r
+\r
+       return _M.STATUS_OK\r
+end\r
+\r
+function _M.set_srv_status(skey, srv, failed)\r
+       local server_status = cluster_status[skey]\r
+       if not server_status then\r
+               server_status = {}\r
+               cluster_status[skey] = server_status\r
+       end\r
+\r
+       local time_now = now()\r
+       local srv_key = str_format("%s:%d", srv.ip, srv.port)\r
+       local srv_status = server_status[srv_key]\r
+       if not srv_status then  -- first set\r
+               srv_status = {\r
+                       status = _M.STATUS_OK,\r
+                       failed_count = 0,\r
+                       lastmodify = time_now\r
+               }\r
+               server_status[srv_key] = srv_status\r
+       elseif srv_status.lastmodify + fail_timeout < time_now then -- srv_status expired\r
+               srv_status.status = _M.STATUS_OK\r
+               srv_status.failed_count = 0\r
+               srv_status.lastmodify = time_now\r
+       end\r
+\r
+       if failed then\r
+               srv_status.failed_count = srv_status.failed_count + 1\r
+               if srv_status.failed_count >= max_fails then\r
+                       srv_status.status = _M.STATUS_ERR\r
+               end\r
+       end\r
+end\r
+\r
+function _M.check_and_reset_srv_status_ifneed(skey,servers)\r
+       local server_status = cluster_status[skey]\r
+       --if disabled servers of the service is empty,do nothing\r
+       if not server_status then \r
+               ngx.log(ngx.DEBUG, "service:",skey,"  server_status is nil")\r
+               return \r
+       end\r
+       local need_reset = true\r
+       for _, srv in ipairs(servers) do\r
+               local srv_key = str_format("%s:%d", srv.ip, srv.port)\r
+           local srv_status = server_status[srv_key]\r
+               if not (srv_status and srv_status.status == _M.STATUS_ERR and srv_status.lastmodify + fail_timeout > now()) then\r
+                       --once find the server is not disabled now, no need to reset the status table. break the loop\r
+                       ngx.log(ngx.DEBUG, "service:",skey," donot need reset,break the loop")\r
+                   need_reset = false\r
+                       break\r
+           end\r
+    end\r
+       if need_reset then\r
+               ngx.log(ngx.DEBUG, "service:",skey," need reset")\r
+               cluster_status[skey] = {}\r
+       end\r
+end\r
+\r
+return _M
\ No newline at end of file
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/router.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/core/router.lua
new file mode 100644 (file)
index 0000000..8915697
--- /dev/null
@@ -0,0 +1,293 @@
+--[[
+
+    Copyright (C) 2017 ZTE, Inc. and others. All rights reserved. (ZTE)
+
+    Licensed 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.
+
+--]]
+
+-- unified layer to access back DB, using two levels of cache mechanism(LRUCache and shcache)
+local _M = {}
+_M._VERSION = '1.0.0'
+local msbConf   =  require('conf.msbinit')
+local dbclient  =  require('dao.db_access')
+local tbl_util  =  require('lib.utils.table_util')
+local svc_util  =  require('lib.utils.svc_util')
+local log_util  =  require('lib.utils.log_util')
+local stats     =  require ('monitor.stats')
+local error_handler  =  require('core.error_handler')
+local dns_util  =  require('lib.utils.dns_util')
+
+local defaultport = msbConf.systemConf.defaultport
+local defaulthttpsport = msbConf.systemConf.defaulthttpsport
+local defaultprefix = msbConf.systemConf.defaultprefix
+local router_subdomain = msbConf.routerConf.subdomain
+local router_defaultprefix = msbConf.routerConf.defaultprefix
+local str_sub = string.sub
+local str_len = string.len
+local str_low = string.lower
+local tbl_concat = table.concat
+local tbl_isempty = tbl_util.isempty
+local svc_is_api_related_types = svc_util.is_api_related_types
+local svc_isactive = svc_util.isactive
+local svc_get_url = svc_util.get_url
+local svc_get_backend_protocol = svc_util.get_backend_protocol
+local svc_use_own_upstream = svc_util.use_own_upstream
+local svc_enable_refer_match = svc_util.enable_refer_match
+local svc_is_allow_access = svc_util.is_allow_access
+local ngx_var = ngx.var
+local log = log_util.log
+local error_svc_not_found = error_handler.svc_not_found
+--local error_upstream_not_found = error_handler.upstream_not_found
+local error_no_server_available = error_handler.no_server_available
+local error_svc_not_allow_access = error_handler.svc_not_allow_access
+local dns_query = dns_util.query
+local tbl_insert = table.insert
+
+local enablerefercheck = msbConf.systemConf.enablerefercheck
+local useconsultemplate = msbConf.systemConf.useconsultemplate
+
+local function _get_key_prefix(server_port)
+       if(not server_port) then
+               server_port = ngx_var.server_port
+       end
+       local svc_name = ngx_var.svc_name
+       if ("microservices" == svc_name or "msdiscover" == svc_name) then
+               return defaultprefix
+       elseif (server_port == defaultport or server_port == defaulthttpsport) then
+               local m, err = ngx.re.match(ngx_var.host, "(?<hostname>.+)\\."..router_subdomain,"o")
+               if m then
+                       return router_defaultprefix..":"..m["hostname"]
+               else
+                       return defaultprefix
+               end
+       else
+               return "msb:"..server_port
+       end
+end
+
+local function _load_common_svc_info(svc_type,server_port)
+       local key_prefix = _get_key_prefix(server_port)
+       local req_res  = ngx_var.req_res
+       local svc_name = ngx_var.svc_name
+       local svc_key = ""
+       local svc_pub_url = ""
+       if(svc_is_api_related_types(svc_type)) then
+               -- process version info first
+               local version1 = ngx_var.svc_version1
+               local version2 = ngx_var.svc_version2
+               local version = ""
+               -- check version info appearing befor or after
+               if(not version2) then version2 = "" end --convert nil to empty sting avoiding throw error
+               if(not version1 or version1 == "") then
+                       version = version2
+               else
+                       version = version1
+                       ngx_var.req_res = version2..req_res
+               end
+               -- remove the slash in front of the version (e.g. /V1.0)
+               local svc_version=str_sub(version,2,str_len(version))
+               svc_key = tbl_concat({key_prefix,"api",svc_name,svc_version},":")
+               svc_pub_url = "/"..svc_type.."/"..svc_name
+               if(svc_version ~= "") then svc_pub_url = svc_pub_url.."/"..svc_version end
+       else
+               svc_key = tbl_concat({key_prefix,"iui",svc_name},":")
+               svc_pub_url = "/iui/"..svc_name
+       end
+
+       local svcinfo = dbclient.load_serviceinfo(svc_key)
+       if tbl_isempty(svcinfo) then
+               return nil,"","","Common not match. key--"..svc_key
+       end
+
+       if not svc_isactive(svcinfo) then
+               return nil,"","","Common matched but service is disabled! key--"..svc_key
+       end
+
+       ngx.ctx.svc_pub_url = svc_pub_url
+       return svcinfo,svc_key,svc_name,""
+end
+
+local function _load_custom_svc_info(svc_type,server_port)
+       local key_prefix = _get_key_prefix(server_port)
+       local get_svckey_custom = function(svcname)
+               return tbl_concat({key_prefix,"custom",svcname},":")
+       end
+       local custom_svc_keypattern = tbl_concat({key_prefix,"custom","*"},":")
+       local svcnames,err = dbclient.load_customsvcnames(custom_svc_keypattern)
+       if not svcnames then
+               error_svc_not_found("Failed to load the route table!","keypattern--"..custom_svc_keypattern)
+       end
+       ngx.ctx.svcnames = svcnames
+       local matchedsvcname
+       local svcinfo
+       local svc_key = ""
+       for _, svcname in ipairs(svcnames) do
+               if (svcname == "/") then
+                       svc_key = get_svckey_custom(svcname)
+                       local svc_info,err = dbclient.load_serviceinfo(svc_key)
+                       if svc_info and svc_isactive(svc_info)then
+                               matchedsvcname = svcname
+                               svcinfo = svc_info
+                               break
+                       end
+               end
+               local from, to, err = ngx.re.find(ngx_var.uri, "^"..svcname.."(/(.*))?$", "jo")
+               --check whether svcname is the prefix of the req uri
+               if from then
+                       svc_key = get_svckey_custom(svcname)
+                       local svc_info,err = dbclient.load_serviceinfo(svc_key)
+                       if svc_info and svc_isactive(svc_info) then 
+                               matchedsvcname = svcname
+                               svcinfo = svc_info
+                               break
+                       end
+               else
+                       --do nothing
+               end
+       end
+       --add by wangyg:20160418 special handler for refer
+       if not matchedsvcname and enablerefercheck then 
+               local refer =  ngx_var.http_referer
+               if(refer and refer~="") then
+                       for _, svcname in ipairs(svcnames) do
+                               local urlreg ="^(https://|http://|)(([1-9]|([1-9]\\d)|(1\\d\\d)|(2([0-4]\\d|5[0-5])))\\.)(([0-9]|([1-9]\\d)|(1\\d\\d)|(2([0-4]\\d|5[0-5])))\\.){2}([1-9]|([1-9]\\d)|(1\\d\\d)|(2([0-4]\\d|5[0-5])))(:\\d{1,5})?"..svcname.."(/(.*))?$";
+                               local from, to, err = ngx.re.find(refer, urlreg, "jo")
+                               ----check whether svcname is the prefix of the req refer
+                               if from then
+                                       svc_key = get_svckey_custom(svcname)
+                                       local svc_info,err = dbclient.load_serviceinfo(svc_key)
+                                       if svc_info and svc_isactive(svc_info) and svc_enable_refer_match(svc_info) then 
+                                               matchedsvcname = svcname
+                                               svcinfo = svc_info
+                                               ngx.ctx.matched_usingrefer = true
+                                               log("matched_usingrefer",true)
+                                               break
+                                       end
+                               end
+                       end
+               end
+       end
+       --end of special handler for refer
+       if not matchedsvcname or tbl_isempty(svcinfo) then
+               return nil,"","","Custom not match"
+       end
+       ngx.ctx.svc_pub_url = matchedsvcname
+       return svcinfo,svc_key,matchedsvcname,""
+end
+
+-- syntax: svc_info,svc_key,matched_svcname,err = _load_service_info(svc_type,server_port)
+local function _load_service_info(svc_type,server_port)
+       if(svc_type ~= "custom") then
+               return _load_common_svc_info(svc_type,server_port)
+       else
+               return _load_custom_svc_info(svc_type,server_port)
+       end
+end
+
+---------------------------------------------------------------
+--Main Entry of the rewrite module
+---------------------------------------------------------------
+
+function _M.execute(server_port,system_tag)
+       ---------------------------------------------------------------
+       --step1:query the service info from share memory or backend db
+       --      svc_info: the requested service information
+       --      svc_key: the redis key
+       --      matched_svcname: the matched service name in the route table
+       --      err: the detail error info while load failed
+       ---------------------------------------------------------------
+       local svc_type = ngx_var.svc_type
+       local svc_info,svc_key,matched_svcname,err = _load_service_info(svc_type,server_port)
+       if(not svc_info) then
+               error_svc_not_found("No route found for this request!",err)
+       end
+       ngx.ctx.svc_key = svc_key
+       ngx.ctx.svc_info = svc_info
+       --log the route info
+       log("matched",matched_svcname)
+
+       if not svc_is_allow_access(system_tag,svc_info) then 
+               error_svc_not_allow_access("Route is not allowed to access!","system_tag:"..system_tag.." svc_key:"..svc_key)
+       end     
+       ---------------------------------------------------------------
+       --step2:rewrite the request uri using the svc_info
+       ---------------------------------------------------------------
+       local svc_url = svc_get_url(svc_info,svc_type)
+       local rewrited_uri =""
+       if(svc_type ~= "custom") then
+               rewrited_uri = svc_url..ngx_var.req_res
+       elseif (matched_svcname == "/") then
+               --special handling: if "/" matched, contact directly
+               rewrited_uri = svc_url..ngx_var.uri
+       else
+               local newuri,n,err = ngx.re.sub(ngx_var.uri, "^"..matched_svcname.."(/.*)?", svc_url.."$1", "o")
+               --add by wangyg:20160418 special handler for refer
+               if(n==0 and ngx.ctx.matched_usingrefer) then newuri = svc_url..ngx_var.uri end --special handling if matched using refer
+               --end of add by wangyg:20160418 special handler for refer
+               rewrited_uri = newuri
+       end
+       if (rewrited_uri == "") then return ngx.redirect(ngx.var.uri.."/") end 
+       ngx.req.set_uri(rewrited_uri)
+       ngx.ctx.svc_url = svc_url
+
+       ---------------------------------------------------------------
+       --step3:process the proxy upstream part
+       -- con1-using consul template:set the upstream name
+       -- con2-using msb balancer:query the server list and store in the ctx
+       ---------------------------------------------------------------
+       --set the http_protocol used by proxy_pass directive
+       ngx_var.http_protocol = svc_get_backend_protocol(svc_info)
+
+       --[[
+       if svc_use_own_upstream(svc_info) then
+               ngx.ctx.use_ownupstream = true
+       end
+               local consul_servicename = svc_info.spec["consulServiceName"]
+               if not consul_servicename or consul_servicename == "" then
+                       error_upstream_not_found()
+               end
+               ngx_var.backend = consul_servicename
+               ngx.ctx.use_ownupstream = true
+       else
+       ]]--
+       local backservers = svc_info.spec.nodes
+       if tbl_isempty(backservers) then
+               error_no_server_available("No active backend server found!"," key--"..svc_key)
+       end
+       local new_backservers = {}
+       for i, server in ipairs(backservers) do
+               local m, err = ngx.re.match(server["ip"], "^([0-9a-zA-Z]([0-9a-zA-Z-]+[\\.]{1})+[a-zA-Z-]+)$","o")
+               if m then
+                       local ipaddr = dns_query(server["ip"])
+                       if ipaddr then
+                               local new_server = {}
+                               new_server["ip"] = ipaddr
+                               new_server["port"] = server["port"]
+                               tbl_insert(new_backservers,new_server)
+                       end
+               else
+                       local new_server = {}
+                       new_server["ip"] = server["ip"]
+                       new_server["port"] = server["port"]
+                       tbl_insert(new_backservers,new_server)
+               end
+       end
+       if tbl_isempty(new_backservers) then
+               error_no_server_available("The domain name of backendserver was not resolved!"," key--"..svc_key)
+       end
+       ngx.ctx.backservers = new_backservers
+       --end
+end
+
+return _M
\ No newline at end of file
index 58d058c..8a69499 100644 (file)
@@ -1,6 +1,6 @@
 --[[\r
 \r
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+    Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
 \r
     Licensed under the Apache License, Version 2.0 (the "License");\r
     you may not use this file except in compliance with the License.\r
@@ -31,6 +31,7 @@ local cacheConf = msbConf.cacheConf
 local positive_ttl = cacheConf.positive_ttl or 60\r
 local negative_ttl = cacheConf.negative_ttl or 2\r
 local actualize_ttl = cacheConf.actualize_ttl or 120\r
+local stats_prefix = "monitor:stats:"\r
 \r
 local svc_shcache = ngx.shared.svc_cache\r
 \r
@@ -120,4 +121,48 @@ local function load_customsvcnames(keypattern)
 end\r
 _M.load_customsvcnames = load_customsvcnames\r
 \r
-return _M
\ No newline at end of file
+\r
+function _M.save_reqnum_stats(date,info)\r
+       local _db = DB:new(options)\r
+       local c, err = _db:connectdb()\r
+       if not c then\r
+               return nil, err\r
+       end\r
+\r
+       local red   = _db.redis\r
+       local resp,err =  red:rpush(stats_prefix..date,info)\r
+       _db:keepalivedb()\r
+       if not resp then\r
+               return nil, "save_reqnum_stats failed! key:"..date.." value:"..info\r
+       else\r
+               return resp,nil\r
+       end\r
+end\r
+\r
+function _M.delete_reqnum_stats(date)\r
+       local _db = DB:new(options)\r
+       local c, err = _db:connectdb()\r
+       if not c then\r
+               return nil, err\r
+       end\r
+       local red   = _db.redis\r
+       red:del(stats_prefix..date)\r
+       _db:keepalivedb()\r
+end\r
+\r
+function _M.get_reqnum_stats(date,latest_num)\r
+       if(type(latest_num)~="number") then\r
+               return nil,"the input num is illegal!"\r
+       end\r
+       local _db = DB:new(options)\r
+       local c, err = _db:connectdb()\r
+       if not c then\r
+               return nil, err\r
+       end\r
+       local red   = _db.redis\r
+       local resp,err =  red:lrange(stats_prefix..date,0-latest_num,-1)\r
+       _db:keepalivedb()\r
+       return resp,nil\r
+end\r
+\r
+return _M\r
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/lib/utils/dns_util.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/lib/utils/dns_util.lua
new file mode 100644 (file)
index 0000000..c2ac7b1
--- /dev/null
@@ -0,0 +1,99 @@
+--[[\r
+\r
+    Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+\r
+    Licensed under the Apache License, Version 2.0 (the "License");\r
+    you may not use this file except in compliance with the License.\r
+    You may obtain a copy of the License at\r
+\r
+            http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+    Unless required by applicable law or agreed to in writing, software\r
+    distributed under the License is distributed on an "AS IS" BASIS,\r
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+    See the License for the specific language governing permissions and\r
+    limitations under the License.\r
+\r
+--]]\r
+\r
+local _M = {}\r
+_M._VERSION = '1.0.0'\r
+\r
+local shcache = require("vendor.shcache")\r
+local msbConf   =  require('conf.msbinit')\r
+local resolver = require "resty.dns.resolver"\r
+local str_util  =  require('lib.utils.str_util')\r
+\r
+local dns_cache = ngx.shared.dns_cache\r
+local dns_servers = msbConf.dns.servers\r
+local dns_cache_positive_ttl = msbConf.dns.cache_positive_ttl or 60\r
+local dns_cache_negative_ttl = msbConf.dns.cache_negative_ttl or 2\r
+local dns_cache_actualize_ttl = msbConf.dns.cache_actualize_ttl or 120\r
+local str_split = str_util.split\r
+\r
+local nameservers = nil\r
+ngx.log(ngx.WARN, "environment variable UPSTREAM_DNS_SERVERS:",dns_servers)\r
+local ok,res = pcall(function() return str_split(dns_servers,",") end)\r
+if not ok then\r
+       ngx.log(ngx.WARN, "failed to parse the DNS Servers from the environment variable UPSTREAM_DNS_SERVERS"," Error:"..res)\r
+else\r
+       nameservers = res\r
+end\r
+\r
+local function query(domain)\r
+       -- closure to perform external lookup to redis\r
+       local dns_query_from_server = function ()\r
+               local r, err = resolver:new{\r
+                       nameservers = nameservers,\r
+                       retrans = 5,  -- 5 retransmissions on receive timeout\r
+                       timeout = 2000,  -- 2 sec\r
+               }\r
+\r
+               if not r then\r
+                       ngx.log(ngx.ERR, "failed to instantiate the resolver:",err)\r
+                       return nil,"failed to instantiate the resolver:"..err\r
+               end\r
+\r
+               --local answers, err = r:query("wygtest.service.openpalette")\r
+               local answers, err = r:query(domain)\r
+               if not answers then\r
+                       ngx.log(ngx.ERR, "failed to query the DNS server:",err)\r
+                       return nil,"failed to query the DNS server:"..err\r
+               end\r
+\r
+               if answers.errcode then\r
+                       ngx.log(ngx.ERR, "server returned error code: ", answers.errcode,\r
+                               ": ", answers.errstr)\r
+                       return nil,"server returned error code: "..answers.errcode..\r
+                       ": ".. answers.errstr\r
+               end\r
+\r
+               for i, ans in ipairs(answers) do\r
+                       if r.TYPE_A==ans.type and r.CLASS_IN==ans.class then\r
+                               return ans.address\r
+                       end\r
+               end\r
+               return nil,"dns servers return answers,but no server is TYPE_A and CLASS_IN"\r
+       end\r
+\r
+       local dns_cache_table = shcache:new(\r
+               dns_cache,\r
+               { external_lookup = dns_query_from_server,\r
+                       --encode = cmsgpack.pack,\r
+                       --encode = cjson_safe.encode,\r
+                       --decode = cmsgpack.unpack\r
+                       --decode = cjson_safe.decode\r
+               },\r
+               {   positive_ttl = dns_cache_positive_ttl,       -- default cache good data for 60s \r
+                       negative_ttl = dns_cache_negative_ttl,           -- default cache failed lookup for 1s\r
+                       actualize_ttl = dns_cache_actualize_ttl,\r
+                       name = 'dns_cache'                  -- "named" cache, useful for debug / report\r
+               }\r
+       )\r
+       local server, from_cache = dns_cache_table:load(domain)\r
+\r
+       return server\r
+end\r
+_M.query = query\r
+\r
+return _M\r
index 6fa8d39..00996c4 100644 (file)
@@ -1,6 +1,6 @@
 --[[\r
 \r
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+    Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
 \r
     Licensed under the Apache License, Version 2.0 (the "License");\r
     you may not use this file except in compliance with the License.\r
 local _M = {}\r
 _M._VERSION = '1.0.0'\r
 \r
+local pl_stringx = require "pl.stringx"\r
+local split      = pl_stringx.split\r
+local strip      = pl_stringx.strip\r
+\r
 function _M.mark_empty_as_nil(t)\r
        if t == "" then\r
                return nil\r
@@ -27,4 +31,14 @@ function _M.mark_empty_as_nil(t)
        end\r
 end\r
 \r
-return _M
\ No newline at end of file
+--- splits a string.\r
+-- just a placeholder to the penlight `pl.stringx.split` function\r
+-- @function split\r
+_M.split = split\r
+\r
+--- strips whitespace from a string.\r
+-- just a placeholder to the penlight `pl.stringx.strip` function\r
+-- @function strip\r
+_M.strip = strip\r
+\r
+return _M\r
index 226e31f..ce65e14 100644 (file)
@@ -1,6 +1,6 @@
 --[[\r
 \r
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+    Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
 \r
     Licensed under the Apache License, Version 2.0 (the "License");\r
     you may not use this file except in compliance with the License.\r
@@ -22,19 +22,18 @@ _M._VERSION = '1.0.0'
 local msbConf= require('conf.msbinit')\r
 local svcConf   =  require('conf.svcconf')\r
 local log_util  =  require('lib.utils.log_util')\r
+local bit = require("bit")\r
 \r
 local log = log_util.log\r
-local ngx_var = ngx.var\r
-\r
-local defaultport = msbConf.systemConf.defaultport\r
-local defaulthttpsport = msbConf.systemConf.defaulthttpsport\r
-local defaultprefix = msbConf.systemConf.defaultprefix\r
-local router_subdomain = msbConf.routerConf.subdomain\r
-local router_defaultprefix = msbConf.routerConf.defaultprefix\r
 local useconsultemplate = msbConf.systemConf.useconsultemplate\r
 local urlfieldMap = svcConf.urlfieldMap\r
 local apiRelatedTypes = svcConf.apiRelatedTypes\r
 \r
+local SYS_SCENARIO_FLAG = {  -- cos    router\r
+       ["ROUTER"]  = 1,         -- 0      1\r
+       ["COS"]     = 2          -- 1      0\r
+}\r
+\r
 function _M.isactive(svcinfo)\r
        if svcinfo["status"] == "1" then\r
                return true\r
@@ -65,32 +64,76 @@ function _M.get_backend_protocol(svcinfo)
        end\r
 end\r
 \r
-function _M.get_key_prefix()\r
-       --now assemble the key prefix according the svc_name and server_port\r
-       local key_prefix = ""\r
-       local server_port = ngx_var.server_port\r
-       local svc_name = ngx_var.svc_name\r
-       if (svc_name == "microservices" or svc_name == "msdiscover") then\r
-               key_prefix = defaultprefix\r
-       elseif (server_port == defaultport or server_port == defaulthttpsport) then\r
-               local m, err = ngx.re.match(ngx_var.host, "(?<hostname>.+)\\."..router_subdomain,"o")\r
-               if m then\r
-                       key_prefix = router_defaultprefix..":"..m["hostname"]\r
+function _M.is_api_related_types(svc_type)\r
+       if(apiRelatedTypes[svc_type]) then\r
+               return true\r
+       else\r
+               return false\r
+       end\r
+end\r
+\r
+function _M.get_connect_timeout(svcinfo)\r
+       local connect_timeout = svcinfo.spec["connect_timeout"]\r
+       if connect_timeout then\r
+               connect_timeout = tonumber(connect_timeout)\r
+               if connect_timeout and connect_timeout<=0 then \r
+                       ngx.log(ngx.WARN, ngx.var.request_id.." ".."bad connect timeout!Zero and negative timeout values are not allowed.Input value:"..connect_timeout)\r
+                       return nil\r
                else\r
-                       key_prefix = defaultprefix\r
+                       return connect_timeout\r
                end\r
        else\r
-               key_prefix = "msb:"..server_port\r
+               return nil\r
        end\r
-       return key_prefix\r
 end\r
 \r
-function _M.is_api_related_types(svc_type)\r
-       if(apiRelatedTypes[svc_type]) then\r
+function _M.get_send_timeout(svcinfo)\r
+       local send_timeout = svcinfo.spec["send_timeout"]\r
+       if send_timeout then\r
+               send_timeout = tonumber(send_timeout)\r
+               if send_timeout and send_timeout<=0 then \r
+                       ngx.log(ngx.WARN, ngx.var.request_id.." ".."bad send timeout!Zero and negative timeout values are not allowed.Input value:"..send_timeout)\r
+                       return nil\r
+               else\r
+                       return send_timeout\r
+               end\r
+       else\r
+               return nil\r
+       end\r
+end\r
+\r
+function _M.get_read_timeout(svcinfo)\r
+       local read_timeout = svcinfo.spec["read_timeout"]\r
+       if read_timeout then\r
+               read_timeout = tonumber(read_timeout)\r
+               if read_timeout and read_timeout <= 0 then \r
+                       ngx.log(ngx.WARN, ngx.var.request_id.." ".."bad send timeout!Zero and negative timeout values are not allowed.Input value:"..read_timeout)\r
+                       return nil\r
+               else\r
+                       return read_timeout\r
+               end\r
+       else\r
+               return nil\r
+       end\r
+end\r
+\r
+function _M.enable_refer_match(svcinfo)\r
+       local enable_refer_match = svcinfo.spec["enable_refer_match"]\r
+       --Be compatible with the old service info. If the field is not filled, the refer match is enabled by default.\r
+       if enable_refer_match == nil or enable_refer_match then\r
                return true\r
        else\r
                return false\r
        end\r
 end\r
 \r
-return _M
\ No newline at end of file
+function _M.is_allow_access(system_tag,svcinfo)\r
+       local scenario = svcinfo.spec["scenario"] or 1\r
+       local ok,res = pcall(function() return bit.band(SYS_SCENARIO_FLAG[system_tag], scenario) end)\r
+       if ok and res==SYS_SCENARIO_FLAG[system_tag] then \r
+               return true\r
+       else\r
+               return false\r
+       end\r
+end\r
+return _M\r
index 48dc1d8..ac9bb1d 100644 (file)
@@ -1,6 +1,6 @@
 --[[\r
 \r
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+    Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
 \r
     Licensed under the Apache License, Version 2.0 (the "License");\r
     you may not use this file except in compliance with the License.\r
 \r
 local b = require "ngx.balancer"\r
 local baseupstream = require "loadbalance.baseupstream"\r
-local log_util  =  require('lib.utils.log_util')\r
-local error_handler  =  require('lib.utils.error_handler')\r
+local stats = require "monitor.stats"\r
+local svc_util  =  require 'lib.utils.svc_util'\r
+\r
+local servers = ngx.ctx.backservers\r
+local svc_key = ngx.ctx.svc_key\r
+local svc_info = ngx.ctx.svc_info\r
+local svc_get_connect_timeout = svc_util.get_connect_timeout\r
+local svc_get_send_timeout = svc_util.get_send_timeout\r
+local svc_get_read_timeout = svc_util.get_read_timeout\r
 \r
-local log = log_util.log\r
-local ngx_ctx = ngx.ctx\r
-local servers = ngx_ctx.backservers\r
-local svc_key = ngx_ctx.svc_key\r
-local error_svc_not_found = error_handler.svc_not_found\r
 \r
 local status = b.get_last_failure()\r
 if status == nil then\r
@@ -35,6 +37,7 @@ elseif status == "failed" then
        local last_peer = ngx.ctx.last_peer\r
        --mark the srv failed one time\r
        baseupstream.mark_srv_failed(svc_key,last_peer)\r
+       stats.backend_failed()\r
 end\r
 \r
 local server,err = baseupstream.get_backserver(svc_key,servers)\r
@@ -46,5 +49,6 @@ if baseupstream.can_retry(svc_key,servers) then
        b.set_more_tries(1)\r
 end\r
 b.set_current_peer(server["ip"],server["port"])\r
---log("upstreamserver",server["ip"]..":"..server["port"])\r
-ngx.ctx.last_peer = { ip=server["ip"], port=server["port"] }
\ No newline at end of file
+b.set_timeouts(svc_get_connect_timeout(svc_info), svc_get_send_timeout(svc_info), svc_get_read_timeout(svc_info))\r
+ngx.ctx.last_peer = { ip=server["ip"], port=server["port"] }\r
+stats.forward_backend()\r
index 4af6dfa..9361eea 100644 (file)
@@ -1,6 +1,6 @@
 --[[\r
 \r
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+    Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
 \r
     Licensed under the Apache License, Version 2.0 (the "License");\r
     you may not use this file except in compliance with the License.\r
 local _M = {\r
        _VERSION = '1.0.0'\r
 }\r
-local policymodule = require "loadbalance.policy.roundrobin"\r
+\r
+local roundrobin = require "loadbalance.policy.roundrobin"\r
+local consistent_hash = require "loadbalance.policy.consistent_hash"\r
 local tbl_util  = require('lib.utils.table_util')\r
-local peerwatcher = require "loadbalance.peerwatcher"\r
+local svc_util  =  require('lib.utils.svc_util')\r
+local peerwatcher = require "core.peerwatcher"\r
 local tbl_isempty = tbl_util.isempty\r
+local svc_use_own_upstream = svc_util.use_own_upstream\r
 \r
 function _M.get_backserver(svc_key,servers)\r
        if tbl_isempty(servers) then return nil,"server list is empty" end\r
@@ -42,14 +46,43 @@ function _M.get_backserver(svc_key,servers)
                        return nil,"only one server but is not available"\r
                end\r
        end\r
-       for i=ngx.ctx.tried_num+1,servers_num do\r
-               ngx.ctx.tried_num = ngx.ctx.tried_num+1\r
-               server = policymodule.select_backserver(servers,svc_key)\r
-               if peerwatcher.is_server_ok(svc_key,server) then\r
-                       return server,""\r
-               end\r
+\r
+       -- A temporary solution, plase modify it when add lb_policy to svc_info\r
+    local svc_info = ngx.ctx.svc_info\r
+    if svc_use_own_upstream(svc_info) then\r
+               svc_info.lb_policy = "ip_hash"\r
+       else\r
+               svc_info.lb_policy = "roundrobin"\r
        end\r
-       return nil,"serveral server but no one is available"\r
+\r
+\r
+       local mode = svc_info.lb_policy\r
+       if mode ~= nil then\r
+           if mode == "ip_hash" then\r
+                   -- iphash\r
+                   for i=ngx.ctx.tried_num+1,servers_num do\r
+                       ngx.ctx.tried_num = ngx.ctx.tried_num+1\r
+                       server = consistent_hash.select_backserver(servers,svc_key)\r
+                       if peerwatcher.is_server_ok(svc_key,server) then\r
+                           return server,""\r
+                       end\r
+            end\r
+            return nil,"serveral server but no one is available"\r
+\r
+        elseif mode == "roundrobin" then\r
+             -- roundrobin\r
+                   for i=ngx.ctx.tried_num+1,servers_num do\r
+                           ngx.ctx.tried_num = ngx.ctx.tried_num+1\r
+                           server = roundrobin.select_backserver(servers,svc_key)\r
+                           if peerwatcher.is_server_ok(svc_key,server) then\r
+                               return server,""\r
+                           end\r
+            end\r
+            return nil,"serveral server but no one is available"\r
+        end\r
+\r
+    end\r
+\r
 end\r
 \r
 function _M.can_retry(svc_key,servers)\r
@@ -63,4 +96,4 @@ end
 function _M.check_and_reset_srv_status_ifneed(svc_key, servers)\r
        peerwatcher.check_and_reset_srv_status_ifneed(svc_key,servers)\r
 end\r
-return _M
\ No newline at end of file
+return _M\r
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/loadbalance/policy/consistent_hash.lua
new file mode 100644 (file)
index 0000000..b3cd46e
--- /dev/null
@@ -0,0 +1,135 @@
+--[[
+
+    Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)
+
+    Licensed 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 _M = {}
+_M._VERSION = '1.0.0'
+
+local floor      = math.floor
+local str_byte   = string.byte
+local tab_sort   = table.sort
+local tab_insert = table.insert
+
+local MOD       = 2 ^ 32
+local REPLICAS  = 20
+local LUCKY_NUM = 13
+
+
+local tbl_util  = require('lib.utils.table_util')
+local tbl_isempty = tbl_util.isempty
+local tbl_isequal = require('pl.tablex')
+local peerwatcher = require "core.peerwatcher"
+local ngx_var = ngx.var
+local hash_data = {}
+
+local function hash_string(str)
+    local key = 0
+    for i = 1, #str do
+        key = (key * 31 + str_byte(str, i)) % MOD
+    end
+    return key
+end
+
+
+local function init_consistent_hash_state(servers)
+    local weight_sum = 0
+    local weight = 1
+    for _, srv in ipairs(servers) do
+        if srv.weight  and srv.weight ~= 0 then
+            weight = srv.weight
+        end
+        weight_sum = weight_sum + weight
+    end
+
+    local circle, members = {}, 0
+    for index, srv in ipairs(servers) do
+        local key = ("%s:%s"):format(srv.ip, srv.port)
+        local base_hash = hash_string(key)
+        for c = 1, REPLICAS * weight_sum do
+            local hash = (base_hash * c * LUCKY_NUM) % MOD
+            tab_insert(circle, { hash, index })
+        end
+        
+        members = members + 1
+    end
+    tab_sort(circle, function(a, b) return a[1] < b[1] end)
+    return { circle = circle, members = members }
+end
+
+local function update_consistent_hash_state(hash_data,servers,svckey)
+    -- compare servers in ctx with servers in cache
+    -- update the hash data if changes occur
+    local serverscache = hash_data[svckey].servers
+    tab_sort(serverscache, function(a, b) return a.ip < b.ip end)
+    tab_sort(servers, function(a, b) return a.ip < b.ip end)
+    if  not tbl_isequal.deepcompare(serverscache, servers, false) then
+        local tmp_chash = init_consistent_hash_state(servers)
+        hash_data[svckey].servers =servers
+        hash_data[svckey].chash = tmp_chash
+    end
+end
+
+local function binary_search(circle, key)
+    local size = #circle
+    local st, ed, mid = 1, size
+
+    while st <= ed do
+        mid = floor((st + ed) / 2)
+        if circle[mid][1] < key then
+            st = mid + 1
+        else
+            ed = mid - 1
+        end
+    end
+
+    return st == size + 1 and 1 or st
+end
+
+
+function _M.select_backserver(servers,svckey)
+
+    if hash_data[svckey] == nil then
+        local tbl = {}
+        tbl['servers'] = {}
+        tbl['chash'] = {}
+        hash_data[svckey] = tbl
+    end
+
+    if tbl_isempty(hash_data[svckey].servers) then
+        local tmp_chash = init_consistent_hash_state(servers)
+        hash_data[svckey].servers = servers
+        hash_data[svckey].chash = tmp_chash
+    else
+        update_consistent_hash_state(hash_data,servers,svckey)
+    end
+
+    local chash = hash_data[svckey].chash
+    local circle = chash.circle
+    local hash_key = ngx_var.remote_addr
+    local st = binary_search(circle, hash_string(hash_key))
+    local size = #circle
+    local ed = st + size - 1
+    for i = st, ed do
+        local idx = circle[(i - 1) % size + 1][2]
+        if peerwatcher.is_server_ok(svckey,hash_data[svckey].servers[idx]) then
+            return hash_data[svckey].servers[idx]
+        end
+    end
+    return nil, "consistent hash: no servers available"
+end
+
+return _M
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/monitor/stats.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/monitor/stats.lua
new file mode 100644 (file)
index 0000000..7a23e64
--- /dev/null
@@ -0,0 +1,177 @@
+--[[\r
+\r
+    Copyright (C) 2017 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+\r
+    Licensed under the Apache License, Version 2.0 (the "License");\r
+    you may not use this file except in compliance with the License.\r
+    You may obtain a copy of the License at\r
+\r
+            http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+    Unless required by applicable law or agreed to in writing, software\r
+    distributed under the License is distributed on an "AS IS" BASIS,\r
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+    See the License for the specific language governing permissions and\r
+    limitations under the License.\r
+\r
+--]]\r
+\r
+local _M = {}\r
+_M._VERSION = '1.0.0'\r
+\r
+local cjson_safe = require "cjson.safe"\r
+local dao = require('dao.dao')\r
+local tbl_util  = require('lib.utils.table_util')\r
+\r
+local stats_cache = ngx.shared.stats\r
+local new_timer = ngx.timer.at\r
+local tbl_isempty = tbl_util.isempty\r
+local accept_preparing_forward_key = "accept_preparing_forward"\r
+local forward_waiting_response_key = "forward_waiting_response"\r
+local receive_resp_not_return_key = "receive_resp_not_return"\r
+local req_num_stats_key = "req_num_stats"\r
+local reset_delay = 60 -- in seconds\r
+local remove_delay = 24*60*60 -- in seconds,24 hours\r
+local lastday_stats = ""\r
+\r
+\r
+function _M.accept_new_request()\r
+    local newval, err = stats_cache:incr(accept_preparing_forward_key, 1, 0)\r
+    if (not newval) then\r
+        ngx.log(ngx.ERR, "increase number of accept_not_forward_key error:", err)\r
+    end\r
+end\r
+\r
+function _M.forward_backend()\r
+    if not ngx.ctx.not_first_forward then\r
+        local newval, err = stats_cache:incr(accept_preparing_forward_key, -1, 1)\r
+        if (not newval) then\r
+            ngx.log(ngx.ERR, "decrease number of accept_preparing_forward error:", err)\r
+        end\r
+    end\r
+    local newval, err = stats_cache:incr(forward_waiting_response_key, 1, 0)\r
+    if (not newval) then\r
+        ngx.log(ngx.ERR, "increase number of forward_waiting_response error:", err)\r
+    end\r
+    ngx.ctx.waiting_backend_resp = true\r
+    ngx.ctx.not_first_forward = true\r
+end\r
+\r
+function _M.backend_failed()\r
+    ngx.ctx.waiting_backend_resp = false\r
+    local newval, err = stats_cache:incr(forward_waiting_response_key, -1, 1)\r
+    if (not newval) then\r
+        ngx.log(ngx.ERR, "decrease number of forward_waiting_response error:", err)\r
+    end\r
+end\r
+\r
+function _M.receive_response()\r
+    if ngx.ctx.waiting_backend_resp then\r
+        ngx.ctx.waiting_backend_resp = false\r
+        local newval, err = stats_cache:incr(forward_waiting_response_key, -1, 1)\r
+        if (not newval) then\r
+            ngx.log(ngx.ERR, "decrease number of forward_waiting_response error:", err)\r
+        end\r
+    else\r
+        local newval, err = stats_cache:incr(accept_preparing_forward_key, -1, 1)\r
+        if (not newval) then\r
+            ngx.log(ngx.ERR, "decrease number of accept_preparing_forward error:", err)\r
+        end\r
+    end\r
+    local newval, err = stats_cache:incr(receive_resp_not_return_key, 1, 0)\r
+    if (not newval) then\r
+        ngx.log(ngx.ERR, "increase number of receive_resp_not_return error:", err)\r
+    end\r
+end\r
+\r
+function _M.return_response()\r
+    local newval, err = stats_cache:incr(receive_resp_not_return_key, -1, 1)\r
+    if (not newval) then\r
+        ngx.log(ngx.ERR, "decrease number of receive_resp_not_return error:", err)\r
+    end\r
+\r
+    local newval, err = stats_cache:incr(req_num_stats_key, 1, 0)\r
+    if (not newval) then\r
+        ngx.log(ngx.ERR, "increase number of total_req_num error:", err)\r
+    end\r
+end\r
+\r
+function _M.format_req_status()\r
+    local req_status = {}\r
+    req_status[accept_preparing_forward_key] = stats_cache:get(accept_preparing_forward_key) or 0\r
+    req_status[forward_waiting_response_key] = stats_cache:get(forward_waiting_response_key) or 0\r
+    req_status[receive_resp_not_return_key] = stats_cache:get(receive_resp_not_return_key) or 0\r
+    local value, err = cjson_safe.encode(req_status)\r
+    if err then\r
+        return "Collect real-time request status failed! Error:" .. err\r
+    end\r
+    return value\r
+end\r
+\r
+function _M.format_conn_status()\r
+    local conn_status = {}\r
+    conn_status["connections_active"] = ngx.var.connections_active\r
+    conn_status["connections_reading"] = ngx.var.connections_reading\r
+    conn_status["connections_writing"] = ngx.var.connections_writing\r
+    conn_status["connections_waiting"] = ngx.var.connections_waiting\r
+\r
+    local value, err = cjson_safe.encode(conn_status)\r
+    if err then\r
+        return "Collect real-time connection status failed! Error:" .. err\r
+    end\r
+    return value\r
+end\r
+\r
+function _M.get_reqnum_stats(latest_num)\r
+    local resp = dao.get_reqnum_stats(ngx.today(), latest_num)\r
+    if tbl_isempty(resp) then\r
+        return "[]"\r
+    end\r
+       return cjson_safe.encode(resp)\r
+end\r
+\r
+_reset_reqnum_stats = function(premature)\r
+       if premature then return end\r
+       local count = stats_cache:get(req_num_stats_key) or 0\r
+    dao.save_reqnum_stats(ngx.today(), ngx.time().."|"..count)\r
+    stats_cache:set(req_num_stats_key, 0)\r
+\r
+    local ok, err = new_timer(reset_delay, _reset_reqnum_stats)\r
+    if not ok then\r
+        ngx.log(ngx.ERR, "failed to create _reset_reqnum_stats timer: ", err)\r
+        return\r
+    end\r
+end\r
+\r
+_remove_old_stats = function(premature)\r
+       if premature then return end\r
+       ngx.update_time()\r
+       if lastday_stats ~= "" and lastday_stats ~= ngx.today() then\r
+               ngx.log(ngx.ERR, "delete old data ")\r
+               dao.delete_reqnum_stats(lastday_stats)\r
+       end\r
+    local ok, err = new_timer(remove_delay, _remove_old_stats)\r
+    if not ok then\r
+        ngx.log(ngx.ERR, "failed to create _remove_old_stats timer: ", err)\r
+        return\r
+    end\r
+       lastday_stats = ngx.today()\r
+end\r
+\r
+function _M.init_timer()\r
+    if 0 == ngx.worker.id() then\r
+        local ok, err = new_timer(reset_delay, _reset_reqnum_stats)\r
+        if not ok then\r
+            ngx.log(ngx.ERR, "failed to create _reset_reqnum_stats timer: ", err)\r
+            return\r
+        end\r
+               \r
+               ok, err = new_timer(10, _remove_old_stats)\r
+        if not ok then\r
+            ngx.log(ngx.ERR, "failed to create _remove_old_stats timer: ", err)\r
+            return\r
+        end\r
+    end\r
+end\r
+\r
+return _M
\ No newline at end of file
index f791bdb..c454173 100644 (file)
@@ -1,6 +1,6 @@
 --[[\r
 \r
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+    Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
 \r
     Licensed under the Apache License, Version 2.0 (the "License");\r
     you may not use this file except in compliance with the License.\r
@@ -22,9 +22,11 @@ _M._DESCRIPTION = 'msb plugins controller'
 \r
 local default_conf   =  require('plugins.config_default')\r
 local custom_conf   =  require('plugins.config_custom')\r
+local msb_router=  require('core.router')\r
 local table_insert = table.insert\r
 local string_find = string.find\r
 local str_low = string.lower\r
+local ngx_var = ngx.var\r
 \r
 --- Borrowed from Kong\r
 --- Try to load a module.\r
@@ -64,7 +66,7 @@ function _M.load_plugins()
                if not loaded then\r
                        error("The following plugin has been enabled in the configuration but it is not installed on the system: "..v)\r
                end\r
-               ngx.log(ngx.DEBUG, "Loading plugin: "..v)\r
+               ngx.log(ngx.WARN, "Loading plugin: "..v)\r
                table_insert(plugins, {\r
                                name = v,\r
                                handler = plugin_handler_mod()\r
@@ -73,6 +75,52 @@ function _M.load_plugins()
        package.loaded.plugins = plugins\r
 end\r
 \r
+function _M.filter_websocket_req()\r
+       local http_upgrade = ngx_var.http_upgrade\r
+       if http_upgrade and str_low(http_upgrade) == "websocket" then\r
+               --ngx.log(ngx.ERR, "Websocket request and redirect to @commonwebsocket")\r
+               return ngx.exec("@websocket");\r
+       end\r
+end\r
+\r
+function _M.route()\r
+       msb_router.execute(ngx_var.server_port,"ROUTER")\r
+end\r
+\r
+local function prepare_route()\r
+       local uri = ngx_var.uri\r
+       local m, err = ngx.re.match(uri, "^/(api|admin|apijson)(/[Vv]\\d+(?:\\.\\d+)*)?/([^/]+)(/[Vv]\\d+(?:\\.\\d+)*)?(.*)", "o")\r
+       if m then\r
+               ngx_var.svc_type = m[1]\r
+               ngx_var.svc_name = m[3]\r
+               ngx_var.svc_version1 = m[2] or ""\r
+               ngx_var.svc_version2 = m[4] or ""\r
+               ngx_var.req_res = m[5]\r
+               return\r
+       end\r
+       local m, err = ngx.re.match(uri, "^/iui/([^/]+)(.*)", "o")\r
+       if m then\r
+               ngx_var.svc_type = "iui"\r
+               ngx_var.svc_name = m[1]\r
+               ngx_var.req_res = m[2]\r
+               return\r
+       end\r
+       ngx_var.svc_type = "custom"\r
+       return\r
+end\r
+\r
+function _M.external_route(server_port,system_tag)\r
+       if not server_port or not system_tag then\r
+               local err = "server_port and system_tag are required while routing!"\r
+               ngx.log(ngx.WARN, ngx.var.request_id.." "..err)\r
+               ngx.status = ngx.HTTP_BAD_GATEWAY\r
+               ngx.print(err)\r
+               return ngx.exit(ngx.status)\r
+       end\r
+       prepare_route()\r
+       msb_router.execute(server_port,system_tag)\r
+end\r
+\r
 function _M.access()\r
        local plugins = package.loaded.plugins\r
        for _, plugin in ipairs(plugins) do\r
@@ -87,4 +135,4 @@ function _M.header_filter()
        end\r
 end\r
 \r
-return _M
\ No newline at end of file
+return _M\r
index cbb3107..3790453 100644 (file)
@@ -1,6 +1,6 @@
 --[[
 
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+    Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -19,9 +19,10 @@ local _M = {}
 _M._VERSION = '1.0.0'
 _M._DESCRIPTION = 'config_default'
 
-
+local str_util  =  require('lib.utils.str_util')
+local mark_empty_as_nil = str_util.mark_empty_as_nil
 _M.plugins_default = {
-       {["name"] = "redirect-transformer",["status"] = "on"}
+       {["name"] = "redirect-transformer",["status"] = mark_empty_as_nil(os.getenv("MSB_REDIRECT_TRANSFORMER_PLUGIN")) or "on"}
 }
 
-return _M
\ No newline at end of file
+return _M
index 72e3330..f469aa0 100644 (file)
@@ -1,6 +1,6 @@
 --[[
 
-    Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+    Copyright (C) 201-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
@@ -19,8 +19,9 @@
 local BasePlugin = require "plugins.base_plugin"
 local msbConf   =  require('conf.msbinit')
 local log_util  =  require('lib.utils.log_util')
+local url_matcher = require "plugins.redirect-transformer.url_matcher"
 local log = log_util.log
-
+local url_match_msb_route = url_matcher.is_match_msb_route
 local RedirectTransformerPluginHandler = BasePlugin:extend()
 
 function RedirectTransformerPluginHandler:new()
@@ -30,13 +31,40 @@ end
 function RedirectTransformerPluginHandler:header_filter()
        RedirectTransformerPluginHandler.super.header_filter(self)
        local originloc = ngx.header.Location
+       local newloc
        if(originloc) then
-               local newloc = ngx.re.sub(originloc, "^(https|http)(.*)", ngx.var.scheme.."$2", "oi")
+               log("origin location:",originloc)
+               local patten_conform,route_match = url_match_msb_route(originloc)
+               if not patten_conform then
+                       log("redirect-transformer output:","The redirect address may be outside msb, do nothing temporarily.")
+                       return
+               end
+
+               if route_match then
+                       --if the redirect address can be forwarded by msb,then donot modify it's url
+                       newloc = ngx.re.sub(originloc, "^(https|http)(.*)", ngx.var.scheme.."$2", "oi")
+               else
+                       --if the redirect address can not be forwarded by msb,then try to modify it's url
+                       local svc_pub_url = ngx.ctx.svc_pub_url
+                       local svc_url = ngx.ctx.svc_url
+                       if(svc_pub_url and svc_pub_url == "/") then
+                               --replace $svc_url with ""
+                               newloc = ngx.re.sub(originloc, "^(https|http)://([^/]+)"..svc_url, ngx.var.scheme.."://".."$2", "oi")
+                       else
+                               --replace $svc_url with $svc_pub_url
+                               newloc = ngx.re.sub(originloc, "^(https|http)://([^/]+)"..svc_url, ngx.var.scheme.."://".."$2"..svc_pub_url, "oi")
+                       end
+               end
+               -- replace the backend server with the host of msb
+               local last_peer = ngx.ctx.last_peer
+               if last_peer then
+                       local backend_ip = ngx.re.gsub(last_peer.ip, "\\.", "\\.", "o")
+                       newloc = ngx.re.sub(newloc, "^(https://|http://)"..backend_ip..":"..last_peer.port, "$1"..ngx.var.host..":"..ngx.var.server_port, "o")
+               end     
                ngx.header["Location"] = newloc
-               log("origin Location:",originloc)
-               log("req scheme:",ngx.var.scheme)
-               log("new Location:",newloc)
+               log("redirect-transformer output:","replace the redirect address to :"..newloc)
+               ngx.log(ngx.WARN, "redirect-transformer replace the redirect address to:"..newloc, " origin location:",originloc)
        end
 end
 
-return RedirectTransformerPluginHandler
\ No newline at end of file
+return RedirectTransformerPluginHandler
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/plugins/redirect-transformer/url_matcher.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/plugins/redirect-transformer/url_matcher.lua
new file mode 100644 (file)
index 0000000..41f7602
--- /dev/null
@@ -0,0 +1,164 @@
+--[[\r
+\r
+    Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)\r
+\r
+    Licensed under the Apache License, Version 2.0 (the "License");\r
+    you may not use this file except in compliance with the License.\r
+    You may obtain a copy of the License at\r
+\r
+            http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+    Unless required by applicable law or agreed to in writing, software\r
+    distributed under the License is distributed on an "AS IS" BASIS,\r
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+    See the License for the specific language governing permissions and\r
+    limitations under the License.\r
+\r
+--]]\r
+\r
+local _M = {}\r
+_M._VERSION = '1.0.0'\r
+\r
+local tbl_util  =  require('lib.utils.table_util')\r
+local dbclient  =  require('dao.db_access')\r
+local msbConf   =  require('conf.msbinit')\r
+\r
+local tbl_concat = table.concat\r
+local defaultport = msbConf.systemConf.defaultport\r
+local defaulthttpsport = msbConf.systemConf.defaulthttpsport\r
+local defaultprefix = msbConf.systemConf.defaultprefix\r
+local router_subdomain = msbConf.routerConf.subdomain\r
+local router_defaultprefix = msbConf.routerConf.defaultprefix\r
+local tbl_isempty = tbl_util.isempty\r
+\r
+local function _get_key_prefix(scheme,host,server_port)\r
+       if(not server_port) then\r
+               if(scheme == "https") then\r
+                       server_port = 443\r
+               else \r
+                       server_port = 80\r
+               end\r
+       end\r
+       if (server_port == defaultport or server_port == defaulthttpsport) then\r
+               local m, err = ngx.re.match(host, "(?<hostname>.+)\\."..router_subdomain,"o")\r
+               if m then\r
+                       return router_defaultprefix..":"..m["hostname"]\r
+               else\r
+                       return defaultprefix\r
+               end\r
+       else\r
+               return "msb:"..server_port\r
+       end\r
+end\r
+\r
+local function _is_match_route_api(uri,key_prefix)\r
+       local svc_name,svc_version\r
+       local m, err = ngx.re.match(uri, "^/(api|admin|apijson)(/[Vv]\\d+(?:\\.\\d+)*)?/([^/]+)(/[Vv]\\d+(?:\\.\\d+)*)?(.*)", "o")\r
+       if m then\r
+               svc_name = m[3]\r
+               local svc_version1 = m[2] or ""\r
+               local svc_version2 = m[4] or ""\r
+               if(not svc_version1 or svc_version1 == "") then\r
+                       svc_version  = svc_version2\r
+               else\r
+                       svc_version = svc_version1\r
+               end     \r
+               local svc_key = tbl_concat({key_prefix,"api",svc_name,svc_version},":")\r
+               local svcinfo = dbclient.load_serviceinfo(svc_key)\r
+               if tbl_isempty(svcinfo) then\r
+                       return false\r
+               else\r
+                       return true\r
+               end\r
+       end\r
+       return false\r
+end\r
+\r
+local function _is_match_route_iui(uri,key_prefix)\r
+       local m, err = ngx.re.match(uri, "^/iui/([^/]+)(.*)", "o")\r
+       if m then\r
+               local svc_name = m[1]\r
+               local svc_key = tbl_concat({key_prefix,"iui",svc_name},":")\r
+               local svcinfo = dbclient.load_serviceinfo(svc_key)\r
+               if tbl_isempty(svcinfo) then\r
+                       return false\r
+               else\r
+                       return true\r
+               end\r
+       end\r
+       return false\r
+end\r
+\r
+local function _is_match_route_custom(uri,key_prefix)\r
+       --[[\r
+       local custom_svc_keypattern = tbl_concat({key_prefix,"custom","*"},":")\r
+       local svcnames,err = dbclient.load_customsvcnames(custom_svc_keypattern)\r
+       ]]\r
+       local svcnames = ngx.ctx.svcnames\r
+       if not svcnames then\r
+               return false\r
+       end\r
+       for _, svcname in ipairs(svcnames) do\r
+               if (svcname == "/") then\r
+                       return true\r
+               end\r
+               local from, to, err = ngx.re.find(uri, "^"..svcname.."(/(.*))?$", "jo")\r
+               --check whether svcname is the prefix of the req uri\r
+               if from then\r
+                       return true\r
+               else\r
+                       --do nothing\r
+               end\r
+       end\r
+       return false\r
+end\r
+\r
+local function _is_patten_conform(host)\r
+       local m1, err = ngx.re.match(host, "^([0-9a-zA-Z]([0-9a-zA-Z-]+[\\.]{1})+[a-zA-Z-]+)$","o")\r
+       if m1 then\r
+               -- domain\r
+               local m2, err = ngx.re.match(host, "(?<hostname>.+)\\."..router_subdomain,"o")\r
+               if m2 then \r
+                       return true\r
+               else\r
+                       return false\r
+               end\r
+       else\r
+               --ip\r
+               if host == ngx.var.host then\r
+                       return true\r
+               else\r
+                       local last_peer = ngx.ctx.last_peer\r
+                       if(last_peer and host == last_peer.ip) then\r
+                               return true\r
+                       else\r
+                               return false\r
+                       end\r
+               end\r
+       end     \r
+end\r
+\r
+-- syntax: patten_conform,route_match = is_match_msb_route(location)\r
+function _M.is_match_msb_route(location)\r
+       local m, err = ngx.re.match(location, "^(\\w+)://([^/:]*)(?::(\\d+))?([^/?]*).*", "o")\r
+       local scheme,host,port,uri\r
+       if m then\r
+               scheme = m[1]\r
+               host = m[2]\r
+               port = m[3]\r
+               uri = m[4]\r
+       else\r
+               return false,false --It is not normal to enter this branch. This match result just let redirect transformer ignore this request(do nothing)\r
+       end\r
+\r
+       -- check the host whether conform to msb rules \r
+       if not _is_patten_conform(host) then\r
+               return false,false\r
+       end\r
+\r
+       local key_prefix = _get_key_prefix(scheme,host,port)\r
+       --return true,_is_match_route_api(uri,key_prefix)  or _is_match_route_iui(uri,key_prefix) or _is_match_route_custom(uri,key_prefix)\r
+       return true,_is_match_route_custom(uri,key_prefix)\r
+end\r
+\r
+return _M\r
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/List.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/List.lua
new file mode 100644 (file)
index 0000000..95d8c0e
--- /dev/null
@@ -0,0 +1,566 @@
+--- Python-style list class.
+--
+-- **Please Note**: methods that change the list will return the list.
+-- This is to allow for method chaining, but please note that `ls = ls:sort()`
+-- does not mean that a new copy of the list is made. In-place (mutable) methods
+-- are marked as returning 'the list' in this documentation.
+--
+-- See the Guide for further @{02-arrays.md.Python_style_Lists|discussion}
+--
+-- See <a href="http://www.python.org/doc/current/tut/tut.html">http://www.python.org/doc/current/tut/tut.html</a>, section 5.1
+--
+-- **Note**: The comments before some of the functions are from the Python docs
+-- and contain Python code.
+--
+-- Written for Lua version Nick Trout 4.0; Redone for Lua 5.1, Steve Donovan.
+--
+-- Dependencies: `pl.utils`, `pl.tablex`, `pl.class`
+-- @classmod pl.List
+-- @pragma nostrip
+
+local tinsert,tremove,concat,tsort = table.insert,table.remove,table.concat,table.sort
+local setmetatable, getmetatable,type,tostring,string = setmetatable,getmetatable,type,tostring,string
+local tablex = require 'pl.tablex'
+local filter,imap,imap2,reduce,transform,tremovevalues = tablex.filter,tablex.imap,tablex.imap2,tablex.reduce,tablex.transform,tablex.removevalues
+local tsub = tablex.sub
+local utils = require 'pl.utils'
+local class = require 'pl.class'
+
+local array_tostring,split,assert_arg,function_arg = utils.array_tostring,utils.split,utils.assert_arg,utils.function_arg
+local normalize_slice = tablex._normalize_slice
+
+-- metatable for our list and map objects has already been defined..
+local Multimap = utils.stdmt.MultiMap
+local List = utils.stdmt.List
+
+local iter
+
+class(nil,nil,List)
+
+-- we want the result to be _covariant_, i.e. t must have type of obj if possible
+local function makelist (t,obj)
+    local klass = List
+    if obj then
+        klass = getmetatable(obj)
+    end
+    return setmetatable(t,klass)
+end
+
+local function simple_table(t)
+    return type(t) == 'table' and not getmetatable(t) and #t > 0
+end
+
+function List._create (src)
+    if simple_table(src) then return src end
+end
+
+function List:_init (src)
+    if self == src then return end -- existing table used as self!
+    if src then
+        for v in iter(src) do
+            tinsert(self,v)
+        end
+    end
+end
+
+--- Create a new list. Can optionally pass a table;
+-- passing another instance of List will cause a copy to be created;
+-- this will return a plain table with an appropriate metatable.
+-- we pass anything which isn't a simple table to iterate() to work out
+-- an appropriate iterator
+--  @see List.iterate
+-- @param[opt] t An optional list-like table
+-- @return a new List
+-- @usage ls = List();  ls = List {1,2,3,4}
+-- @function List.new
+
+List.new = List
+
+--- Make a copy of an existing list.
+-- The difference from a plain 'copy constructor' is that this returns
+-- the actual List subtype.
+function List:clone()
+    local ls = makelist({},self)
+    ls:extend(self)
+    return ls
+end
+
+---Add an item to the end of the list.
+-- @param i An item
+-- @return the list
+function List:append(i)
+    tinsert(self,i)
+    return self
+end
+
+List.push = tinsert
+
+--- Extend the list by appending all the items in the given list.
+-- equivalent to 'a[len(a):] = L'.
+-- @tparam List L Another List
+-- @return the list
+function List:extend(L)
+    assert_arg(1,L,'table')
+    for i = 1,#L do tinsert(self,L[i]) end
+    return self
+end
+
+--- Insert an item at a given position. i is the index of the
+-- element before which to insert.
+-- @int i index of element before whichh to insert
+-- @param x A data item
+-- @return the list
+function List:insert(i, x)
+    assert_arg(1,i,'number')
+    tinsert(self,i,x)
+    return self
+end
+
+--- Insert an item at the begining of the list.
+-- @param x a data item
+-- @return the list
+function List:put (x)
+    return self:insert(1,x)
+end
+
+--- Remove an element given its index.
+-- (equivalent of Python's del s[i])
+-- @int i the index
+-- @return the list
+function List:remove (i)
+    assert_arg(1,i,'number')
+    tremove(self,i)
+    return self
+end
+
+--- Remove the first item from the list whose value is given.
+-- (This is called 'remove' in Python; renamed to avoid confusion
+-- with table.remove)
+-- Return nil if there is no such item.
+-- @param x A data value
+-- @return the list
+function List:remove_value(x)
+    for i=1,#self do
+        if self[i]==x then tremove(self,i) return self end
+    end
+    return self
+ end
+
+--- Remove the item at the given position in the list, and return it.
+-- If no index is specified, a:pop() returns the last item in the list.
+-- The item is also removed from the list.
+-- @int[opt] i An index
+-- @return the item
+function List:pop(i)
+    if not i then i = #self end
+    assert_arg(1,i,'number')
+    return tremove(self,i)
+end
+
+List.get = List.pop
+
+--- Return the index in the list of the first item whose value is given.
+-- Return nil if there is no such item.
+-- @function List:index
+-- @param x A data value
+-- @int[opt=1] idx where to start search
+-- @return the index, or nil if not found.
+
+local tfind = tablex.find
+List.index = tfind
+
+--- does this list contain the value?.
+-- @param x A data value
+-- @return true or false
+function List:contains(x)
+    return tfind(self,x) and true or false
+end
+
+--- Return the number of times value appears in the list.
+-- @param x A data value
+-- @return number of times x appears
+function List:count(x)
+    local cnt=0
+    for i=1,#self do
+        if self[i]==x then cnt=cnt+1 end
+    end
+    return cnt
+end
+
+--- Sort the items of the list, in place.
+-- @func[opt='<'] cmp an optional comparison function
+-- @return the list
+function List:sort(cmp)
+    if cmp then cmp = function_arg(1,cmp) end
+    tsort(self,cmp)
+    return self
+end
+
+--- return a sorted copy of this list.
+-- @func[opt='<'] cmp an optional comparison function
+-- @return a new list
+function List:sorted(cmp)
+    return List(self):sort(cmp)
+end
+
+--- Reverse the elements of the list, in place.
+-- @return the list
+function List:reverse()
+    local t = self
+    local n = #t
+    for i = 1,n/2 do
+        t[i],t[n] = t[n],t[i]
+        n = n - 1
+    end
+    return self
+end
+
+--- return the minimum and the maximum value of the list.
+-- @return minimum value
+-- @return maximum value
+function List:minmax()
+    local vmin,vmax = 1e70,-1e70
+    for i = 1,#self do
+        local v = self[i]
+        if v < vmin then vmin = v end
+        if v > vmax then vmax = v end
+    end
+    return vmin,vmax
+end
+
+--- Emulate list slicing.  like  'list[first:last]' in Python.
+-- If first or last are negative then they are relative to the end of the list
+-- eg. slice(-2) gives last 2 entries in a list, and
+-- slice(-4,-2) gives from -4th to -2nd
+-- @param first An index
+-- @param last An index
+-- @return a new List
+function List:slice(first,last)
+    return tsub(self,first,last)
+end
+
+--- empty the list.
+-- @return the list
+function List:clear()
+    for i=1,#self do tremove(self) end
+    return self
+end
+
+local eps = 1.0e-10
+
+--- Emulate Python's range(x) function.
+-- Include it in List table for tidiness
+-- @int start A number
+-- @int[opt] finish A number greater than start; if absent,
+-- then start is 1 and finish is start
+-- @int[opt=1] incr an increment (may be less than 1)
+-- @return a List from start .. finish
+-- @usage List.range(0,3) == List{0,1,2,3}
+-- @usage List.range(4) = List{1,2,3,4}
+-- @usage List.range(5,1,-1) == List{5,4,3,2,1}
+function List.range(start,finish,incr)
+    if not finish then
+        finish = start
+        start = 1
+    end
+    if incr then
+    assert_arg(3,incr,'number')
+    if math.ceil(incr) ~= incr then finish = finish + eps end
+    else
+        incr = 1
+    end
+    assert_arg(1,start,'number')
+    assert_arg(2,finish,'number')
+    local t = List()
+    for i=start,finish,incr do tinsert(t,i) end
+    return t
+end
+
+--- list:len() is the same as #list.
+function List:len()
+    return #self
+end
+
+-- Extended operations --
+
+--- Remove a subrange of elements.
+-- equivalent to 'del s[i1:i2]' in Python.
+-- @int i1 start of range
+-- @int i2 end of range
+-- @return the list
+function List:chop(i1,i2)
+    return tremovevalues(self,i1,i2)
+end
+
+--- Insert a sublist into a list
+-- equivalent to 's[idx:idx] = list' in Python
+-- @int idx index
+-- @tparam List list list to insert
+-- @return the list
+-- @usage  l = List{10,20}; l:splice(2,{21,22});  assert(l == List{10,21,22,20})
+function List:splice(idx,list)
+    assert_arg(1,idx,'number')
+    idx = idx - 1
+    local i = 1
+    for v in iter(list) do
+        tinsert(self,i+idx,v)
+        i = i + 1
+    end
+    return self
+end
+
+--- general slice assignment s[i1:i2] = seq.
+-- @int i1  start index
+-- @int i2  end index
+-- @tparam List seq a list
+-- @return the list
+function List:slice_assign(i1,i2,seq)
+    assert_arg(1,i1,'number')
+    assert_arg(1,i2,'number')
+    i1,i2 = normalize_slice(self,i1,i2)
+    if i2 >= i1 then self:chop(i1,i2) end
+    self:splice(i1,seq)
+    return self
+end
+
+--- concatenation operator.
+-- @within metamethods
+-- @tparam List L another List
+-- @return a new list consisting of the list with the elements of the new list appended
+function List:__concat(L)
+    assert_arg(1,L,'table')
+    local ls = self:clone()
+    ls:extend(L)
+    return ls
+end
+
+--- equality operator ==.  True iff all elements of two lists are equal.
+-- @within metamethods
+-- @tparam List L another List
+-- @return true or false
+function List:__eq(L)
+    if #self ~= #L then return false end
+    for i = 1,#self do
+        if self[i] ~= L[i] then return false end
+    end
+    return true
+end
+
+--- join the elements of a list using a delimiter. 
+-- This method uses tostring on all elements.
+-- @string[opt=''] delim a delimiter string, can be empty.
+-- @return a string
+function List:join (delim)
+    delim = delim or ''
+    assert_arg(1,delim,'string')
+    return concat(array_tostring(self),delim)
+end
+
+--- join a list of strings. <br>
+-- Uses `table.concat` directly.
+-- @function List:concat
+-- @string[opt=''] delim a delimiter
+-- @return a string
+List.concat = concat
+
+local function tostring_q(val)
+    local s = tostring(val)
+    if type(val) == 'string' then
+        s = '"'..s..'"'
+    end
+    return s
+end
+
+--- how our list should be rendered as a string. Uses join().
+-- @within metamethods
+-- @see List:join
+function List:__tostring()
+    return '{'..self:join(',',tostring_q)..'}'
+end
+
+--- call the function on each element of the list.
+-- @func fun a function or callable object
+-- @param ... optional values to pass to function
+function List:foreach (fun,...)
+    fun = function_arg(1,fun)
+    for i = 1,#self do
+        fun(self[i],...)
+    end
+end
+
+local function lookup_fun (obj,name)
+    local f = obj[name]
+    if not f then error(type(obj).." does not have method "..name,3) end
+    return f
+end
+
+--- call the named method on each element of the list.
+-- @string name the method name
+-- @param ... optional values to pass to function
+function List:foreachm (name,...)
+    for i = 1,#self do
+        local obj = self[i]
+        local f = lookup_fun(obj,name)
+        f(obj,...)
+    end
+end
+
+--- create a list of all elements which match a function.
+-- @func fun a boolean function
+-- @param[opt] arg optional argument to be passed as second argument of the predicate
+-- @return a new filtered list.
+function List:filter (fun,arg)
+    return makelist(filter(self,fun,arg),self)
+end
+
+--- split a string using a delimiter.
+-- @string s the string
+-- @string[opt] delim the delimiter (default spaces)
+-- @return a List of strings
+-- @see pl.utils.split
+function List.split (s,delim)
+    assert_arg(1,s,'string')
+    return makelist(split(s,delim))
+end
+
+--- apply a function to all elements.
+-- Any extra arguments will be passed to the function.
+-- @func fun a function of at least one argument
+-- @param ... arbitrary extra arguments.
+-- @return a new list: {f(x) for x in self}
+-- @usage List{'one','two'}:map(string.upper) == {'ONE','TWO'}
+-- @see pl.tablex.imap
+function List:map (fun,...)
+    return makelist(imap(fun,self,...),self)
+end
+
+--- apply a function to all elements, in-place.
+-- Any extra arguments are passed to the function.
+-- @func fun A function that takes at least one argument
+-- @param ... arbitrary extra arguments.
+-- @return the list.
+function List:transform (fun,...)
+    transform(fun,self,...)
+       return self
+end
+
+--- apply a function to elements of two lists.
+-- Any extra arguments will be passed to the function
+-- @func fun a function of at least two arguments
+-- @tparam List ls another list
+-- @param ... arbitrary extra arguments.
+-- @return a new list: {f(x,y) for x in self, for x in arg1}
+-- @see pl.tablex.imap2
+function List:map2 (fun,ls,...)
+    return makelist(imap2(fun,self,ls,...),self)
+end
+
+--- apply a named method to all elements.
+-- Any extra arguments will be passed to the method.
+-- @string name name of method
+-- @param ... extra arguments
+-- @return a new list of the results
+-- @see pl.seq.mapmethod
+function List:mapm (name,...)
+    local res = {}
+    for i = 1,#self do
+      local val = self[i]
+      local fn = lookup_fun(val,name)
+      res[i] = fn(val,...)
+    end
+    return makelist(res,self)
+end
+
+local function composite_call (method,f)
+    return function(self,...)
+        return self[method](self,f,...)
+    end
+end
+
+function List.default_map_with(T)
+    return function(self,name)
+        local m
+        if T then
+            local f = lookup_fun(T,name)
+            m = composite_call('map',f)
+        else
+            m = composite_call('mapn',name)
+        end
+        getmetatable(self)[name] = m -- and cache..
+        return m
+    end
+end
+
+List.default_map = List.default_map_with
+
+--- 'reduce' a list using a binary function.
+-- @func fun a function of two arguments
+-- @return result of the function
+-- @see pl.tablex.reduce
+function List:reduce (fun)
+    return reduce(fun,self)
+end
+
+--- partition a list using a classifier function.
+-- The function may return nil, but this will be converted to the string key '<nil>'.
+-- @func fun a function of at least one argument
+-- @param ... will also be passed to the function
+-- @treturn MultiMap a table where the keys are the returned values, and the values are Lists
+-- of values where the function returned that key.
+-- @see pl.MultiMap
+function List:partition (fun,...)
+    fun = function_arg(1,fun)
+    local res = {}
+    for i = 1,#self do
+        local val = self[i]
+        local klass = fun(val,...)
+        if klass == nil then klass = '<nil>' end
+        if not res[klass] then res[klass] = List() end
+        res[klass]:append(val)
+    end
+    return setmetatable(res,Multimap)
+end
+
+--- return an iterator over all values.
+function List:iter ()
+    return iter(self)
+end
+
+--- Create an iterator over a seqence.
+-- This captures the Python concept of 'sequence'.
+-- For tables, iterates over all values with integer indices.
+-- @param seq a sequence; a string (over characters), a table, a file object (over lines) or an iterator function
+-- @usage for x in iterate {1,10,22,55} do io.write(x,',') end ==> 1,10,22,55
+-- @usage for ch in iterate 'help' do do io.write(ch,' ') end ==> h e l p
+function List.iterate(seq)
+    if type(seq) == 'string' then
+        local idx = 0
+        local n = #seq
+        local sub = string.sub
+        return function ()
+            idx = idx + 1
+            if idx > n then return nil
+            else
+                return sub(seq,idx,idx)
+            end
+        end
+    elseif type(seq) == 'table' then
+        local idx = 0
+        local n = #seq
+        return function()
+            idx = idx + 1
+            if idx > n then return nil
+            else
+                return seq[idx]
+            end
+        end
+    elseif type(seq) == 'function' then
+        return seq
+    elseif type(seq) == 'userdata' and io.type(seq) == 'file' then
+        return seq:lines()
+    end
+end
+iter = List.iterate
+
+return List
+
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/class.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/class.lua
new file mode 100644 (file)
index 0000000..a57ac71
--- /dev/null
@@ -0,0 +1,261 @@
+--- Provides a reuseable and convenient framework for creating classes in Lua.
+-- Two possible notations:
+--
+--    B = class(A)
+--    class.B(A)
+--
+-- The latter form creates a named class within the current environment. Note
+-- that this implicitly brings in `pl.utils` as a dependency.
+--
+-- See the Guide for further @{01-introduction.md.Simplifying_Object_Oriented_Programming_in_Lua|discussion}
+-- @module pl.class
+
+local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type =
+    _G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type
+local compat
+
+-- this trickery is necessary to prevent the inheritance of 'super' and
+-- the resulting recursive call problems.
+local function call_ctor (c,obj,...)
+    -- nice alias for the base class ctor
+    local base = rawget(c,'_base')
+    if base then
+        local parent_ctor = rawget(base,'_init')
+        while not parent_ctor do
+            base = rawget(base,'_base')
+            if not base then break end
+            parent_ctor = rawget(base,'_init')
+        end
+        if parent_ctor then
+            rawset(obj,'super',function(obj,...)
+                call_ctor(base,obj,...)
+            end)
+        end
+    end
+    local res = c._init(obj,...)
+    rawset(obj,'super',nil)
+    return res
+end
+
+--- initializes an __instance__ upon creation.
+-- @function class:_init
+-- @param ... parameters passed to the constructor
+-- @usage local Cat = class()
+-- function Cat:_init(name)
+--   --self:super(name)   -- call the ancestor initializer if needed
+--   self.name = name
+-- end
+--
+-- local pussycat = Cat("pussycat")
+-- print(pussycat.name)  --> pussycat
+
+--- checks whether an __instance__ is derived from some class.
+-- Works the other way around as `class_of`. It has two ways of using;
+-- 1) call with a class to check against, 2) call without params.
+-- @function instance:is_a
+-- @param some_class class to check against, or `nil` to return the class
+-- @return `true` if `instance` is derived from `some_class`, or if `some_class == nil` then
+-- it returns the class table of the instance
+-- @usage local pussycat = Lion()  -- assuming Lion derives from Cat
+-- if pussycat:is_a(Cat) then
+--   -- it's true, it is a Lion, but also a Cat
+-- end
+-- 
+-- if pussycat:is_a() == Lion then
+--   -- It's true
+-- end
+local function is_a(self,klass)
+    if klass == nil then
+        -- no class provided, so return the class this instance is derived from
+        return getmetatable(self)
+    end
+    local m = getmetatable(self)
+    if not m then return false end --*can't be an object!
+    while m do
+        if m == klass then return true end
+        m = rawget(m,'_base')
+    end
+    return false
+end
+
+--- checks whether an __instance__ is derived from some class.
+-- Works the other way around as `is_a`.
+-- @function some_class:class_of
+-- @param some_instance instance to check against
+-- @return `true` if `some_instance` is derived from `some_class`
+-- @usage local pussycat = Lion()  -- assuming Lion derives from Cat
+-- if Cat:class_of(pussycat) then
+--   -- it's true
+-- end
+local function class_of(klass,obj)
+    if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end
+    return klass.is_a(obj,klass)
+end
+
+--- cast an object to another class.
+-- It is not clever (or safe!) so use carefully.
+-- @param some_instance the object to be changed
+-- @function some_class:cast
+local function cast (klass, obj)
+    return setmetatable(obj,klass)
+end
+
+
+local function _class_tostring (obj)
+    local mt = obj._class
+    local name = rawget(mt,'_name')
+    setmetatable(obj,nil)
+    local str = tostring(obj)
+    setmetatable(obj,mt)
+    if name then str = name ..str:gsub('table','') end
+    return str
+end
+
+local function tupdate(td,ts,dont_override)
+    for k,v in pairs(ts) do
+        if not dont_override or td[k] == nil then
+            td[k] = v
+        end
+    end
+end
+
+local function _class(base,c_arg,c)
+    -- the class `c` will be the metatable for all its objects,
+    -- and they will look up their methods in it.
+    local mt = {}   -- a metatable for the class to support __call and _handler
+    -- can define class by passing it a plain table of methods
+    local plain = type(base) == 'table' and not getmetatable(base)
+    if plain then
+        c = base
+        base = c._base
+    else
+        c = c or {}
+    end
+   
+    if type(base) == 'table' then
+        -- our new class is a shallow copy of the base class!
+        -- but be careful not to wipe out any methods we have been given at this point!
+        tupdate(c,base,plain)
+        c._base = base
+        -- inherit the 'not found' handler, if present
+        if rawget(c,'_handler') then mt.__index = c._handler end
+    elseif base ~= nil then
+        error("must derive from a table type",3)
+    end
+
+    c.__index = c
+    setmetatable(c,mt)
+    if not plain then
+        c._init = nil
+    end
+
+    if base and rawget(base,'_class_init') then
+        base._class_init(c,c_arg)
+    end
+
+    -- expose a ctor which can be called by <classname>(<args>)
+    mt.__call = function(class_tbl,...)
+        local obj
+        if rawget(c,'_create') then obj = c._create(...) end
+        if not obj then obj = {} end
+        setmetatable(obj,c)
+
+        if rawget(c,'_init') then -- explicit constructor
+            local res = call_ctor(c,obj,...)
+            if res then -- _if_ a ctor returns a value, it becomes the object...
+                obj = res
+                setmetatable(obj,c)
+            end
+        elseif base and rawget(base,'_init') then -- default constructor
+            -- make sure that any stuff from the base class is initialized!
+            call_ctor(base,obj,...)
+        end
+
+        if base and rawget(base,'_post_init') then
+            base._post_init(obj)
+        end
+
+        return obj
+    end
+    -- Call Class.catch to set a handler for methods/properties not found in the class!
+    c.catch = function(self, handler)
+        if type(self) == "function" then
+            -- called using . instead of :
+            handler = self
+        end
+        c._handler = handler
+        mt.__index = handler
+    end
+    c.is_a = is_a
+    c.class_of = class_of
+    c.cast = cast
+    c._class = c
+
+    if not rawget(c,'__tostring') then
+        c.__tostring = _class_tostring
+    end
+
+    return c
+end
+
+--- create a new class, derived from a given base class.
+-- Supporting two class creation syntaxes:
+-- either `Name = class(base)` or `class.Name(base)`.
+-- The first form returns the class directly and does not set its `_name`.
+-- The second form creates a variable `Name` in the current environment set
+-- to the class, and also sets `_name`.
+-- @function class
+-- @param base optional base class
+-- @param c_arg optional parameter to class constructor
+-- @param c optional table to be used as class
+local class
+class = setmetatable({},{
+    __call = function(fun,...)
+        return _class(...)
+    end,
+    __index = function(tbl,key)
+        if key == 'class' then
+            io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n')
+            return class
+        end
+        compat = compat or require 'pl.compat'
+        local env = compat.getfenv(2)
+        return function(...)
+            local c = _class(...)
+            c._name = key
+            rawset(env,key,c)
+            return c
+        end
+    end
+})
+
+class.properties = class()
+
+function class.properties._class_init(klass)
+    klass.__index = function(t,key)
+        -- normal class lookup!
+        local v = klass[key]
+        if v then return v end
+        -- is it a getter?
+        v = rawget(klass,'get_'..key)
+        if v then
+            return v(t)
+        end
+        -- is it a field?
+        return rawget(t,'_'..key)
+    end
+    klass.__newindex = function (t,key,value)
+        -- if there's a setter, use that, otherwise directly set table
+        local p = 'set_'..key
+        local setter = klass[p]
+        if setter then
+            setter(t,value)
+        else
+            rawset(t,key,value)
+        end
+    end
+end
+
+
+return class
+
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/compat.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/compat.lua
new file mode 100644 (file)
index 0000000..c9adc4c
--- /dev/null
@@ -0,0 +1,143 @@
+----------------
+--- Lua 5.1/5.2/5.3 compatibility.
+-- Ensures that `table.pack` and `package.searchpath` are available
+-- for Lua 5.1 and LuaJIT.
+-- The exported function `load` is Lua 5.2 compatible.
+-- `compat.setfenv` and `compat.getfenv` are available for Lua 5.2, although
+-- they are not always guaranteed to work.
+-- @module pl.compat
+
+local compat = {}
+
+compat.lua51 = _VERSION == 'Lua 5.1'
+
+local isJit = (tostring(assert):match('builtin') ~= nil)
+if isJit then
+    -- 'goto' is a keyword when 52 compatibility is enabled in LuaJit
+    compat.jit52 = not loadstring("local goto = 1")
+end
+
+--- execute a shell command.
+-- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
+-- @param cmd a shell command
+-- @return true if successful
+-- @return actual return code
+function compat.execute (cmd)
+    local res1,_,res3 = os.execute(cmd)
+    if compat.lua51 and not compat.jit52 then
+        return res1==0,res1
+    else
+        return not not res1,res3
+    end
+end
+
+----------------
+-- Load Lua code as a text or binary chunk.
+-- @param ld code string or loader
+-- @param[opt] source name of chunk for errors
+-- @param[opt] mode 'b', 't' or 'bt'
+-- @param[opt] env environment to load the chunk in
+-- @function compat.load
+
+---------------
+-- Get environment of a function.
+-- With Lua 5.2, may return nil for a function with no global references!
+-- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
+-- @param f a function or a call stack reference
+-- @function compat.getfenv
+
+---------------
+-- Set environment of a function
+-- @param f a function or a call stack reference
+-- @param env a table that becomes the new environment of `f`
+-- @function compat.setfenv
+
+if compat.lua51 then -- define Lua 5.2 style load()
+    if not isJit then -- but LuaJIT's load _is_ compatible
+        local lua51_load = load
+        function compat.load(str,src,mode,env)
+            local chunk,err
+            if type(str) == 'string' then
+                if str:byte(1) == 27 and not (mode or 'bt'):find 'b' then
+                    return nil,"attempt to load a binary chunk"
+                end
+                chunk,err = loadstring(str,src)
+            else
+                chunk,err = lua51_load(str,src)
+            end
+            if chunk and env then setfenv(chunk,env) end
+            return chunk,err
+        end
+    else
+        compat.load = load
+    end
+    compat.setfenv, compat.getfenv = setfenv, getfenv
+else
+    compat.load = load
+    -- setfenv/getfenv replacements for Lua 5.2
+    -- by Sergey Rozhenko
+    -- http://lua-users.org/lists/lua-l/2010-06/msg00313.html
+    -- Roberto Ierusalimschy notes that it is possible for getfenv to return nil
+    -- in the case of a function with no globals:
+    -- http://lua-users.org/lists/lua-l/2010-06/msg00315.html
+    function compat.setfenv(f, t)
+        f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
+        local name
+        local up = 0
+        repeat
+            up = up + 1
+            name = debug.getupvalue(f, up)
+        until name == '_ENV' or name == nil
+        if name then
+            debug.upvaluejoin(f, up, function() return name end, 1) -- use unique upvalue
+            debug.setupvalue(f, up, t)
+        end
+        if f ~= 0 then return f end
+    end
+
+    function compat.getfenv(f)
+        local f = f or 0
+        f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func)
+        local name, val
+        local up = 0
+        repeat
+            up = up + 1
+            name, val = debug.getupvalue(f, up)
+        until name == '_ENV' or name == nil
+        return val
+    end
+end
+
+--- Lua 5.2 Functions Available for 5.1
+-- @section lua52
+
+--- pack an argument list into a table.
+-- @param ... any arguments
+-- @return a table with field n set to the length
+-- @return the length
+-- @function table.pack
+if not table.pack then
+    function table.pack (...)
+        return {n=select('#',...); ...}
+    end
+end
+
+------
+-- return the full path where a Lua module name would be matched.
+-- @param mod module name, possibly dotted
+-- @param path a path in the same form as package.path or package.cpath
+-- @see path.package_path
+-- @function package.searchpath
+if not package.searchpath then
+    local sep = package.config:sub(1,1)
+    function package.searchpath (mod,path)
+        mod = mod:gsub('%.',sep)
+        for m in path:gmatch('[^;]+') do
+            local nm = m:gsub('?',mod)
+            local f = io.open(nm,'r')
+            if f then f:close(); return nm end
+        end
+    end
+end
+
+return compat
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/stringx.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/stringx.lua
new file mode 100644 (file)
index 0000000..f5a27d4
--- /dev/null
@@ -0,0 +1,548 @@
+--- Python-style extended string library.
+--
+-- see 3.6.1 of the Python reference.
+-- If you want to make these available as string methods, then say
+-- `stringx.import()` to bring them into the standard `string` table.
+--
+-- See @{03-strings.md|the Guide}
+--
+-- Dependencies: `pl.utils`
+-- @module pl.stringx
+local utils = require 'pl.utils'
+local string = string
+local find = string.find
+local type,setmetatable,ipairs = type,setmetatable,ipairs
+local error = error
+local gsub = string.gsub
+local rep = string.rep
+local sub = string.sub
+local concat = table.concat
+local append = table.insert
+local escape = utils.escape
+local ceil, max = math.ceil, math.max
+local assert_arg,usplit = utils.assert_arg,utils.split
+local lstrip
+
+local function assert_string (n,s)
+    assert_arg(n,s,'string')
+end
+
+local function non_empty(s)
+    return #s > 0
+end
+
+local function assert_nonempty_string(n,s)
+    assert_arg(n,s,'string',non_empty,'must be a non-empty string')
+end
+
+local function makelist(l)
+    return setmetatable(l, require('pl.List'))
+end
+
+local stringx = {}
+
+------------------
+-- String Predicates
+-- @section predicates
+
+--- does s only contain alphabetic characters?
+-- @string s a string
+function stringx.isalpha(s)
+    assert_string(1,s)
+    return find(s,'^%a+$') == 1
+end
+
+--- does s only contain digits?
+-- @string s a string
+function stringx.isdigit(s)
+    assert_string(1,s)
+    return find(s,'^%d+$') == 1
+end
+
+--- does s only contain alphanumeric characters?
+-- @string s a string
+function stringx.isalnum(s)
+    assert_string(1,s)
+    return find(s,'^%w+$') == 1
+end
+
+--- does s only contain spaces?
+-- @string s a string
+function stringx.isspace(s)
+    assert_string(1,s)
+    return find(s,'^%s+$') == 1
+end
+
+--- does s only contain lower case characters?
+-- @string s a string
+function stringx.islower(s)
+    assert_string(1,s)
+    return find(s,'^[%l%s]+$') == 1
+end
+
+--- does s only contain upper case characters?
+-- @string s a string
+function stringx.isupper(s)
+    assert_string(1,s)
+    return find(s,'^[%u%s]+$') == 1
+end
+
+local function raw_startswith(s, prefix)
+    return find(s,prefix,1,true) == 1
+end
+
+local function raw_endswith(s, suffix)
+    return #s >= #suffix and find(s, suffix, #s-#suffix+1, true) and true or false
+end
+
+local function test_affixes(s, affixes, fn)
+    if type(affixes) == 'string' then
+        return fn(s,affixes)
+    elseif type(affixes) == 'table' then
+        for _,affix in ipairs(affixes) do
+            if fn(s,affix) then return true end
+        end
+        return false
+    else
+        error(("argument #2 expected a 'string' or a 'table', got a '%s'"):format(type(affixes)))
+    end
+end
+
+--- does s start with prefix or one of prefixes?
+-- @string s a string
+-- @param prefix a string or an array of strings
+function stringx.startswith(s,prefix)
+    assert_string(1,s)
+    return test_affixes(s,prefix,raw_startswith)
+end
+
+--- does s end with suffix or one of suffixes?
+-- @string s a string
+-- @param suffix a string or an array of strings
+function stringx.endswith(s,suffix)
+    assert_string(1,s)
+    return test_affixes(s,suffix,raw_endswith)
+end
+
+--- Strings and Lists
+-- @section lists
+
+--- concatenate the strings using this string as a delimiter.
+-- @string s the string
+-- @param seq a table of strings or numbers
+-- @usage (' '):join {1,2,3} == '1 2 3'
+function stringx.join(s,seq)
+    assert_string(1,s)
+    return concat(seq,s)
+end
+
+--- Split a string into a list of lines.
+-- `"\r"`, `"\n"`, and `"\r\n"` are considered line ends.
+-- They are not included in the lines unless `keepends` is passed.
+-- Terminal line end does not produce an extra line.
+-- Splitting an empty string results in an empty list.
+-- @string s the string.
+-- @bool[opt] keep_ends include line ends.
+function stringx.splitlines(s, keep_ends)
+    assert_string(1, s)
+    local res = {}
+    local pos = 1
+    while true do
+        local line_end_pos = find(s, '[\r\n]', pos)
+        if not line_end_pos then
+            break
+        end
+
+        local line_end = sub(s, line_end_pos, line_end_pos)
+        if line_end == '\r' and sub(s, line_end_pos + 1, line_end_pos + 1) == '\n' then
+            line_end = '\r\n'
+        end
+
+        local line = sub(s, pos, line_end_pos - 1)
+        if keep_ends then
+            line = line .. line_end
+        end
+        append(res, line)
+
+        pos = line_end_pos + #line_end
+    end
+
+    if pos <= #s then
+        append(res, sub(s, pos))
+    end
+    return makelist(res)
+end
+
+--- split a string into a list of strings using a delimiter.
+-- @function split
+-- @string s the string
+-- @string[opt] re a delimiter (defaults to whitespace)
+-- @int[opt] n maximum number of results
+-- @usage #(('one two'):split()) == 2
+-- @usage ('one,two,three'):split(',') == List{'one','two','three'}
+-- @usage ('one,two,three'):split(',',2) == List{'one','two,three'}
+function stringx.split(s,re,n)
+    assert_string(1,s)
+    local plain = true
+    if not re then -- default spaces
+        s = lstrip(s)
+        plain = false
+    end
+    local res = usplit(s,re,plain,n)
+    if re and re ~= '' and find(s,re,-#re,true) then
+        res[#res+1] = ""
+    end
+       return makelist(res)
+end
+
+--- replace all tabs in s with tabsize spaces. If not specified, tabsize defaults to 8.
+-- with 0.9.5 this now correctly expands to the next tab stop (if you really
+-- want to just replace tabs, use :gsub('\t','  ') etc)
+-- @string s the string
+-- @int tabsize[opt=8] number of spaces to expand each tab
+function stringx.expandtabs(s,tabsize)
+    assert_string(1,s)
+    tabsize = tabsize or 8
+    return (s:gsub("([^\t\r\n]*)\t", function(before_tab)
+        return before_tab .. (" "):rep(tabsize - #before_tab % tabsize)
+    end))
+end
+
+--- Finding and Replacing
+-- @section find
+
+local function _find_all(s,sub,first,last)
+    first = first or 1
+    last = last or #s
+    if sub == '' then return last+1,last-first+1 end
+    local i1,i2 = find(s,sub,first,true)
+    local res
+    local k = 0
+    while i1 do
+        if last and i2 > last then break end
+        res = i1
+        k = k + 1
+        i1,i2 = find(s,sub,i2+1,true)
+    end
+    return res,k
+end
+
+--- find index of first instance of sub in s from the left.
+-- @string s the string
+-- @string sub substring
+-- @int[opt] first first index
+-- @int[opt] last last index
+function stringx.lfind(s,sub,first,last)
+    assert_string(1,s)
+    assert_string(2,sub)
+    local i1, i2 = find(s,sub,first,true)
+
+    if i1 and (not last or i2 <= last) then
+        return i1
+    else
+        return nil
+    end
+end
+
+--- find index of first instance of sub in s from the right.
+-- @string s the string
+-- @string sub substring
+-- @int[opt] first first index
+-- @int[opt] last last index
+function stringx.rfind(s,sub,first,last)
+    assert_string(1,s)
+    assert_string(2,sub)
+    return (_find_all(s,sub,first,last))
+end
+
+--- replace up to n instances of old by new in the string s.
+-- if n is not present, replace all instances.
+-- @string s the string
+-- @string old the target substring
+-- @string new the substitution
+-- @int[opt] n optional maximum number of substitutions
+-- @return result string
+function stringx.replace(s,old,new,n)
+    assert_string(1,s)
+    assert_string(2,old)
+    assert_string(3,new)
+    return (gsub(s,escape(old),new:gsub('%%','%%%%'),n))
+end
+
+--- count all instances of substring in string.
+-- @string s the string
+-- @string sub substring
+function stringx.count(s,sub)
+    assert_string(1,s)
+    local i,k = _find_all(s,sub,1)
+    return k
+end
+
+--- Stripping and Justifying
+-- @section strip
+
+local function _just(s,w,ch,left,right)
+    local n = #s
+    if w > n then
+        if not ch then ch = ' ' end
+        local f1,f2
+        if left and right then
+            local rn = ceil((w-n)/2)
+            local ln = w - n - rn
+            f1 = rep(ch,ln)
+            f2 = rep(ch,rn)
+        elseif right then
+            f1 = rep(ch,w-n)
+            f2 = ''
+        else
+            f2 = rep(ch,w-n)
+            f1 = ''
+        end
+        return f1..s..f2
+    else
+        return s
+    end
+end
+
+--- left-justify s with width w.
+-- @string s the string
+-- @int w width of justification
+-- @string[opt=' '] ch padding character
+function stringx.ljust(s,w,ch)
+    assert_string(1,s)
+    assert_arg(2,w,'number')
+    return _just(s,w,ch,true,false)
+end
+
+--- right-justify s with width w.
+-- @string s the string
+-- @int w width of justification
+-- @string[opt=' '] ch padding character
+function stringx.rjust(s,w,ch)
+    assert_string(1,s)
+    assert_arg(2,w,'number')
+    return _just(s,w,ch,false,true)
+end
+
+--- center-justify s with width w.
+-- @string s the string
+-- @int w width of justification
+-- @string[opt=' '] ch padding character
+function stringx.center(s,w,ch)
+    assert_string(1,s)
+    assert_arg(2,w,'number')
+    return _just(s,w,ch,true,true)
+end
+
+local function _strip(s,left,right,chrs)
+    if not chrs then
+        chrs = '%s'
+    else
+        chrs = '['..escape(chrs)..']'
+    end
+    if left then
+        local i1,i2 = find(s,'^'..chrs..'*')
+        if i2 >= i1 then
+            s = sub(s,i2+1)
+        end
+    end
+    if right then
+        local i1,i2 = find(s,chrs..'*$')
+        if i2 >= i1 then
+            s = sub(s,1,i1-1)
+        end
+    end
+    return s
+end
+
+--- trim any whitespace on the left of s.
+-- @string s the string
+-- @string[opt='%s'] chrs default any whitespace character,
+--  but can be a string of characters to be trimmed
+function stringx.lstrip(s,chrs)
+    assert_string(1,s)
+    return _strip(s,true,false,chrs)
+end
+lstrip = stringx.lstrip
+
+--- trim any whitespace on the right of s.
+-- @string s the string
+-- @string[opt='%s'] chrs default any whitespace character,
+--  but can be a string of characters to be trimmed
+function stringx.rstrip(s,chrs)
+    assert_string(1,s)
+    return _strip(s,false,true,chrs)
+end
+
+--- trim any whitespace on both left and right of s.
+-- @string s the string
+-- @string[opt='%s'] chrs default any whitespace character,
+--  but can be a string of characters to be trimmed
+function stringx.strip(s,chrs)
+    assert_string(1,s)
+    return _strip(s,true,true,chrs)
+end
+
+--- Partioning Strings
+-- @section partioning
+
+--- split a string using a pattern. Note that at least one value will be returned!
+-- @string s the string
+-- @string[opt='%s'] re a Lua string pattern (defaults to whitespace)
+-- @return the parts of the string
+-- @usage  a,b = line:splitv('=')
+function stringx.splitv(s,re)
+    assert_string(1,s)
+    return utils.splitv(s,re)
+end
+
+-- The partition functions split a string  using a delimiter into three parts:
+-- the part before, the delimiter itself, and the part afterwards
+local function _partition(p,delim,fn)
+    local i1,i2 = fn(p,delim)
+    if not i1 or i1 == -1 then
+        return p,'',''
+    else
+        if not i2 then i2 = i1 end
+        return sub(p,1,i1-1),sub(p,i1,i2),sub(p,i2+1)
+    end
+end
+
+--- partition the string using first occurance of a delimiter
+-- @string s the string
+-- @string ch delimiter
+-- @return part before ch
+-- @return ch
+-- @return part after ch
+function stringx.partition(s,ch)
+    assert_string(1,s)
+    assert_nonempty_string(2,ch)
+    return _partition(s,ch,stringx.lfind)
+end
+
+--- partition the string p using last occurance of a delimiter
+-- @string s the string
+-- @string ch delimiter
+-- @return part before ch
+-- @return ch
+-- @return part after ch
+function stringx.rpartition(s,ch)
+    assert_string(1,s)
+    assert_nonempty_string(2,ch)
+    return _partition(s,ch,stringx.rfind)
+end
+
+--- return the 'character' at the index.
+-- @string s the string
+-- @int idx an index (can be negative)
+-- @return a substring of length 1 if successful, empty string otherwise.
+function stringx.at(s,idx)
+    assert_string(1,s)
+    assert_arg(2,idx,'number')
+    return sub(s,idx,idx)
+end
+
+--- Miscelaneous
+-- @section misc
+
+--- return an iterator over all lines in a string
+-- @string s the string
+-- @return an iterator
+function stringx.lines(s)
+    assert_string(1,s)
+    if not s:find '\n$' then s = s..'\n' end
+    return s:gmatch('([^\n]*)\n')
+end
+
+--- iniital word letters uppercase ('title case').
+-- Here 'words' mean chunks of non-space characters.
+-- @string s the string
+-- @return a string with each word's first letter uppercase
+function stringx.title(s)
+    assert_string(1,s)
+    return (s:gsub('(%S)(%S*)',function(f,r)
+        return f:upper()..r:lower()
+    end))
+end
+
+stringx.capitalize = stringx.title
+
+local ellipsis = '...'
+local n_ellipsis = #ellipsis
+
+--- Return a shortened version of a string.
+-- Fits string within w characters. Removed characters are marked with ellipsis.
+-- @string s the string
+-- @int w the maxinum size allowed
+-- @bool tail true if we want to show the end of the string (head otherwise)
+-- @usage ('1234567890'):shorten(8) == '12345...'
+-- @usage ('1234567890'):shorten(8, true) == '...67890'
+-- @usage ('1234567890'):shorten(20) == '1234567890'
+function stringx.shorten(s,w,tail)
+    assert_string(1,s)
+    if #s > w then
+        if w < n_ellipsis then return ellipsis:sub(1,w) end
+        if tail then
+            local i = #s - w + 1 + n_ellipsis
+            return ellipsis .. s:sub(i)
+        else
+            return s:sub(1,w-n_ellipsis) .. ellipsis
+        end
+    end
+    return s
+end
+
+--- Utility function that finds any patterns that match a long string's an open or close.
+-- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with.
+-- Right now, it simply returns the greatest number of them found.
+-- @param s The string
+-- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches.
+local function has_lquote(s)
+    local lstring_pat = '([%[%]])(=*)%1'
+    local equals
+    local start, finish, bracket, new_equals = nil, 1, nil, nil
+
+    repeat
+        start, finish, bracket, new_equals = s:find(lstring_pat, finish)
+        if new_equals then
+            equals = max(equals or 0, #new_equals)
+        end
+    until not new_equals
+
+    return equals 
+end
+
+--- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
+-- @param s The string to be quoted.
+-- @return The quoted string.
+function stringx.quote_string(s)
+    assert_string(1,s)
+    -- Find out if there are any embedded long-quote sequences that may cause issues.
+    -- This is important when strings are embedded within strings, like when serializing.
+    -- Append a closing bracket to catch unfinished long-quote sequences at the end of the string.
+    local equal_signs = has_lquote(s .. "]")
+
+    -- Note that strings containing "\r" can't be quoted using long brackets
+    -- as Lua lexer converts all newlines to "\n" within long strings.
+    if (s:find("\n") or equal_signs) and not s:find("\r") then
+        -- If there is an embedded sequence that matches a long quote, then
+        -- find the one with the maximum number of = signs and add one to that number.
+        equal_signs = ("="):rep((equal_signs or -1) + 1)
+        -- Long strings strip out leading newline. We want to retain that, when quoting.
+        if s:find("^\n") then s = "\n" .. s end
+        local lbracket, rbracket =  
+            "[" .. equal_signs .. "[",  
+            "]" .. equal_signs .. "]"
+        s = lbracket .. s .. rbracket
+    else
+        -- Escape funny stuff. Lua 5.1 does not handle "\r" correctly.
+        s = ("%q"):format(s):gsub("\r", "\\r")
+    end
+    return s
+end
+
+function stringx.import()
+    utils.import(stringx,string)
+end
+
+return stringx
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/tablex.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/tablex.lua
new file mode 100644 (file)
index 0000000..03ecba9
--- /dev/null
@@ -0,0 +1,927 @@
+--- Extended operations on Lua tables.
+--
+-- See @{02-arrays.md.Useful_Operations_on_Tables|the Guide}
+--
+-- Dependencies: `pl.utils`, `pl.types`
+-- @module pl.tablex
+local utils = require ('pl.utils')
+local types = require ('pl.types')
+local getmetatable,setmetatable,require = getmetatable,setmetatable,require
+local tsort,append,remove = table.sort,table.insert,table.remove
+local min = math.min
+local pairs,type,unpack,select,tostring = pairs,type,utils.unpack,select,tostring
+local function_arg = utils.function_arg
+local assert_arg = utils.assert_arg
+
+local tablex = {}
+
+-- generally, functions that make copies of tables try to preserve the metatable.
+-- However, when the source has no obvious type, then we attach appropriate metatables
+-- like List, Map, etc to the result.
+local function setmeta (res,tbl,pl_class)
+    local mt = getmetatable(tbl) or pl_class and require('pl.' .. pl_class)
+    return mt and setmetatable(res, mt) or res
+end
+
+local function makelist(l)
+    return setmetatable(l, require('pl.List'))
+end
+
+local function makemap(m)
+    return setmetatable(m, require('pl.Map'))
+end
+
+local function complain (idx,msg)
+    error(('argument %d is not %s'):format(idx,msg),3)
+end
+
+local function assert_arg_indexable (idx,val)
+    if not types.is_indexable(val) then
+        complain(idx,"indexable")
+    end
+end
+
+local function assert_arg_iterable (idx,val)
+    if not types.is_iterable(val) then
+        complain(idx,"iterable")
+    end
+end
+
+local function assert_arg_writeable (idx,val)
+    if not types.is_writeable(val) then
+        complain(idx,"writeable")
+    end
+end
+
+--- copy a table into another, in-place.
+-- @within Copying
+-- @tab t1 destination table
+-- @tab t2 source (actually any iterable object)
+-- @return first table
+function tablex.update (t1,t2)
+    assert_arg_writeable(1,t1)
+    assert_arg_iterable(2,t2)
+    for k,v in pairs(t2) do
+        t1[k] = v
+    end
+    return t1
+end
+
+--- total number of elements in this table.
+-- Note that this is distinct from `#t`, which is the number
+-- of values in the array part; this value will always
+-- be greater or equal. The difference gives the size of
+-- the hash part, for practical purposes. Works for any
+-- object with a __pairs metamethod.
+-- @tab t a table
+-- @return the size
+function tablex.size (t)
+    assert_arg_iterable(1,t)
+    local i = 0
+    for k in pairs(t) do i = i + 1 end
+    return i
+end
+
+--- make a shallow copy of a table
+-- @within Copying
+-- @tab t an iterable source
+-- @return new table
+function tablex.copy (t)
+    assert_arg_iterable(1,t)
+    local res = {}
+    for k,v in pairs(t) do
+        res[k] = v
+    end
+    return res
+end
+
+--- make a deep copy of a table, recursively copying all the keys and fields.
+-- This will also set the copied table's metatable to that of the original.
+-- @within Copying
+-- @tab t A table
+-- @return new table
+function tablex.deepcopy(t)
+    if type(t) ~= 'table' then return t end
+    assert_arg_iterable(1,t)
+    local mt = getmetatable(t)
+    local res = {}
+    for k,v in pairs(t) do
+        if type(v) == 'table' then
+            v = tablex.deepcopy(v)
+        end
+        res[k] = v
+    end
+    setmetatable(res,mt)
+    return res
+end
+
+local abs, deepcompare = math.abs
+
+--- compare two values.
+-- if they are tables, then compare their keys and fields recursively.
+-- @within Comparing
+-- @param t1 A value
+-- @param t2 A value
+-- @bool[opt] ignore_mt if true, ignore __eq metamethod (default false)
+-- @number[opt] eps if defined, then used for any number comparisons
+-- @return true or false
+function tablex.deepcompare(t1,t2,ignore_mt,eps)
+    local ty1 = type(t1)
+    local ty2 = type(t2)
+    if ty1 ~= ty2 then return false end
+    -- non-table types can be directly compared
+    if ty1 ~= 'table' then
+        if ty1 == 'number' and eps then return abs(t1-t2) < eps end
+        return t1 == t2
+    end
+    -- as well as tables which have the metamethod __eq
+    local mt = getmetatable(t1)
+    if not ignore_mt and mt and mt.__eq then return t1 == t2 end
+    for k1 in pairs(t1) do
+        if t2[k1]==nil then return false end
+    end
+    for k2 in pairs(t2) do
+        if t1[k2]==nil then return false end
+    end
+    for k1,v1 in pairs(t1) do
+        local v2 = t2[k1]
+        if not deepcompare(v1,v2,ignore_mt,eps) then return false end
+    end
+
+    return true
+end
+
+deepcompare = tablex.deepcompare
+
+--- compare two arrays using a predicate.
+-- @within Comparing
+-- @array t1 an array
+-- @array t2 an array
+-- @func cmp A comparison function
+function tablex.compare (t1,t2,cmp)
+    assert_arg_indexable(1,t1)
+    assert_arg_indexable(2,t2)
+    if #t1 ~= #t2 then return false end
+    cmp = function_arg(3,cmp)
+    for k = 1,#t1 do
+        if not cmp(t1[k],t2[k]) then return false end
+    end
+    return true
+end
+
+--- compare two list-like tables using an optional predicate, without regard for element order.
+-- @within Comparing
+-- @array t1 a list-like table
+-- @array t2 a list-like table
+-- @param cmp A comparison function (may be nil)
+function tablex.compare_no_order (t1,t2,cmp)
+    assert_arg_indexable(1,t1)
+    assert_arg_indexable(2,t2)
+    if cmp then cmp = function_arg(3,cmp) end
+    if #t1 ~= #t2 then return false end
+    local visited = {}
+    for i = 1,#t1 do
+        local val = t1[i]
+        local gotcha
+        for j = 1,#t2 do if not visited[j] then
+            local match
+            if cmp then match = cmp(val,t2[j]) else match = val == t2[j] end
+            if match then
+                gotcha = j
+                break
+            end
+        end end
+        if not gotcha then return false end
+        visited[gotcha] = true
+    end
+    return true
+end
+
+
+--- return the index of a value in a list.
+-- Like string.find, there is an optional index to start searching,
+-- which can be negative.
+-- @within Finding
+-- @array t A list-like table
+-- @param val A value
+-- @int idx index to start; -1 means last element,etc (default 1)
+-- @return index of value or nil if not found
+-- @usage find({10,20,30},20) == 2
+-- @usage find({'a','b','a','c'},'a',2) == 3
+function tablex.find(t,val,idx)
+    assert_arg_indexable(1,t)
+    idx = idx or 1
+    if idx < 0 then idx = #t + idx + 1 end
+    for i = idx,#t do
+        if t[i] == val then return i end
+    end
+    return nil
+end
+
+--- return the index of a value in a list, searching from the end.
+-- Like string.find, there is an optional index to start searching,
+-- which can be negative.
+-- @within Finding
+-- @array t A list-like table
+-- @param val A value
+-- @param idx index to start; -1 means last element,etc (default 1)
+-- @return index of value or nil if not found
+-- @usage rfind({10,10,10},10) == 3
+function tablex.rfind(t,val,idx)
+    assert_arg_indexable(1,t)
+    idx = idx or #t
+    if idx < 0 then idx = #t + idx + 1 end
+    for i = idx,1,-1 do
+        if t[i] == val then return i end
+    end
+    return nil
+end
+
+
+--- return the index (or key) of a value in a table using a comparison function.
+-- @within Finding
+-- @tab t A table
+-- @func cmp A comparison function
+-- @param arg an optional second argument to the function
+-- @return index of value, or nil if not found
+-- @return value returned by comparison function
+function tablex.find_if(t,cmp,arg)
+    assert_arg_iterable(1,t)
+    cmp = function_arg(2,cmp)
+    for k,v in pairs(t) do
+        local c = cmp(v,arg)
+        if c then return k,c end
+    end
+    return nil
+end
+
+--- return a list of all values in a table indexed by another list.
+-- @tab tbl a table
+-- @array idx an index table (a list of keys)
+-- @return a list-like table
+-- @usage index_by({10,20,30,40},{2,4}) == {20,40}
+-- @usage index_by({one=1,two=2,three=3},{'one','three'}) == {1,3}
+function tablex.index_by(tbl,idx)
+    assert_arg_indexable(1,tbl)
+    assert_arg_indexable(2,idx)
+    local res = {}
+    for i = 1,#idx do
+        res[i] = tbl[idx[i]]
+    end
+    return setmeta(res,tbl,'List')
+end
+
+--- apply a function to all values of a table.
+-- This returns a table of the results.
+-- Any extra arguments are passed to the function.
+-- @within MappingAndFiltering
+-- @func fun A function that takes at least one argument
+-- @tab t A table
+-- @param ... optional arguments
+-- @usage map(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900,fred=4}
+function tablex.map(fun,t,...)
+    assert_arg_iterable(1,t)
+    fun = function_arg(1,fun)
+    local res = {}
+    for k,v in pairs(t) do
+        res[k] = fun(v,...)
+    end
+    return setmeta(res,t)
+end
+
+--- apply a function to all values of a list.
+-- This returns a table of the results.
+-- Any extra arguments are passed to the function.
+-- @within MappingAndFiltering
+-- @func fun A function that takes at least one argument
+-- @array t a table (applies to array part)
+-- @param ... optional arguments
+-- @return a list-like table
+-- @usage imap(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900}
+function tablex.imap(fun,t,...)
+    assert_arg_indexable(1,t)
+    fun = function_arg(1,fun)
+    local res = {}
+    for i = 1,#t do
+        res[i] = fun(t[i],...) or false
+    end
+    return setmeta(res,t,'List')
+end
+
+--- apply a named method to values from a table.
+-- @within MappingAndFiltering
+-- @string name the method name
+-- @array t a list-like table
+-- @param ... any extra arguments to the method
+function tablex.map_named_method (name,t,...)
+    utils.assert_string(1,name)
+    assert_arg_indexable(2,t)
+    local res = {}
+    for i = 1,#t do
+        local val = t[i]
+        local fun = val[name]
+        res[i] = fun(val,...)
+    end
+    return setmeta(res,t,'List')
+end
+
+--- apply a function to all values of a table, in-place.
+-- Any extra arguments are passed to the function.
+-- @func fun A function that takes at least one argument
+-- @tab t a table
+-- @param ... extra arguments
+function tablex.transform (fun,t,...)
+    assert_arg_iterable(1,t)
+    fun = function_arg(1,fun)
+    for k,v in pairs(t) do
+        t[k] = fun(v,...)
+    end
+end
+
+--- generate a table of all numbers in a range.
+-- This is consistent with a numerical for loop.
+-- @int start  number
+-- @int finish number
+-- @int[opt=1] step  make this negative for start < finish
+function tablex.range (start,finish,step)
+    local res
+    step = step or 1
+    if start == finish then
+        res = {start}
+    elseif (start > finish and step > 0) or (finish > start and step < 0) then
+        res = {}
+    else
+        local k = 1
+        res = {}
+        for i=start,finish,step do res[k]=i; k=k+1 end
+    end
+    return makelist(res)
+end
+
+--- apply a function to values from two tables.
+-- @within MappingAndFiltering
+-- @func fun a function of at least two arguments
+-- @tab t1 a table
+-- @tab t2 a table
+-- @param ... extra arguments
+-- @return a table
+-- @usage map2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23,m=44}
+function tablex.map2 (fun,t1,t2,...)
+    assert_arg_iterable(1,t1)
+    assert_arg_iterable(2,t2)
+    fun = function_arg(1,fun)
+    local res = {}
+    for k,v in pairs(t1) do
+        res[k] = fun(v,t2[k],...)
+    end
+    return setmeta(res,t1,'List')
+end
+
+--- apply a function to values from two arrays.
+-- The result will be the length of the shortest array.
+-- @within MappingAndFiltering
+-- @func fun a function of at least two arguments
+-- @array t1 a list-like table
+-- @array t2 a list-like table
+-- @param ... extra arguments
+-- @usage imap2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23}
+function tablex.imap2 (fun,t1,t2,...)
+    assert_arg_indexable(2,t1)
+    assert_arg_indexable(3,t2)
+    fun = function_arg(1,fun)
+    local res,n = {},math.min(#t1,#t2)
+    for i = 1,n do
+        res[i] = fun(t1[i],t2[i],...)
+    end
+    return res
+end
+
+--- 'reduce' a list using a binary function.
+-- @func fun a function of two arguments
+-- @array t a list-like table
+-- @array memo optional initial memo value. Defaults to first value in table.
+-- @return the result of the function
+-- @usage reduce('+',{1,2,3,4}) == 10
+function tablex.reduce (fun,t,memo)
+    assert_arg_indexable(2,t)
+    fun = function_arg(1,fun)
+    local n = #t
+    if n == 0 then
+        return memo
+    end
+    local res = memo and fun(memo, t[1]) or t[1]
+    for i = 2,n do
+        res = fun(res,t[i])
+    end
+    return res
+end
+
+--- apply a function to all elements of a table.
+-- The arguments to the function will be the value,
+-- the key and _finally_ any extra arguments passed to this function.
+-- Note that the Lua 5.0 function table.foreach passed the _key_ first.
+-- @within Iterating
+-- @tab t a table
+-- @func fun a function with at least one argument
+-- @param ... extra arguments
+function tablex.foreach(t,fun,...)
+    assert_arg_iterable(1,t)
+    fun = function_arg(2,fun)
+    for k,v in pairs(t) do
+        fun(v,k,...)
+    end
+end
+
+--- apply a function to all elements of a list-like table in order.
+-- The arguments to the function will be the value,
+-- the index and _finally_ any extra arguments passed to this function
+-- @within Iterating
+-- @array t a table
+-- @func fun a function with at least one argument
+-- @param ... optional arguments
+function tablex.foreachi(t,fun,...)
+    assert_arg_indexable(1,t)
+    fun = function_arg(2,fun)
+    for i = 1,#t do
+        fun(t[i],i,...)
+    end
+end
+
+--- Apply a function to a number of tables.
+-- A more general version of map
+-- The result is a table containing the result of applying that function to the
+-- ith value of each table. Length of output list is the minimum length of all the lists
+-- @within MappingAndFiltering
+-- @func fun a function of n arguments
+-- @tab ... n tables
+-- @usage mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333}
+-- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300}
+-- @param fun A function that takes as many arguments as there are tables
+function tablex.mapn(fun,...)
+    fun = function_arg(1,fun)
+    local res = {}
+    local lists = {...}
+    local minn = 1e40
+    for i = 1,#lists do
+        minn = min(minn,#(lists[i]))
+    end
+    for i = 1,minn do
+        local args,k = {},1
+        for j = 1,#lists do
+            args[k] = lists[j][i]
+            k = k + 1
+        end
+        res[#res+1] = fun(unpack(args))
+    end
+    return res
+end
+
+--- call the function with the key and value pairs from a table.
+-- The function can return a value and a key (note the order!). If both
+-- are not nil, then this pair is inserted into the result: if the key already exists, we convert the value for that
+-- key into a table and append into it. If only value is not nil, then it is appended to the result.
+-- @within MappingAndFiltering
+-- @func fun A function which will be passed each key and value as arguments, plus any extra arguments to pairmap.
+-- @tab t A table
+-- @param ... optional arguments
+-- @usage pairmap(function(k,v) return v end,{fred=10,bonzo=20}) is {10,20} _or_ {20,10}
+-- @usage pairmap(function(k,v) return {k,v},k end,{one=1,two=2}) is {one={'one',1},two={'two',2}}
+function tablex.pairmap(fun,t,...)
+    assert_arg_iterable(1,t)
+    fun = function_arg(1,fun)
+    local res = {}
+    for k,v in pairs(t) do
+        local rv,rk = fun(k,v,...)
+        if rk then
+                       if res[rk] then
+                               if type(res[rk]) == 'table' then
+                                       table.insert(res[rk],rv)
+                               else
+                                       res[rk] = {res[rk], rv}
+                               end
+                       else
+               res[rk] = rv
+                       end
+        else
+            res[#res+1] = rv
+        end
+    end
+    return res
+end
+
+local function keys_op(i,v) return i end
+
+--- return all the keys of a table in arbitrary order.
+-- @within Extraction
+--  @tab t A table
+function tablex.keys(t)
+    assert_arg_iterable(1,t)
+    return makelist(tablex.pairmap(keys_op,t))
+end
+
+local function values_op(i,v) return v end
+
+--- return all the values of the table in arbitrary order
+-- @within Extraction
+--  @tab t A table
+function tablex.values(t)
+    assert_arg_iterable(1,t)
+    return makelist(tablex.pairmap(values_op,t))
+end
+
+local function index_map_op (i,v) return i,v end
+
+--- create an index map from a list-like table. The original values become keys,
+-- and the associated values are the indices into the original list.
+-- @array t a list-like table
+-- @return a map-like table
+function tablex.index_map (t)
+    assert_arg_indexable(1,t)
+    return makemap(tablex.pairmap(index_map_op,t))
+end
+
+local function set_op(i,v) return true,v end
+
+--- create a set from a list-like table. A set is a table where the original values
+-- become keys, and the associated values are all true.
+-- @array t a list-like table
+-- @return a set (a map-like table)
+function tablex.makeset (t)
+    assert_arg_indexable(1,t)
+    return setmetatable(tablex.pairmap(set_op,t),require('pl.Set'))
+end
+
+--- combine two tables, either as union or intersection. Corresponds to
+-- set operations for sets () but more general. Not particularly
+-- useful for list-like tables.
+-- @within Merging
+-- @tab t1 a table
+-- @tab t2 a table
+-- @bool dup true for a union, false for an intersection.
+-- @usage merge({alice=23,fred=34},{bob=25,fred=34}) is {fred=34}
+-- @usage merge({alice=23,fred=34},{bob=25,fred=34},true) is {bob=25,fred=34,alice=23}
+-- @see tablex.index_map
+function tablex.merge (t1,t2,dup)
+    assert_arg_iterable(1,t1)
+    assert_arg_iterable(2,t2)
+    local res = {}
+    for k,v in pairs(t1) do
+        if dup or t2[k] then res[k] = v end
+    end
+    if dup then
+      for k,v in pairs(t2) do
+        res[k] = v
+      end
+    end
+    return setmeta(res,t1,'Map')
+end
+
+--- the union of two map-like tables.
+-- If there are duplicate keys, the second table wins.
+-- @tab t1 a table
+-- @tab t2 a table
+-- @treturn tab
+-- @see tablex.merge
+function tablex.union(t1, t2)
+    return tablex.merge(t1, t2, true)
+end
+
+--- the intersection of two map-like tables.
+-- @tab t1 a table
+-- @tab t2 a table
+-- @treturn tab
+-- @see tablex.merge
+function tablex.intersection(t1, t2)
+    return tablex.merge(t1, t2, false)
+end
+
+--- a new table which is the difference of two tables.
+-- With sets (where the values are all true) this is set difference and
+-- symmetric difference depending on the third parameter.
+-- @within Merging
+-- @tab s1 a map-like table or set
+-- @tab s2 a map-like table or set
+-- @bool symm symmetric difference (default false)
+-- @return a map-like table or set
+function tablex.difference (s1,s2,symm)
+    assert_arg_iterable(1,s1)
+    assert_arg_iterable(2,s2)
+    local res = {}
+    for k,v in pairs(s1) do
+        if s2[k] == nil then res[k] = v end
+    end
+    if symm then
+        for k,v in pairs(s2) do
+            if s1[k] == nil then res[k] = v end
+        end
+    end
+    return setmeta(res,s1,'Map')
+end
+
+--- A table where the key/values are the values and value counts of the table.
+-- @array t a list-like table
+-- @func cmp a function that defines equality (otherwise uses ==)
+-- @return a map-like table
+-- @see seq.count_map
+function tablex.count_map (t,cmp)
+    assert_arg_indexable(1,t)
+    local res,mask = {},{}
+    cmp = function_arg(2,cmp or '==')
+    local n = #t
+    for i = 1,#t do
+        local v = t[i]
+        if not mask[v] then
+            mask[v] = true
+            -- check this value against all other values
+            res[v] = 1  -- there's at least one instance
+            for j = i+1,n do
+                local w = t[j]
+                local ok = cmp(v,w)
+                if ok then
+                    res[v] = res[v] + 1
+                    mask[w] = true
+                end
+            end
+        end
+    end
+    return makemap(res)
+end
+
+--- filter an array's values using a predicate function
+-- @within MappingAndFiltering
+-- @array t a list-like table
+-- @func pred a boolean function
+-- @param arg optional argument to be passed as second argument of the predicate
+function tablex.filter (t,pred,arg)
+    assert_arg_indexable(1,t)
+    pred = function_arg(2,pred)
+    local res,k = {},1
+    for i = 1,#t do
+        local v = t[i]
+        if pred(v,arg) then
+            res[k] = v
+            k = k + 1
+        end
+    end
+    return setmeta(res,t,'List')
+end
+
+--- return a table where each element is a table of the ith values of an arbitrary
+-- number of tables. It is equivalent to a matrix transpose.
+-- @within Merging
+-- @usage zip({10,20,30},{100,200,300}) is {{10,100},{20,200},{30,300}}
+-- @array ... arrays to be zipped
+function tablex.zip(...)
+    return tablex.mapn(function(...) return {...} end,...)
+end
+
+local _copy
+function _copy (dest,src,idest,isrc,nsrc,clean_tail)
+    idest = idest or 1
+    isrc = isrc or 1
+    local iend
+    if not nsrc then
+        nsrc = #src
+        iend = #src
+    else
+        iend = isrc + min(nsrc-1,#src-isrc)
+    end
+    if dest == src then -- special case
+        if idest > isrc and iend >= idest then -- overlapping ranges
+            src = tablex.sub(src,isrc,nsrc)
+            isrc = 1; iend = #src
+        end
+    end
+    for i = isrc,iend do
+        dest[idest] = src[i]
+        idest = idest + 1
+    end
+    if clean_tail then
+        tablex.clear(dest,idest)
+    end
+    return dest
+end
+
+--- copy an array into another one, clearing `dest` after `idest+nsrc`, if necessary.
+-- @within Copying
+-- @array dest a list-like table
+-- @array src a list-like table
+-- @int[opt=1] idest where to start copying values into destination
+-- @int[opt=1] isrc where to start copying values from source
+-- @int[opt=#src] nsrc number of elements to copy from source
+function tablex.icopy (dest,src,idest,isrc,nsrc)
+    assert_arg_indexable(1,dest)
+    assert_arg_indexable(2,src)
+    return _copy(dest,src,idest,isrc,nsrc,true)
+end
+
+--- copy an array into another one.
+-- @within Copying
+-- @array dest a list-like table
+-- @array src a list-like table
+-- @int[opt=1] idest where to start copying values into destination
+-- @int[opt=1] isrc where to start copying values from source
+-- @int[opt=#src] nsrc number of elements to copy from source
+function tablex.move (dest,src,idest,isrc,nsrc)
+    assert_arg_indexable(1,dest)
+    assert_arg_indexable(2,src)
+    return _copy(dest,src,idest,isrc,nsrc,false)
+end
+
+function tablex._normalize_slice(self,first,last)
+  local sz = #self
+  if not first then first=1 end
+  if first<0 then first=sz+first+1 end
+  -- make the range _inclusive_!
+  if not last then last=sz end
+  if last < 0 then last=sz+1+last end
+  return first,last
+end
+
+--- Extract a range from a table, like  'string.sub'.
+-- If first or last are negative then they are relative to the end of the list
+-- eg. sub(t,-2) gives last 2 entries in a list, and
+-- sub(t,-4,-2) gives from -4th to -2nd
+-- @within Extraction
+-- @array t a list-like table
+-- @int first An index
+-- @int last An index
+-- @return a new List
+function tablex.sub(t,first,last)
+    assert_arg_indexable(1,t)
+    first,last = tablex._normalize_slice(t,first,last)
+    local res={}
+    for i=first,last do append(res,t[i]) end
+    return setmeta(res,t,'List')
+end
+
+--- set an array range to a value. If it's a function we use the result
+-- of applying it to the indices.
+-- @array t a list-like table
+-- @param val a value
+-- @int[opt=1] i1 start range
+-- @int[opt=#t] i2 end range
+function tablex.set (t,val,i1,i2)
+    assert_arg_indexable(1,t)
+    i1,i2 = i1 or 1,i2 or #t
+    if types.is_callable(val) then
+        for i = i1,i2 do
+            t[i] = val(i)
+        end
+    else
+        for i = i1,i2 do
+            t[i] = val
+        end
+    end
+end
+
+--- create a new array of specified size with initial value.
+-- @int n size
+-- @param val initial value (can be `nil`, but don't expect `#` to work!)
+-- @return the table
+function tablex.new (n,val)
+    local res = {}
+    tablex.set(res,val,1,n)
+    return res
+end
+
+--- clear out the contents of a table.
+-- @array t a list
+-- @param istart optional start position
+function tablex.clear(t,istart)
+    istart = istart or 1
+    for i = istart,#t do remove(t) end
+end
+
+--- insert values into a table.
+-- similar to `table.insert` but inserts values from given table `values`,
+-- not the object itself, into table `t` at position `pos`.
+-- @within Copying
+-- @array t the list
+-- @int[opt] position (default is at end)
+-- @array values
+function tablex.insertvalues(t, ...)
+    assert_arg(1,t,'table')
+    local pos, values
+    if select('#', ...) == 1 then
+        pos,values = #t+1, ...
+    else
+        pos,values = ...
+    end
+    if #values > 0 then
+        for i=#t,pos,-1 do
+            t[i+#values] = t[i]
+        end
+        local offset = 1 - pos
+        for i=pos,pos+#values-1 do
+            t[i] = values[i + offset]
+        end
+    end
+    return t
+end
+
+--- remove a range of values from a table.
+-- End of range may be negative.
+-- @array t a list-like table
+-- @int i1 start index
+-- @int i2 end index
+-- @return the table
+function tablex.removevalues (t,i1,i2)
+    assert_arg(1,t,'table')
+    i1,i2 = tablex._normalize_slice(t,i1,i2)
+    for i = i1,i2 do
+        remove(t,i1)
+    end
+    return t
+end
+
+local _find
+_find = function (t,value,tables)
+    for k,v in pairs(t) do
+        if v == value then return k end
+    end
+    for k,v in pairs(t) do
+        if not tables[v] and type(v) == 'table' then
+            tables[v] = true
+            local res = _find(v,value,tables)
+            if res then
+                res = tostring(res)
+                if type(k) ~= 'string' then
+                    return '['..k..']'..res
+                else
+                    return k..'.'..res
+                end
+            end
+        end
+    end
+end
+
+--- find a value in a table by recursive search.
+-- @within Finding
+-- @tab t the table
+-- @param value the value
+-- @array[opt] exclude any tables to avoid searching
+-- @usage search(_G,math.sin,{package.path}) == 'math.sin'
+-- @return a fieldspec, e.g. 'a.b' or 'math.sin'
+function tablex.search (t,value,exclude)
+    assert_arg_iterable(1,t)
+    local tables = {[t]=true}
+    if exclude then
+        for _,v in pairs(exclude) do tables[v] = true end
+    end
+    return _find(t,value,tables)
+end
+
+--- return an iterator to a table sorted by its keys
+-- @within Iterating
+-- @tab t the table
+-- @func f an optional comparison function (f(x,y) is true if x < y)
+-- @usage for k,v in tablex.sort(t) do print(k,v) end
+-- @return an iterator to traverse elements sorted by the keys
+function tablex.sort(t,f)
+    local keys = {}
+    for k in pairs(t) do keys[#keys + 1] = k end
+    tsort(keys,f)
+    local i = 0
+    return function()
+        i = i + 1
+        return keys[i], t[keys[i]]
+    end
+end
+
+--- return an iterator to a table sorted by its values
+-- @within Iterating
+-- @tab t the table
+-- @func f an optional comparison function (f(x,y) is true if x < y)
+-- @usage for k,v in tablex.sortv(t) do print(k,v) end
+-- @return an iterator to traverse elements sorted by the values
+function tablex.sortv(t,f)
+    f = function_arg(2, f or '<')
+    local keys = {}
+    for k in pairs(t) do keys[#keys + 1] = k end
+    tsort(keys,function(x, y) return f(t[x], t[y]) end)
+    local i = 0
+    return function()
+        i = i + 1
+        return keys[i], t[keys[i]]
+    end
+end
+
+--- modifies a table to be read only.
+-- This only offers weak protection. Tables can still be modified with
+-- `table.insert` and `rawset`.
+-- @tab t the table
+-- @return the table read only.
+function tablex.readonly(t)
+    local mt = {
+        __index=t,
+        __newindex=function(t, k, v) error("Attempt to modify read-only table", 2) end,
+        __pairs=function() return pairs(t) end,
+        __ipairs=function() return ipairs(t) end,
+        __len=function() return #t end,
+        __metatable=false
+    }
+    return setmetatable({}, mt)
+end
+
+return tablex
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/types.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/types.lua
new file mode 100644 (file)
index 0000000..f4ab236
--- /dev/null
@@ -0,0 +1,145 @@
+---- Dealing with Detailed Type Information
+
+-- Dependencies `pl.utils`
+-- @module pl.types
+
+local utils = require 'pl.utils'
+local types = {}
+
+--- is the object either a function or a callable object?.
+-- @param obj Object to check.
+function types.is_callable (obj)
+    return type(obj) == 'function' or getmetatable(obj) and getmetatable(obj).__call and true
+end
+
+--- is the object of the specified type?.
+-- If the type is a string, then use type, otherwise compare with metatable
+-- @param obj An object to check
+-- @param tp String of what type it should be
+-- @function is_type
+types.is_type = utils.is_type
+
+local fileMT = getmetatable(io.stdout)
+
+--- a string representation of a type.
+-- For tables with metatables, we assume that the metatable has a `_name`
+-- field. Knows about Lua file objects.
+-- @param obj an object
+-- @return a string like 'number', 'table' or 'List'
+function types.type (obj)
+    local t = type(obj)
+    if t == 'table' or t == 'userdata' then
+        local mt = getmetatable(obj)
+        if mt == fileMT then
+            return 'file'
+        elseif mt == nil then
+            return t
+        else
+            return mt._name or "unknown "..t
+        end
+    else
+        return t
+    end
+end
+
+--- is this number an integer?
+-- @param x a number
+-- @raise error if x is not a number
+function types.is_integer (x)
+    return math.ceil(x)==x
+end
+
+--- Check if the object is "empty".
+-- An object is considered empty if it is nil, a table with out any items (key,
+-- value pairs or indexes), or a string with no content ("").
+-- @param o The object to check if it is empty.
+-- @param ignore_spaces If the object is a string and this is true the string is
+-- considered empty is it only contains spaces.
+-- @return true if the object is empty, otherwise false.
+function types.is_empty(o, ignore_spaces)
+    if o == nil or (type(o) == "table" and not next(o)) or (type(o) == "string" and (o == "" or (ignore_spaces and o:match("^%s+$")))) then
+        return true
+    end
+    return false
+end
+
+local function check_meta (val)
+    if type(val) == 'table' then return true end
+    return getmetatable(val)
+end
+
+--- is an object 'array-like'?
+-- @param val any value.
+function types.is_indexable (val)
+    local mt = check_meta(val)
+    if mt == true then return true end
+    return mt and mt.__len and mt.__index and true
+end
+
+--- can an object be iterated over with `pairs`?
+-- @param val any value.
+function types.is_iterable (val)
+    local mt = check_meta(val)
+    if mt == true then return true end
+    return mt and mt.__pairs and true
+end
+
+--- can an object accept new key/pair values?
+-- @param val any value.
+function types.is_writeable (val)
+    local mt = check_meta(val)
+    if mt == true then return true end
+    return mt and mt.__newindex and true
+end
+
+-- Strings that should evaluate to true.
+local trues = { yes=true, y=true, ["true"]=true, t=true, ["1"]=true }
+-- Conditions types should evaluate to true.
+local true_types = {
+    boolean=function(o, true_strs, check_objs) return o end,
+    string=function(o, true_strs, check_objs)
+        if trues[o:lower()] then
+            return true
+        end
+        -- Check alternative user provided strings.
+        for _,v in ipairs(true_strs or {}) do
+            if type(v) == "string" and o == v:lower() then
+                return true
+            end
+        end
+        return false
+    end,
+    number=function(o, true_strs, check_objs) return o ~= 0 end,
+    table=function(o, true_strs, check_objs) if check_objs and next(o) ~= nil then return true end return false end
+}
+--- Convert to a boolean value.
+-- True values are:
+--
+-- * boolean: true.
+-- * string: 'yes', 'y', 'true', 't', '1' or additional strings specified by `true_strs`.
+-- * number: Any non-zero value.
+-- * table: Is not empty and `check_objs` is true.
+-- * object: Is not `nil` and `check_objs` is true.
+--
+-- @param o The object to evaluate.
+-- @param[opt] true_strs optional Additional strings that when matched should evaluate to true. Comparison is case insensitive.
+-- This should be a List of strings. E.g. "ja" to support German.
+-- @param[opt] check_objs True if objects should be evaluated. Default is to evaluate objects as true if not nil
+-- or if it is a table and it is not empty.
+-- @return true if the input evaluates to true, otherwise false.
+function types.to_bool(o, true_strs, check_objs)
+    local true_func
+    if true_strs then
+        utils.assert_arg(2, true_strs, "table")
+    end
+    true_func = true_types[type(o)]
+    if true_func then
+        return true_func(o, true_strs, check_objs)
+    elseif check_objs and o ~= nil then
+        return true
+    end
+    return false
+end
+
+
+return types
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/utils.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/pl/utils.lua
new file mode 100644 (file)
index 0000000..d3d6e11
--- /dev/null
@@ -0,0 +1,516 @@
+--- Generally useful routines.
+-- See  @{01-introduction.md.Generally_useful_functions|the Guide}.
+--
+-- Dependencies: `pl.compat`
+--
+-- @module pl.utils
+local format = string.format
+local compat = require 'pl.compat'
+local stdout = io.stdout
+local append = table.insert
+local unpack = rawget(_G,'unpack') or rawget(table,'unpack')
+
+local utils = {
+    _VERSION = "1.4.1",
+    lua51 = compat.lua51,
+    setfenv = compat.setfenv,
+    getfenv = compat.getfenv,
+    load = compat.load,
+    execute = compat.execute,
+    dir_separator = _G.package.config:sub(1,1),
+    unpack = unpack
+}
+
+--- end this program gracefully.
+-- @param code The exit code or a message to be printed
+-- @param ... extra arguments for message's format'
+-- @see utils.fprintf
+function utils.quit(code,...)
+    if type(code) == 'string' then
+        utils.fprintf(io.stderr,code,...)
+        code = -1
+    else
+        utils.fprintf(io.stderr,...)
+    end
+    io.stderr:write('\n')
+    os.exit(code)
+end
+
+--- print an arbitrary number of arguments using a format.
+-- @param fmt The format (see string.format)
+-- @param ... Extra arguments for format
+function utils.printf(fmt,...)
+    utils.assert_string(1,fmt)
+    utils.fprintf(stdout,fmt,...)
+end
+
+--- write an arbitrary number of arguments to a file using a format.
+-- @param f File handle to write to.
+-- @param fmt The format (see string.format).
+-- @param ... Extra arguments for format
+function utils.fprintf(f,fmt,...)
+    utils.assert_string(2,fmt)
+    f:write(format(fmt,...))
+end
+
+local function import_symbol(T,k,v,libname)
+    local key = rawget(T,k)
+    -- warn about collisions!
+    if key and k ~= '_M' and k ~= '_NAME' and k ~= '_PACKAGE' and k ~= '_VERSION' then
+        utils.fprintf(io.stderr,"warning: '%s.%s' will not override existing symbol\n",libname,k)
+        return
+    end
+    rawset(T,k,v)
+end
+
+local function lookup_lib(T,t)
+    for k,v in pairs(T) do
+        if v == t then return k end
+    end
+    return '?'
+end
+
+local already_imported = {}
+
+--- take a table and 'inject' it into the local namespace.
+-- @param t The Table
+-- @param T An optional destination table (defaults to callers environment)
+function utils.import(t,T)
+    T = T or _G
+    t = t or utils
+    if type(t) == 'string' then
+        t = require (t)
+    end
+    local libname = lookup_lib(T,t)
+    if already_imported[t] then return end
+    already_imported[t] = libname
+    for k,v in pairs(t) do
+        import_symbol(T,k,v,libname)
+    end
+end
+
+utils.patterns = {
+    FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*',
+    INTEGER = '[+%-%d]%d*',
+    IDEN = '[%a_][%w_]*',
+    FILE = '[%a%.\\][:%][%w%._%-\\]*'
+}
+
+--- escape any 'magic' characters in a string
+-- @param s The input string
+function utils.escape(s)
+    utils.assert_string(1,s)
+    return (s:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1'))
+end
+
+--- return either of two values, depending on a condition.
+-- @param cond A condition
+-- @param value1 Value returned if cond is true
+-- @param value2 Value returned if cond is false (can be optional)
+function utils.choose(cond,value1,value2)
+    if cond then return value1
+    else return value2
+    end
+end
+
+local raise
+
+--- return the contents of a file as a string
+-- @param filename The file path
+-- @param is_bin open in binary mode
+-- @return file contents
+function utils.readfile(filename,is_bin)
+    local mode = is_bin and 'b' or ''
+    utils.assert_string(1,filename)
+    local f,open_err = io.open(filename,'r'..mode)
+    if not f then return utils.raise (open_err) end
+    local res,read_err = f:read('*a')
+    f:close()
+    if not res then
+        -- Errors in io.open have "filename: " prefix,
+        -- error in file:read don't, add it.
+        return raise (filename..": "..read_err)
+    end
+    return res
+end
+
+--- write a string to a file
+-- @param filename The file path
+-- @param str The string
+-- @param is_bin open in binary mode
+-- @return true or nil
+-- @return error message
+-- @raise error if filename or str aren't strings
+function utils.writefile(filename,str,is_bin)
+    local mode = is_bin and 'b' or ''
+    utils.assert_string(1,filename)
+    utils.assert_string(2,str)
+    local f,err = io.open(filename,'w'..mode)
+    if not f then return raise(err) end
+    f:write(str)
+    f:close()
+    return true
+end
+
+--- return the contents of a file as a list of lines
+-- @param filename The file path
+-- @return file contents as a table
+-- @raise errror if filename is not a string
+function utils.readlines(filename)
+    utils.assert_string(1,filename)
+    local f,err = io.open(filename,'r')
+    if not f then return raise(err) end
+    local res = {}
+    for line in f:lines() do
+        append(res,line)
+    end
+    f:close()
+    return res
+end
+
+--- split a string into a list of strings separated by a delimiter.
+-- @param s The input string
+-- @param re A Lua string pattern; defaults to '%s+'
+-- @param plain don't use Lua patterns
+-- @param n optional maximum number of splits
+-- @return a list-like table
+-- @raise error if s is not a string
+function utils.split(s,re,plain,n)
+    utils.assert_string(1,s)
+    local find,sub,append = string.find, string.sub, table.insert
+    local i1,ls = 1,{}
+    if not re then re = '%s+' end
+    if re == '' then return {s} end
+    while true do
+        local i2,i3 = find(s,re,i1,plain)
+        if not i2 then
+            local last = sub(s,i1)
+            if last ~= '' then append(ls,last) end
+            if #ls == 1 and ls[1] == '' then
+                return {}
+            else
+                return ls
+            end
+        end
+        append(ls,sub(s,i1,i2-1))
+        if n and #ls == n then
+            ls[#ls] = sub(s,i1)
+            return ls
+        end
+        i1 = i3+1
+    end
+end
+
+--- split a string into a number of values.
+-- @param s the string
+-- @param re the delimiter, default space
+-- @return n values
+-- @usage first,next = splitv('jane:doe',':')
+-- @see split
+function utils.splitv (s,re)
+    return unpack(utils.split(s,re))
+end
+
+--- convert an array of values to strings.
+-- @param t a list-like table
+-- @param temp buffer to use, otherwise allocate
+-- @param tostr custom tostring function, called with (value,index).
+-- Otherwise use `tostring`
+-- @return the converted buffer
+function utils.array_tostring (t,temp,tostr)
+    temp, tostr = temp or {}, tostr or tostring
+    for i = 1,#t do
+        temp[i] = tostr(t[i],i)
+    end
+    return temp
+end
+
+local is_windows = package.config:sub(1, 1) == "\\"
+
+--- Quote an argument of a command.
+-- Quotes a single argument of a command to be passed
+-- to `os.execute`, `pl.utils.execute` or `pl.utils.executeex`.
+-- @string argument the argument.
+-- @return quoted argument.
+function utils.quote_arg(argument)
+    if is_windows then
+        if argument == "" or argument:find('[ \f\t\v]') then
+            -- Need to quote the argument.
+            -- Quotes need to be escaped with backslashes;
+            -- additionally, backslashes before a quote, escaped or not,
+            -- need to be doubled.
+            -- See documentation for CommandLineToArgvW Windows function.
+            argument = '"' .. argument:gsub([[(\*)"]], [[%1%1\"]]):gsub([[\+$]], "%0%0") .. '"'
+        end
+
+        -- os.execute() uses system() C function, which on Windows passes command
+        -- to cmd.exe. Escape its special characters.
+        return (argument:gsub('["^<>!|&%%]', "^%0"))
+    else
+        if argument == "" or argument:find('[^a-zA-Z0-9_@%+=:,./-]') then
+            -- To quote arguments on posix-like systems use single quotes.
+            -- To represent an embedded single quote close quoted string ('),
+            -- add escaped quote (\'), open quoted string again (').
+            argument = "'" .. argument:gsub("'", [['\'']]) .. "'"
+        end
+
+        return argument
+    end
+end
+
+--- execute a shell command and return the output.
+-- This function redirects the output to tempfiles and returns the content of those files.
+-- @param cmd a shell command
+-- @param bin boolean, if true, read output as binary file
+-- @return true if successful
+-- @return actual return code
+-- @return stdout output (string)
+-- @return errout output (string)
+function utils.executeex(cmd, bin)
+    local mode
+    local outfile = os.tmpname()
+    local errfile = os.tmpname()
+
+    if is_windows and not outfile:find(':') then
+        outfile = os.getenv('TEMP')..outfile
+        errfile = os.getenv('TEMP')..errfile
+    end
+    cmd = cmd .. " > " .. utils.quote_arg(outfile) .. " 2> " .. utils.quote_arg(errfile)
+
+    local success, retcode = utils.execute(cmd)
+    local outcontent = utils.readfile(outfile, bin)
+    local errcontent = utils.readfile(errfile, bin)
+    os.remove(outfile)
+    os.remove(errfile)
+    return success, retcode, (outcontent or ""), (errcontent or "")
+end
+
+--- 'memoize' a function (cache returned value for next call).
+-- This is useful if you have a function which is relatively expensive,
+-- but you don't know in advance what values will be required, so
+-- building a table upfront is wasteful/impossible.
+-- @param func a function of at least one argument
+-- @return a function with at least one argument, which is used as the key.
+function utils.memoize(func)
+    local cache = {}
+    return function(k)
+        local res = cache[k]
+        if res == nil then
+            res = func(k)
+            cache[k] = res
+        end
+        return res
+    end
+end
+
+
+utils.stdmt = {
+    List = {_name='List'}, Map = {_name='Map'},
+    Set = {_name='Set'}, MultiMap = {_name='MultiMap'}
+}
+
+local _function_factories = {}
+
+--- associate a function factory with a type.
+-- A function factory takes an object of the given type and
+-- returns a function for evaluating it
+-- @tab mt metatable
+-- @func fun a callable that returns a function
+function utils.add_function_factory (mt,fun)
+    _function_factories[mt] = fun
+end
+
+local function _string_lambda(f)
+    local raise = utils.raise
+    if f:find '^|' or f:find '_' then
+        local args,body = f:match '|([^|]*)|(.+)'
+        if f:find '_' then
+            args = '_'
+            body = f
+        else
+            if not args then return raise 'bad string lambda' end
+        end
+        local fstr = 'return function('..args..') return '..body..' end'
+        local fn,err = utils.load(fstr)
+        if not fn then return raise(err) end
+        fn = fn()
+        return fn
+    else return raise 'not a string lambda'
+    end
+end
+
+--- an anonymous function as a string. This string is either of the form
+-- '|args| expression' or is a function of one argument, '_'
+-- @param lf function as a string
+-- @return a function
+-- @usage string_lambda '|x|x+1' (2) == 3
+-- @usage string_lambda '_+1' (2) == 3
+-- @function utils.string_lambda
+utils.string_lambda = utils.memoize(_string_lambda)
+
+local ops
+
+--- process a function argument.
+-- This is used throughout Penlight and defines what is meant by a function:
+-- Something that is callable, or an operator string as defined by <code>pl.operator</code>,
+-- such as '>' or '#'. If a function factory has been registered for the type, it will
+-- be called to get the function.
+-- @param idx argument index
+-- @param f a function, operator string, or callable object
+-- @param msg optional error message
+-- @return a callable
+-- @raise if idx is not a number or if f is not callable
+function utils.function_arg (idx,f,msg)
+    utils.assert_arg(1,idx,'number')
+    local tp = type(f)
+    if tp == 'function' then return f end  -- no worries!
+    -- ok, a string can correspond to an operator (like '==')
+    if tp == 'string' then
+        if not ops then ops = require 'pl.operator'.optable end
+        local fn = ops[f]
+        if fn then return fn end
+        local fn, err = utils.string_lambda(f)
+        if not fn then error(err..': '..f) end
+        return fn
+    elseif tp == 'table' or tp == 'userdata' then
+        local mt = getmetatable(f)
+        if not mt then error('not a callable object',2) end
+        local ff = _function_factories[mt]
+        if not ff then
+            if not mt.__call then error('not a callable object',2) end
+            return f
+        else
+            return ff(f) -- we have a function factory for this type!
+        end
+    end
+    if not msg then msg = " must be callable" end
+    if idx > 0 then
+        error("argument "..idx..": "..msg,2)
+    else
+        error(msg,2)
+    end
+end
+
+--- bind the first argument of the function to a value.
+-- @param fn a function of at least two values (may be an operator string)
+-- @param p a value
+-- @return a function such that f(x) is fn(p,x)
+-- @raise same as @{function_arg}
+-- @see func.bind1
+function utils.bind1 (fn,p)
+    fn = utils.function_arg(1,fn)
+    return function(...) return fn(p,...) end
+end
+
+--- bind the second argument of the function to a value.
+-- @param fn a function of at least two values (may be an operator string)
+-- @param p a value
+-- @return a function such that f(x) is fn(x,p)
+-- @raise same as @{function_arg}
+function utils.bind2 (fn,p)
+    fn = utils.function_arg(1,fn)
+    return function(x,...) return fn(x,p,...) end
+end
+
+
+--- assert that the given argument is in fact of the correct type.
+-- @param n argument index
+-- @param val the value
+-- @param tp the type
+-- @param verify an optional verification function
+-- @param msg an optional custom message
+-- @param lev optional stack position for trace, default 2
+-- @raise if the argument n is not the correct type
+-- @usage assert_arg(1,t,'table')
+-- @usage assert_arg(n,val,'string',path.isdir,'not a directory')
+function utils.assert_arg (n,val,tp,verify,msg,lev)
+    if type(val) ~= tp then
+        error(("argument %d expected a '%s', got a '%s'"):format(n,tp,type(val)),lev or 2)
+    end
+    if verify and not verify(val) then
+        error(("argument %d: '%s' %s"):format(n,val,msg),lev or 2)
+    end
+end
+
+--- assert the common case that the argument is a string.
+-- @param n argument index
+-- @param val a value that must be a string
+-- @raise val must be a string
+function utils.assert_string (n,val)
+    utils.assert_arg(n,val,'string',nil,nil,3)
+end
+
+local err_mode = 'default'
+
+--- control the error strategy used by Penlight.
+-- Controls how <code>utils.raise</code> works; the default is for it
+-- to return nil and the error string, but if the mode is 'error' then
+-- it will throw an error. If mode is 'quit' it will immediately terminate
+-- the program.
+-- @param mode - either 'default', 'quit'  or 'error'
+-- @see utils.raise
+function utils.on_error (mode)
+    if ({['default'] = 1, ['quit'] = 2, ['error'] = 3})[mode] then
+      err_mode = mode
+    else
+      -- fail loudly
+      if err_mode == 'default' then err_mode = 'error' end
+      utils.raise("Bad argument expected string; 'default', 'quit', or 'error'. Got '"..tostring(mode).."'")
+    end
+end
+
+--- used by Penlight functions to return errors.  Its global behaviour is controlled
+-- by <code>utils.on_error</code>
+-- @param err the error string.
+-- @see utils.on_error
+function utils.raise (err)
+    if err_mode == 'default' then return nil,err
+    elseif err_mode == 'quit' then utils.quit(err)
+    else error(err,2)
+    end
+end
+
+--- is the object of the specified type?.
+-- If the type is a string, then use type, otherwise compare with metatable
+-- @param obj An object to check
+-- @param tp String of what type it should be
+function utils.is_type (obj,tp)
+    if type(tp) == 'string' then return type(obj) == tp end
+    local mt = getmetatable(obj)
+    return tp == mt
+end
+
+raise = utils.raise
+
+--- load a code string or bytecode chunk.
+-- @param code Lua code as a string or bytecode
+-- @param name for source errors
+-- @param mode kind of chunk, 't' for text, 'b' for bytecode, 'bt' for all (default)
+-- @param env  the environment for the new chunk (default nil)
+-- @return compiled chunk
+-- @return error message (chunk is nil)
+-- @function utils.load
+
+---------------
+-- Get environment of a function.
+-- With Lua 5.2, may return nil for a function with no global references!
+-- Based on code by [Sergey Rozhenko](http://lua-users.org/lists/lua-l/2010-06/msg00313.html)
+-- @param f a function or a call stack reference
+-- @function utils.getfenv
+
+---------------
+-- Set environment of a function
+-- @param f a function or a call stack reference
+-- @param env a table that becomes the new environment of `f`
+-- @function utils.setfenv
+
+--- execute a shell command.
+-- This is a compatibility function that returns the same for Lua 5.1 and Lua 5.2
+-- @param cmd a shell command
+-- @return true if successful
+-- @return actual return code
+-- @function utils.execute
+
+return utils
+
+
index 745c290..2150e70 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
        
        default_type text/html;
        
-       # the flag identify whether to check doing internal redirect or not
-       set $websocket_internal_redirect "on";
        set $http_protocol "http";
-
        
        location = /iui/microservices {
                try_files $uri @addslash;
                set $svc_url "";
                set $backend "defaultbackend";
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/customrewrite.lua;
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       msb.filter_websocket_req()
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                
                proxy_pass $http_protocol://$backend;
-               proxy_redirect $http_protocol://$host:$server_port$svc_url $http_protocol://$host:$server_port$svc_name;  
+               #proxy_redirect $http_protocol://$host:$server_port$svc_url $http_protocol://$host:$server_port$svc_name;  
        }
        
-       location @customwebsocket {
-               set $websocket_internal_redirect "off";
+       location @websocket {
+               set $stats_new_req "false";
                #set header for websocket
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header Host $host:$server_port;
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/customrewrite.lua;
-               access_by_lua_block {
-                       msb.access()
-               }
-               header_filter_by_lua_block {
-                       msb.header_filter()
-               }
-               #log by the lua file
-               log_by_lua_file luaext/log/logger.lua;
-               
-               proxy_pass $http_protocol://$backend;  
-       }
-       
-       location @commonwebsocket {
-               set $websocket_internal_redirect "off";
-               #set header for websocket
-               proxy_http_version 1.1;
-               proxy_set_header Upgrade $http_upgrade;
-               proxy_set_header Connection "Upgrade";
-               
-               proxy_set_header X-Real-IP $remote_addr;
-               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-               proxy_set_header Host $host:$server_port;
-               
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/commonrewrite.lua;
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                set $svc_url "";
                set $backend "defaultbackend";
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/customrewrite.lua;
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       stats.accept_new_request()
+                       msb.filter_websocket_req()
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                
                proxy_pass $http_protocol://$backend;
-               proxy_redirect $http_protocol://$host:$server_port$svc_url $http_protocol://$host:$server_port$svc_name;  
+               #proxy_redirect $http_protocol://$host:$server_port$svc_url $http_protocol://$host:$server_port$svc_name;  
        }
 
        location ~ ^/(api|admin|apijson)(/[Vv]\d+(?:\.\d+)*)?/([^/]+)(/[Vv]\d+(?:\.\d+)*)?(.*) {
                set $req_res $5;
                set $backend "defaultbackend";
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/commonrewrite.lua;
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       stats.accept_new_request()
+                       msb.filter_websocket_req()
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                set $req_res $2.$3;
                set $backend "defaultbackend";
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/commonrewrite.lua;
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       stats.accept_new_request()
+                       msb.filter_websocket_req()
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                set $req_res $2;
                set $backend "defaultbackend";
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/commonrewrite.lua;
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       stats.accept_new_request()
+                       msb.filter_websocket_req()
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                set $svc_url "";
                set $backend "defaultbackend";
                
-               #rewrite by the lua file
-               rewrite_by_lua_file luaext/rewrite/customrewrite.lua; 
                access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       stats.accept_new_request()
+                       msb.filter_websocket_req()
+                       msb.route()
                        msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
                }
                header_filter_by_lua_block {
+                       stats.receive_response()
                        msb.header_filter()
+                       stats.return_response()
                }
                #log by the lua file
                log_by_lua_file luaext/log/logger.lua;
                proxy_cache nginx_cache;
                add_header X-Cache-Status $upstream_cache_status;
                proxy_pass $http_protocol://$backend;
-               proxy_redirect http://$host:$server_port$svc_url http://$host:$server_port$svc_name;  
+               #proxy_redirect http://$host:$server_port$svc_url http://$host:$server_port$svc_name;  
        }
        
        location = /favicon.ico {
                log_not_found off;
-       }
\ No newline at end of file
+       }
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/msb-enabled/location-ext/monitor.conf b/openresty-ext/src/assembly/resources/openresty/nginx/msb-enabled/location-ext/monitor.conf
new file mode 100644 (file)
index 0000000..c1ae2d2
--- /dev/null
@@ -0,0 +1,56 @@
+#
+# Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)
+#
+# Licensed 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.
+#
+location = /admin/microservices/v1/status/connection {
+    access_log off;
+    content_by_lua_block{
+        if ngx.req.get_method() == "GET" then
+               ngx.print(stats.format_conn_status())
+        else
+               ngx.status = ngx.HTTP_NOT_FOUND
+            ngx.say("request method not supported!")
+            return ngx.exit(ngx.status)
+        end
+    }
+}
+location = /admin/microservices/v1/status/request {
+    access_log off;
+    content_by_lua_block{
+        if ngx.req.get_method() == "GET" then
+               ngx.print(stats.format_req_status())
+        else
+               ngx.status = ngx.HTTP_NOT_FOUND
+            ngx.say("request method not supported!")
+            return ngx.exit(ngx.status)
+        end
+    }
+}
+location = /admin/microservices/v1/statistics/request {
+    access_log off;
+    content_by_lua_block{
+        if ngx.req.get_method() == "GET" then
+                       --if input latestNum is empty or illegal, then set to 1
+                       local latest_num = tonumber(ngx.var.arg_latestNum) or 1
+                       if latest_num<=0 then
+                               latest_num =1
+                       end
+               ngx.print(stats.get_reqnum_stats(latest_num))
+        else
+               ngx.status = ngx.HTTP_NOT_FOUND
+            ngx.say("request method not supported!")
+            return ngx.exit(ngx.status)
+        end
+    }
+}
index 8e854bb..3e7038b 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -41,13 +41,15 @@ proxy_next_upstream error timeout;
 proxy_next_upstream_tries 5;
 
 # Lua settings
-lua_package_path "$prefix/../lualib/?.lua;$prefix/luaext/?.lua;;";
+lua_package_path "$prefix/../lualib/?.lua;$prefix/luaext/?.lua;$prefix/luaext/vendor/?.lua;;";
 lua_package_cpath "$prefix/../lualib/?.so;;";
 #lua_shared_dict rr_cache 1M;
 #lua_shared_dict rr_locks 100k;
 
 lua_shared_dict svc_cache 5M;
 lua_shared_dict locks 200k;
+lua_shared_dict stats 1M;
+lua_shared_dict dns_cache 1M;
 
 lua_code_cache on;
 
@@ -60,8 +62,12 @@ upstream defaultbackend {
 init_by_lua_block {
        msb = require('msb')
        msb.load_plugins()
+       stats = require ('monitor.stats')
+}
+init_worker_by_lua_block {
+       stats.init_timer()
 }
 server {
        listen 80;
        include ../msb-enabled/location-default/msblocations.conf;
-}
\ No newline at end of file
+}
index 430c05d..9015d65 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ server {
        listen 443 ssl;
        ssl_certificate ../ssl/cert/cert.crt;
        ssl_certificate_key ../ssl/cert/cert.key;
+       ssl_protocols TLSv1.1 TLSv1.2;
        ssl_dhparam ../ssl/dh-pubkey/dhparams.pem;
        include ../msb-enabled/location-default/msblocations.conf;
-}
\ No newline at end of file
+}
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/sites-enabled/cosSample b/openresty-ext/src/assembly/resources/openresty/nginx/sites-enabled/cosSample
new file mode 100644 (file)
index 0000000..b5891f8
--- /dev/null
@@ -0,0 +1,88 @@
+#
+# Copyright (C) 2018 ZTE, Inc. and others. All rights reserved. (ZTE)
+#
+# Licensed 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.
+#
+server {
+       listen 8000;
+       default_type text/html;
+       
+       set $http_protocol "http";
+       
+       location / {
+               set $svc_type "";
+               set $svc_name "";
+               set $svc_version1 "";
+               set $svc_version2 "";
+               set $req_res "";
+               set $svc_url "";
+               set $backend "defaultbackend";
+               access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       stats.accept_new_request()
+                       msb.filter_websocket_req()
+                       -- 80 is the publish port in the msb, COS is the system_tag. These two parameters are required
+                       msb.external_route("80","COS")
+                       msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
+               }
+               header_filter_by_lua_block {
+                       stats.receive_response()
+                       msb.header_filter()
+                       stats.return_response()
+               }
+               #log by the lua file
+               log_by_lua_file luaext/log/logger.lua;
+               
+               proxy_pass $http_protocol://$backend;
+               
+       }
+       
+       location @websocket {
+               #set header for websocket
+               proxy_http_version 1.1;
+               proxy_set_header Upgrade $http_upgrade;
+               proxy_set_header Connection "Upgrade";
+               
+               proxy_set_header X-Real-IP $remote_addr;
+               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+               proxy_set_header Host $host:$server_port;
+               access_by_lua_block {
+                       ngx.log(ngx.INFO, ngx.var.request_id..":receive the request")
+                       msb.external_route("80","COS")
+                       msb.access()
+                       if ngx.ctx.use_ownupstream then
+                               stats.forward_backend()
+                       end
+               }
+               header_filter_by_lua_block {
+                       stats.receive_response()
+                       msb.header_filter()
+                       stats.return_response()
+               }
+               #log by the lua file
+               log_by_lua_file luaext/log/logger.lua;
+               
+               proxy_pass $http_protocol://$backend;
+       }
+       
+       location @commonnotfound {
+               return 502;
+       }
+       
+       location = /favicon.ico {
+               log_not_found off;
+       }
+}
index 240b4b0..601df14 100644 (file)
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -20,7 +20,8 @@ HOME=`cd $DIRNAME/nginx; pwd`
 _NGINXCMD="$HOME/sbin/nginx"
 
 cd $HOME; pwd
-echo =========== begin to reload ===============
+SYSTEM_TIME=`date '+%Y-%m-%d %T'`
+echo ${SYSTEM_TIME}  ========= begin to reload ===============
 echo @WORK_DIR@ $HOME
 echo @C_CMD@ $_NGINXCMD -p $HOME/ -s reload
-$_NGINXCMD -p $HOME/ -s reload
\ No newline at end of file
+$_NGINXCMD -p $HOME/ -s reload
index 4217ca3..5a4d917 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ DIRNAME=`dirname $0`
 HOME=`cd $DIRNAME/; pwd`
 _REDISCMD="$HOME/redis-server"
 _REDISCONF="$HOME/conf/redis.conf"
-_BGREWRITEAOF="$HOME/BGREWRITEAOF.sh"
+
 REDIS_WORKS=$HOME/../redis-works
 
 if [ ! -d "$REDIS_WORKS" ]; then 
@@ -29,12 +29,6 @@ mkdir "$REDIS_WORKS"
 fi 
 
 
-if [ -n "${APIGATEWAY_MODE}" -a -n "${APIGATEWAY_REDIS_PORT}" ]; then
-        sed -i 's/port 6379/port '${APIGATEWAY_REDIS_PORT}'/g' $_REDISCONF
-        sed -i 's/redis_6379/redis_'${APIGATEWAY_REDIS_PORT}'/g' $_REDISCONF
-        sed -i 's/-p 6379/-p '${APIGATEWAY_REDIS_PORT}'/g' $_BGREWRITEAOF
-fi
-
 echo =========== Redis config info  =============================================
 echo Redis_HOME=$HOME
 echo config file=$_REDISCONF
@@ -43,4 +37,4 @@ echo ===========================================================================
 cd $HOME; pwd
 
 echo @C_CMD@ $_REDISCMD $_REDISCONF
-$_REDISCMD $_REDISCONF
\ No newline at end of file
+$_REDISCMD $_REDISCONF
index d266b42..8c4ab93 100644 (file)
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2016 ZTE, Inc. and others. All rights reserved. (ZTE)
+# Copyright (C) 2017-2018 ZTE, Inc. and others. All rights reserved. (ZTE)
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -22,12 +22,9 @@ _REDISCLIENT="$HOME/redis-cli"
 
 echo ===================== Redis info  =============================================
 echo Redis_HOME=$HOME
-echo TIP:This shell script close the Redis instance!
+echo TIP:This shell script close the Redis instance listening on 6379!
 echo ===============================================================================
 cd $HOME; pwd
-_REDIS_PORT="6379"
-if [ -n "${APIGATEWAY_MODE}" -a -n "${APIGATEWAY_REDIS_PORT}" ]; then
-         _REDIS_PORT=${APIGATEWAY_REDIS_PORT}
-fi
-echo @C_CMD@ $_REDISCLIENT  -p $_REDIS_PORT shutdown
-$_REDISCLIENT  -p $_REDIS_PORT shutdown
\ No newline at end of file
+
+echo @C_CMD@ $_REDISCLIENT  -p 6379 shutdown
+$_REDISCLIENT  -p 6379 shutdown