Skip to content

Instantly share code, notes, and snippets.

@lagagain
Created October 5, 2025 06:04
Show Gist options
  • Save lagagain/762e2560787cf44f6a54ffe8c47aa5da to your computer and use it in GitHub Desktop.
Save lagagain/762e2560787cf44f6a54ffe8c47aa5da to your computer and use it in GitHub Desktop.
【示範】擴充APISIX服務發現機制
local require = require
local local_conf = require('apisix.core.config_local').local_conf()
local http = require('resty.http')
local core = require('apisix.core')
local log = core.log
local ngx_timer_at = ngx.timer.at
local ngx_timer_every = ngx.timer.every
local debug = require("debug")
local default_weight
local services_dict
local _M = {
version = 0.1,
}
local function get_base_url()
local host = local_conf.discovery.customer01.host
local prefix = "/"
local url
if local_conf.discovery.customer01.prefix then
url = host .. local_conf.discovery.customer01.prefix
end
return url
end
local function request_get(url)
local httpc = http.new()
local timeout = local_conf.discovery.customer01.timeout
local connect_timeout = timeout and timeout.connect or 2000
local send_timeout = timeout and timeout.send or 2000
local read_timeout = timeout and timeout.read or 5000
log.info("connect_timeout:", connect_timeout, ", send_timeout:", send_timeout,
", read_timeout:", read_timeout, ".")
httpc:set_timeouts(connect_timeout, send_timeout, read_timeout)
return httpc:request_uri(url, {
version = 1.1,
method = 'GET',
})
end
local function get_service_nodes(service_name)
local url = get_base_url()
url = url .. ngx.escape_uri(service_name .. '.json')
local res, err = request_get(url)
if err then
log.error(string.format('get servcie nodes failed, %q', err))
return
end
if not res.body or res.status ~= 200 then
log.error(string.format('url = %s', url))
log.error(string.format('response is empty, status %d', res.status))
-- log.error(string.format('response is empty, status: %s', tostring(res.status)))
return
end
local json_str = res.body
local data, err = core.json.decode(json_str)
if err then
log.error('parse data failed, %q', err)
-- log.error('parse data failed, status = ')
return
end
local targets = {}
services_dict[service_name] = targets
for _i, node in ipairs(data) do
if node.IsEnabled then
table.insert(targets, {
host = node.Host,
port = node.Port,
weight = node.Weight or default_weight,
-- metadata = {
-- ["management.port"] = node.Port
-- }
})
end
log.info(string.format('[%s]%s({%d}): %s : %s -> %s', node.IsEnabled, service_name, _i, node.Name, node.Description, node.Endpoint))
end
return targets
end
local function fetch_full_registry(premature)
if premature then
return
end
local up_app = {}
for service_name in pairs(services_dict) do
up_app[service_name] = get_service_nodes(service_name)
end
services_dict = up_app
return up_app
end
function _M.nodes(service_name)
-- local mock = {
-- {
-- host = "192.168.56.3",
-- port = 8081,
-- weight = 100,
-- metadata = {
-- ["management.port"] = "8081"
-- }
-- }
-- }
-- return mock
if services_dict[servicd_name] then
return services_dict[service_name]
else
return get_service_nodes(service_name)
end
end
function _M.init_worker()
default_weight = local_conf.discovery.customer01.weight
services_dict = {}
log.info('default_weight:', default_weight)
local fetch_interval = local_conf.discovery.customer01.fetch_interval
log.info('fetch_interval:', fetch_interval)
ngx_timer_at(0, fetch_full_registry)
ngx_timer_every(fetch_interval, fetch_full_registry)
end
function _M.dump_data()
return {services = services_dict or {}}
end
return _M
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local host_pattern = [[^http(s)?:\/\/([a-zA-Z0-9-_.]+:.+\@)?[a-zA-Z0-9-_.:]+$]]
local prefix_pattern = [[^[\/a-zA-Z0-9-_.]+$]]
return {
type = 'object',
properties = {
host = {
type = 'string',
pattern = host_pattern,
maxLength = 500,
},
prefix = {
type = 'string',
pattern = prefix_pattern,
maxLength = 100,
default = '/'
},
fetch_interval = {type = 'integer', minimum = 1, default = 30},
weight = {type = 'integer', minimum = 1, default = 100},
timeout = {
type = 'object',
properties = {
connect = {type = 'integer', minimum = 1, default = 2000},
send = {type = 'integer', minimum = 1, default = 2000},
read = {type = 'integer', minimum = 1, default = 5000},
},
default = {
connect = 2000,
send = 2000,
read = 5000,
}
}
},
required = {'host'}
}
[
{
"Id": "02198043-1888-41e4-b055-493fae277f4a",
"Name": "新竹巨城",
"Description": "新竹巨城開始營業",
"Host": "192.168.56.3",
"Port": 8081,
"IsEnabled": true,
"Weight": 10,
"CreatedOn": "2025-01-16T11:02:55.943+08:00",
"CreatedBy": "System",
"ModifiedOn": "2025-08-12T14:54:42.52+08:00",
"ModifiedBy": "System",
"LastServiceHistoryId": "02198043-1888-41e4-b055-493fae277f4a"
},
{
"Id": "5ef3f698-51dd-4a4e-b25c-bd81e2f96e24",
"Name": "新竹SOGO",
"Description": "新竹SOGO開始營業",
"Host": "192.168.56.3",
"Port": 8082,
"IsEnabled": true,
"Weight": 10,
"CreatedOn": "2025-01-16T11:02:55.943+08:00",
"CreatedBy": "System",
"ModifiedOn": "2025-08-12T14:54:42.52+08:00",
"ModifiedBy": "System",
"LastServiceHistoryId": "5ef3f698-51dd-4a4e-b25c-bd81e2f96e24"
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment