diff --git a/apisix/core/config_etcd.lua b/apisix/core/config_etcd.lua index 4cca5a44cde9..f3a617394578 100644 --- a/apisix/core/config_etcd.lua +++ b/apisix/core/config_etcd.lua @@ -499,7 +499,7 @@ local function load_full_data(self, dir_res, headers) item.clean_handlers = {} if self.filter then - self.filter(item) + self.filter(item, 1, self) end end @@ -551,7 +551,7 @@ local function load_full_data(self, dir_res, headers) item.clean_handlers = {} if self.filter then - self.filter(item) + self.filter(item, #values, self) end end @@ -695,9 +695,10 @@ local function sync_data(self) return false end + local pre_val local pre_index = self.values_hash[key] if pre_index then - local pre_val = self.values[pre_index] + pre_val = self.values[pre_index] if pre_val then config_util.fire_all_clean_handlers(pre_val) end @@ -755,7 +756,7 @@ local function sync_data(self) -- /plugins' filter need to known self.values when it is called -- so the filter should be called after self.values set. if self.filter then - self.filter(res) + self.filter(res, pre_val, self) end self.conf_version = self.conf_version + 1 diff --git a/apisix/http/route.lua b/apisix/http/route.lua index d475646b56c6..a489b2d1a794 100644 --- a/apisix/http/route.lua +++ b/apisix/http/route.lua @@ -73,6 +73,7 @@ function _M.create_radixtree_uri_router(routes, uri_routes, with_parameter) core.log.info("insert uri route: ", core.json.delay_encode(route.value, true)) core.table.insert(uri_routes, { + id = route.value.id, paths = route.value.uris or route.value.uri, methods = route.value.methods, priority = route.value.priority, @@ -92,7 +93,11 @@ function _M.create_radixtree_uri_router(routes, uri_routes, with_parameter) end end - event.push(event.CONST.BUILD_ROUTER, routes) + local router_module = require("apisix.router") + if router_module.router_http then + event.push(event.CONST.BUILD_ROUTER, routes) + end + core.log.info("route items: ", core.json.delay_encode(uri_routes, true)) if with_parameter then diff --git a/apisix/http/router/radixtree_host_uri.lua b/apisix/http/router/radixtree_host_uri.lua index 532576e53d4a..61eadd9871ec 100644 --- a/apisix/http/router/radixtree_host_uri.lua +++ b/apisix/http/router/radixtree_host_uri.lua @@ -20,6 +20,10 @@ local core = require("apisix.core") local event = require("apisix.core.event") local get_services = require("apisix.http.service").services local service_fetch = require("apisix.http.service").get +local ar = require("apisix.router") +local table = require("apisix.core.table") +local json = require("apisix.core.json") +local rdx = require("resty.radixtree") local ipairs = ipairs local type = type local tab_insert = table.insert @@ -35,7 +39,7 @@ local _M = {version = 0.1} local function push_host_router(route, host_routes, only_uri_routes) - if type(route) ~= "table" then + if route == nil or (type(route) ~= "table" or route.value == nil) then return end @@ -70,6 +74,7 @@ local function push_host_router(route, host_routes, only_uri_routes) end local radixtree_route = { + id = route.value.id, paths = route.value.uris or route.value.uri, methods = route.value.methods, priority = route.value.priority, @@ -90,14 +95,15 @@ local function push_host_router(route, host_routes, only_uri_routes) return end - for i, host in ipairs(hosts) do - local host_rev = host:reverse() - if not host_routes[host_rev] then - host_routes[host_rev] = {radixtree_route} - else - tab_insert(host_routes[host_rev], radixtree_route) - end + local rev = {} + for i, h in ipairs(hosts) do + tab_insert(rev, h:reverse()) end + + core.table.insert(host_routes, { + rev = rev, + route = radixtree_route + }) end @@ -115,13 +121,13 @@ local function create_radixtree_router(routes) end end - -- create router: host_router local host_router_routes = {} - for host_rev, routes in pairs(host_routes) do - local sub_router = router.new(routes) + for i, hr in ipairs(host_routes) do + local sub_router = router.new(hr["route"]) core.table.insert(host_router_routes, { - paths = host_rev, + id = hr["route"]["id"], + paths = hr["rev"], filter_fun = function(vars, opts, ...) return sub_router:dispatch(vars.uri, opts, ...) end, @@ -143,6 +149,87 @@ local function create_radixtree_router(routes) end +local function incremental_operate_radixtree(routes) + if ar.need_create_radixtree then + core.log.notice("create object of radixtree host uri after load_full_data or init.", #routes) + create_radixtree_router(routes) + ar.need_create_radixtree = false + table.clear(ar.sync_tb) + return + end + + local op, route, last_route + local router_opts = { + no_param_match = true + } + + event.push(event.CONST.BUILD_ROUTER, routes) + for k, _ in pairs(ar.sync_tb) do + op = ar.sync_tb[k]["op"] + route = ar.sync_tb[k]["outer_routeoute"] + last_route = ar.sync_tb[k]["last_route"] + local host_routes = {} + local only_uri_routes = {} + local last_host_routes = {} + local last_only_uri_routes = {} + local outer_route, last_outer_route = {paths={}}, {paths={}} + local inner_route, last_inner_route = {}, {} + local sub_router + + push_host_router(route, host_routes, only_uri_routes) + push_host_router(last_route, last_host_routes, last_only_uri_routes) + + if #host_routes > 0 or #last_host_routes > 0 then + if route then + if #host_routes > 0 then + inner_route = host_routes[1]["route"] + end + + if #last_host_routes > 0 then + last_inner_route = last_host_routes[1]["route"] + last_outer_route = { + id = last_route.value.id, + paths = last_host_routes[1]["rev"], + } + end + + sub_router = router.new(inner_route) + + outer_route = { + id = route.value.id, + paths = host_routes[1]["rev"], + filter_fun = function(vars, opts, ...) + return sub_router:dispatch(vars.uri, opts, ...) + end, + handler = function (api_ctx, match_opts) + api_ctx.real_curr_req_matched_host = match_opts.matched._path + end + } + end + + core.log.notice("update routes watched from etcd into radixtree.", json.encode(route)) + local err = host_router:update_route(last_outer_route, outer_route, router_opts) + if err ~= nil then + core.log.error("update a route into radixtree failed.", json.encode(route), err) + return + end + else + local cur_tmp = only_uri_routes[1] or {paths = {}} + local last_tmp = last_only_uri_routes[1] or {paths = {}} + + core.log.notice("update routes watched from etcd into radixtree.", json.encode(route)) + local err = only_uri_router:update_route(last_tmp, cur_tmp, router_opts) + if err ~= nil then + core.log.error("update a route into radixtree failed.", json.encode(route), err) + return + end + end + + ar.sync_tb[k] = nil + end +end + + local match_opts = {} function _M.match(api_ctx) local user_routes = _M.user_routes @@ -150,7 +237,8 @@ function _M.match(api_ctx) if not cached_router_version or cached_router_version ~= user_routes.conf_version or not cached_service_version or cached_service_version ~= service_version then - create_radixtree_router(user_routes.values) + --create_radixtree_router(user_routes.values) + incremental_operate_radixtree(user_routes.values) cached_router_version = user_routes.conf_version cached_service_version = service_version end diff --git a/apisix/http/router/radixtree_uri.lua b/apisix/http/router/radixtree_uri.lua index 6e546364ac14..9a602f201f99 100644 --- a/apisix/http/router/radixtree_uri.lua +++ b/apisix/http/router/radixtree_uri.lua @@ -18,24 +18,128 @@ local require = require local core = require("apisix.core") local base_router = require("apisix.http.route") local get_services = require("apisix.http.service").services +local ar = require("apisix.router") +local json = require("apisix.core.json") +local table = require("apisix.core.table") +local event = require("apisix.core.event") local cached_router_version local cached_service_version +local uri_routes = {} +local uri_router +local match_opts = {} local _M = {version = 0.2} - local uri_routes = {} - local uri_router - local match_opts = {} +function _M.incremental_operate_radixtree(routes, no_param) + local sync_tb = ar.sync_tb + if ar.need_create_radixtree then + uri_router = base_router.create_radixtree_uri_router(routes, uri_routes, false) + ar.need_create_radixtree = false + table.clear(ar.sync_tb) + return + end + + local op, route, last_route, err + local cur_tmp, last_tmp + local router_opts = { + no_param_match = no_param + } + + event.push(event.CONST.BUILD_ROUTER, routes) + for k, v in pairs(ar.sync_tb) do + op = ar.sync_tb[k]["op"] + route = ar.sync_tb[k]["cur_route"] + last_route = ar.sync_tb[k]["last_route"] + cur_tmp = {} + last_tmp = {} + + if route and route.value then + local status = table.try_read_attr(route, "value", "status") + if status and status == 0 then + goto CONTINUE + end + + local filter_fun, err + if route.value.filter_func then + filter_fun, err = loadstring( + "return " .. route.value.filter_func, + "router#" .. route.value.id + ) + if not filter_fun then + core.log.error("failed to load filter function: ", err, " route id", route.value.id) + return + end + + filter_fun = filter_fun() + end + + cur_tmp = { + id = route.value.id, + paths = route.value.uris or route.value.uri, + methods = route.value.methods, + priority = route.value.priority, + hosts = route.value.hosts or route.value.host, + remote_addrs = route.value.remote_addrs or route.value.remote_addr, + vars = route.value.vars, + filter_fun = filter_fun, + handler = function(api_ctx, match_opts) + api_ctx.matched_params = nil + api_ctx.matched_route = route + api_ctx.curr_req_matched = match_opts.matched + end + } + end + + if last_route and last_route.value then + last_tmp = { + id = last_route.value.id, + paths = last_route.value.uris or last_route.value.uri, + methods = last_route.value.methods, + priority = last_route.value.priority, + hosts = last_route.value.hosts or last_route.value.host, + remote_addrs = last_route.value.remote_addrs or last_route.value.remote_addr, + vars = last_route.value.vars + } + end + + if op == "update" then + core.log.notice("update routes watched from etcd into radixtree.", json.encode(route)) + err = uri_router:update_route(last_tmp, cur_tmp, router_opts) + if err ~= nil then + core.log.error("update a route into radixtree failed.", json.encode(route), err) + return + end + elseif op == "create" then + core.log.notice("create routes watched from etcd into radixtree.", json.encode(route)) + err = uri_router:add_route(cur_tmp, router_opts) + if err ~= nil then + core.log.error("add routes into radixtree failed.", json.encode(route), err) + return + end + elseif op == "delete" then + core.log.notice("delete routes watched from etcd into radixtree.", json.encode(last_route)) + err = uri_router:delete_route(last_tmp, router_opts) + if err ~= nil then + core.log.error("delete a route into radixtree failed.", json.encode(last_route), err) + return + end + end + + ar.sync_tb[k] = nil + ::CONTINUE:: + end +end + + function _M.match(api_ctx) local user_routes = _M.user_routes local _, service_version = get_services() if not cached_router_version or cached_router_version ~= user_routes.conf_version or not cached_service_version or cached_service_version ~= service_version then - uri_router = base_router.create_radixtree_uri_router(user_routes.values, - uri_routes, false) + _M.incremental_operate_radixtree(user_routes.values,true) cached_router_version = user_routes.conf_version cached_service_version = service_version end diff --git a/apisix/http/router/radixtree_uri_with_parameter.lua b/apisix/http/router/radixtree_uri_with_parameter.lua index 4bf7f3ebee5f..918b47865756 100644 --- a/apisix/http/router/radixtree_uri_with_parameter.lua +++ b/apisix/http/router/radixtree_uri_with_parameter.lua @@ -18,24 +18,30 @@ local require = require local core = require("apisix.core") local base_router = require("apisix.http.route") local get_services = require("apisix.http.service").services +local apisix_router = require("apisix.router") +local json = require("apisix.core.json") +local table = require("apisix.core.table") +local event = require("apisix.core.event") +local radixtree_uri = requre("apisix.http.router.radixtree_uri") local cached_router_version local cached_service_version +local uri_routes = {} +local uri_router +local match_opts = {} local _M = {} - local uri_routes = {} - local uri_router - local match_opts = {} + + function _M.match(api_ctx) local user_routes = _M.user_routes local _, service_version = get_services() if not cached_router_version or cached_router_version ~= user_routes.conf_version or not cached_service_version or cached_service_version ~= service_version then - uri_router = base_router.create_radixtree_uri_router(user_routes.values, - uri_routes, true) + radixtree_uri.incremental_operate_radixtree(user_routes.values) cached_router_version = user_routes.conf_version cached_service_version = service_version end diff --git a/apisix/router.lua b/apisix/router.lua index 2fd14917c299..a04c95db9436 100644 --- a/apisix/router.lua +++ b/apisix/router.lua @@ -17,35 +17,101 @@ local require = require local http_route = require("apisix.http.route") local apisix_upstream = require("apisix.upstream") -local core = require("apisix.core") +local core = require("apisix.core") +local json = require("apisix.core.json") local plugin_checker = require("apisix.plugin").plugin_checker local str_lower = string.lower -local error = error -local ipairs = ipairs +local error = error +local ipairs = ipairs +local sub_str = string.sub local _M = {version = 0.3} +_M.need_create_radixtree = true -local function filter(route) + +local function short_key(self, str) + return sub_str(str, #self.key + 2) +end + + +local function filter(route, pre_route_or_size, obj) route.orig_modifiedIndex = route.modifiedIndex route.update_count = 0 route.has_domain = false - if not route.value then - return + if route.value then + if route.value.host then + route.value.host = str_lower(route.value.host) + elseif route.value.hosts then + for i, v in ipairs(route.value.hosts) do + route.value.hosts[i] = str_lower(v) + end + end + + apisix_upstream.filter_upstream(route.value.upstream, route) end - if route.value.host then - route.value.host = str_lower(route.value.host) - elseif route.value.hosts then - for i, v in ipairs(route.value.hosts) do - route.value.hosts[i] = str_lower(v) + core.log.info("filter route: ", core.json.delay_encode(route, true)) + + if not obj then + return + end + --save sync route and operation type into a map + if type(pre_route_or_size) == "number" then + if pre_route_or_size == #obj.values then + _M.need_create_radixtree = true end + return end - apisix_upstream.filter_upstream(route.value.upstream, route) + local key + if obj.single_item then + key = obj.key + else + key = short_key(obj, route.key) + end + + local sync_tb = _M.sync_tb + if pre_route_or_size then + if route.value then + --update route + core.log.notice("update routes watched from etcd into radixtree.", json.encode(route)) + if not sync_tb[route.value.id] then + sync_tb[route.value.id] = {op = "update", last_route = pre_route_or_size, cur_route = route} + elseif sync_tb[route.value.id]["op"] == "update" then + sync_tb[route.value.id] = {op = "update", last_route = sync_tb[route.value.id]["last_route"], + cur_route = route} + elseif sync_tb[route.value.id]["op"] == "create" then + sync_tb[route.value.id] = {op = "create", cur_route = route} + end + else + --delete route + core.log.notice("delete routes watched from etcd into radixtree.", json.encode(route)) + if not sync_tb[key] then + sync_tb[key] = {op = "delete", last_route = pre_route_or_size} + elseif sync_tb[key]["op"] == "create" then + sync_tb[key] = nil + elseif sync_tb[key]["op"] == "update" then + sync_tb[key] = {op = "delete", last_route = sync_tb[key]["last_route"]} + end + end + elseif route.value then + --create route + core.log.notice("create routes watched from etcd into radixtree.", json.encode(route)) + if not sync_tb[route.value.id] then + sync_tb[route.value.id] = {op = "create", cur_route = route} + elseif sync_tb[route.value.id]["op"] == "delete" then + sync_tb[route.value.id] = {op = "update", cur_route = route, + last_route = sync_tb[route.value.id]["last_route"]} + end + else + core.log.error("invalid operation type for a route.", route.key) + return + end + _M.sync_tb = sync_tb core.log.info("filter route: ", core.json.delay_encode(route, true)) end @@ -72,6 +138,7 @@ end function _M.http_init_worker() + _M.sync_tb = {} local conf = core.config.local_conf() local router_http_name = "radixtree_uri" local router_ssl_name = "radixtree_sni" diff --git a/rockspec/apisix-master-0.rockspec b/rockspec/apisix-master-0.rockspec index ab3bee75c877..a383cbb4504b 100644 --- a/rockspec/apisix-master-0.rockspec +++ b/rockspec/apisix-master-0.rockspec @@ -45,7 +45,7 @@ dependencies = { "lua-resty-cookie = 0.1.0", "lua-resty-session = 3.10", "opentracing-openresty = 0.1", - "lua-resty-radixtree = 2.8.2", + "lua-resty-radixtree = 2.8.3", "lua-protobuf = 0.4.1", "lua-resty-openidc = 1.7.5", "luafilesystem = 1.7.0-2",