searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

APISIX源码剖析--插件

2023-06-09 08:04:02
359
0

APISIX 目前内置了各类插件,覆盖了 API 网关的各种领域,如认证鉴权、安全管理、流量管理、可观测性、多协议接入等,内置插件是通过lua实现的。

Apisix是基于Openresty+Lua实现的,Openresty处理http请求的执行阶段来自于Nginx,Nginx的HTTP框架依据常见的处理流程将处理阶段划分成了11个阶段,其中每个处理阶段可以由任意多个模块流水式处理请求。Openresty通过ngx_http_lua_module将lua脚本嵌入到各个处理阶段,执行顺序如下图(具体各个阶段的工作可以参考文档https://github.com/openresty/lua-nginx-module)。Apisix利用各个阶段执行lua脚本指令,完成插件的初始化,加载,执行,卸载功能。

 

插件初始化

nginx中的配置

    init_worker_by_lua_block {
        require("apisix").http_init_worker()
    }

文件apisix/init.lua中源码:

function _M.http_init_worker()
    local seed, err = core.utils.get_seed_from_urandom()
    if not seed then
        core.log.warn('failed to get seed from urandom: ', err)
        seed = ngx_now() * 1000 + ngx.worker.pid()
    end
    math.randomseed(seed)
    -- Because go's scheduler doesn't work after fork, we have to load the gRPC module
    -- in each worker.
    core.grpc = require("apisix.core.grpc")
    if type(core.grpc) ~= "table" then
        core.grpc = nil
    end

    local we = require("resty.worker.events")
    local ok, err = we.configure({shm = "worker-events", interval = 0.1})
    local discovery = require("apisix.discovery.init").discovery
   ......

    if core.config.init_worker then  
        local ok, err = core.config.init_worker()
        if not ok then
            core.log.error("failed to init worker process of ", core.config.type,
                           " config center, err: ", err)
        end
    end

    plugin.init_worker() #apisix自有的插件初始化
    router.http_init_worker()
    require("apisix.http.service").init_worker()
    plugin_config.init_worker()
    require("apisix.consumer").init_worker()
    consumer_group.init_worker()
    apisix_secret.init_worker()

    apisix_upstream.init_worker()
    require("apisix.plugins.ext-plugin.init").init_worker() # 第三方插件初始化
    ...
end

从源码可看,插件进程初始化实在工作进程中,在负载均衡,服务发现模块等模块之后才进行插件初始化。插件初始化包括各种插件的配置初始化以及加载到工作进程中:

function _M.init_worker()
    local _, http_plugin_names, stream_plugin_names = get_plugin_names()

    -- some plugins need to be initialized in init* phases
    if is_http and core.table.array_find(http_plugin_names, "prometheus") then
        local prometheus_enabled_in_stream =
            core.table.array_find(stream_plugin_names, "prometheus")
        require("apisix.plugins.prometheus.exporter").http_init(prometheus_enabled_in_stream)
    elseif not is_http and core.table.array_find(stream_plugin_names, "prometheus") then
        require("apisix.plugins.prometheus.exporter").stream_init()
    end

    -- someone's plugin needs to be initialized after prometheus
    -- see https://github.com/apache/apisix/issues/3286
    _M.load()

    if local_conf and not local_conf.apisix.enable_admin then
        init_plugins_syncer()
    end

    local plugin_metadatas, err = core.config.new("/plugin_metadata",
        {
            automatic = true,
            checker = check_plugin_metadata
        }
    )
    if not plugin_metadatas then
        error("failed to create etcd instance for fetching /plugin_metadatas : "
              .. err)
    end

    _M.plugin_metadatas = plugin_metadatas
end

插件执行

nginx.conf配置:

access_by_lua_block {
  apisix.http_access_phase()
}

插件处理流程图如下:

插件处理流程包括根据请求中的路由信息和用户相关信息匹配插件,经过筛选过滤插件以及插件信息合并后再执行插件,apisix/init.lua中http_access_phase 涉及到插件的主要代码如下:

......
    local route = api_ctx.matched_route
    if not route then
        -- run global rule when there is no matching route
        plugin.run_global_rules(api_ctx, router.global_rules, nil)

        core.log.info("not find any matched route")
        return core.response.exit(404,
                    {error_msg = "404 Route Not Found"})
    end
    core.log.info("matched route: ",
                  core.json.delay_encode(api_ctx.matched_route, true))

    local enable_websocket = route.value.enable_websocket

    if route.value.plugin_config_id then
        local conf = plugin_config.get(route.value.plugin_config_id)
        if not conf then
            core.log.error("failed to fetch plugin config by ",
                            "id: ", route.value.plugin_config_id)
            return core.response.exit(503)
        end

        route = plugin_config.merge(route, conf)
    end
......
    -- run global rule
    plugin.run_global_rules(api_ctx, router.global_rules, nil)
    local plugins = plugin.filter(api_ctx, route)
    plugin.run_plugin("rewrite", plugins, api_ctx)

run_plugin的核心代码:

    for i = 1, #plugins, 2 do
        local phase_func = plugins[i][phase]
        local conf = plugins[i + 1]
        if phase_func and meta_filter(api_ctx, plugins[i]["name"], conf) then
            plugin_run = true
            api_ctx._plugin_name = plugins[i]["name"]
            phase_func(conf, api_ctx)
            api_ctx._plugin_name = nil
        end
    end
0条评论
0 / 1000
w****n
6文章数
0粉丝数
w****n
6 文章 | 0 粉丝
原创

APISIX源码剖析--插件

2023-06-09 08:04:02
359
0

APISIX 目前内置了各类插件,覆盖了 API 网关的各种领域,如认证鉴权、安全管理、流量管理、可观测性、多协议接入等,内置插件是通过lua实现的。

Apisix是基于Openresty+Lua实现的,Openresty处理http请求的执行阶段来自于Nginx,Nginx的HTTP框架依据常见的处理流程将处理阶段划分成了11个阶段,其中每个处理阶段可以由任意多个模块流水式处理请求。Openresty通过ngx_http_lua_module将lua脚本嵌入到各个处理阶段,执行顺序如下图(具体各个阶段的工作可以参考文档https://github.com/openresty/lua-nginx-module)。Apisix利用各个阶段执行lua脚本指令,完成插件的初始化,加载,执行,卸载功能。

 

插件初始化

nginx中的配置

    init_worker_by_lua_block {
        require("apisix").http_init_worker()
    }

文件apisix/init.lua中源码:

function _M.http_init_worker()
    local seed, err = core.utils.get_seed_from_urandom()
    if not seed then
        core.log.warn('failed to get seed from urandom: ', err)
        seed = ngx_now() * 1000 + ngx.worker.pid()
    end
    math.randomseed(seed)
    -- Because go's scheduler doesn't work after fork, we have to load the gRPC module
    -- in each worker.
    core.grpc = require("apisix.core.grpc")
    if type(core.grpc) ~= "table" then
        core.grpc = nil
    end

    local we = require("resty.worker.events")
    local ok, err = we.configure({shm = "worker-events", interval = 0.1})
    local discovery = require("apisix.discovery.init").discovery
   ......

    if core.config.init_worker then  
        local ok, err = core.config.init_worker()
        if not ok then
            core.log.error("failed to init worker process of ", core.config.type,
                           " config center, err: ", err)
        end
    end

    plugin.init_worker() #apisix自有的插件初始化
    router.http_init_worker()
    require("apisix.http.service").init_worker()
    plugin_config.init_worker()
    require("apisix.consumer").init_worker()
    consumer_group.init_worker()
    apisix_secret.init_worker()

    apisix_upstream.init_worker()
    require("apisix.plugins.ext-plugin.init").init_worker() # 第三方插件初始化
    ...
end

从源码可看,插件进程初始化实在工作进程中,在负载均衡,服务发现模块等模块之后才进行插件初始化。插件初始化包括各种插件的配置初始化以及加载到工作进程中:

function _M.init_worker()
    local _, http_plugin_names, stream_plugin_names = get_plugin_names()

    -- some plugins need to be initialized in init* phases
    if is_http and core.table.array_find(http_plugin_names, "prometheus") then
        local prometheus_enabled_in_stream =
            core.table.array_find(stream_plugin_names, "prometheus")
        require("apisix.plugins.prometheus.exporter").http_init(prometheus_enabled_in_stream)
    elseif not is_http and core.table.array_find(stream_plugin_names, "prometheus") then
        require("apisix.plugins.prometheus.exporter").stream_init()
    end

    -- someone's plugin needs to be initialized after prometheus
    -- see https://github.com/apache/apisix/issues/3286
    _M.load()

    if local_conf and not local_conf.apisix.enable_admin then
        init_plugins_syncer()
    end

    local plugin_metadatas, err = core.config.new("/plugin_metadata",
        {
            automatic = true,
            checker = check_plugin_metadata
        }
    )
    if not plugin_metadatas then
        error("failed to create etcd instance for fetching /plugin_metadatas : "
              .. err)
    end

    _M.plugin_metadatas = plugin_metadatas
end

插件执行

nginx.conf配置:

access_by_lua_block {
  apisix.http_access_phase()
}

插件处理流程图如下:

插件处理流程包括根据请求中的路由信息和用户相关信息匹配插件,经过筛选过滤插件以及插件信息合并后再执行插件,apisix/init.lua中http_access_phase 涉及到插件的主要代码如下:

......
    local route = api_ctx.matched_route
    if not route then
        -- run global rule when there is no matching route
        plugin.run_global_rules(api_ctx, router.global_rules, nil)

        core.log.info("not find any matched route")
        return core.response.exit(404,
                    {error_msg = "404 Route Not Found"})
    end
    core.log.info("matched route: ",
                  core.json.delay_encode(api_ctx.matched_route, true))

    local enable_websocket = route.value.enable_websocket

    if route.value.plugin_config_id then
        local conf = plugin_config.get(route.value.plugin_config_id)
        if not conf then
            core.log.error("failed to fetch plugin config by ",
                            "id: ", route.value.plugin_config_id)
            return core.response.exit(503)
        end

        route = plugin_config.merge(route, conf)
    end
......
    -- run global rule
    plugin.run_global_rules(api_ctx, router.global_rules, nil)
    local plugins = plugin.filter(api_ctx, route)
    plugin.run_plugin("rewrite", plugins, api_ctx)

run_plugin的核心代码:

    for i = 1, #plugins, 2 do
        local phase_func = plugins[i][phase]
        local conf = plugins[i + 1]
        if phase_func and meta_filter(api_ctx, plugins[i]["name"], conf) then
            plugin_run = true
            api_ctx._plugin_name = plugins[i]["name"]
            phase_func(conf, api_ctx)
            api_ctx._plugin_name = nil
        end
    end
文章来自个人专栏
云原生网关
6 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0