You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

能否通过自定义插件覆盖Apache APISIX默认的不一致错误响应行为?如何实现?

能否通过自定义插件覆盖Apache APISIX默认的不一致错误响应行为?如何实现?

当然可以!不用修改Nginx配置,通过自定义Lua插件就能统一所有错误响应的格式,而且更符合APISIX的插件化设计思路,灵活性拉满。我结合你提供的独立模式Docker环境,一步步给你讲清楚怎么做。

先唠两句背景:你说的这个问题确实挺头疼——APISIX默认的错误响应格式太乱了,404返回JSON、502返回HTML,Content-Type还不统一,客户端处理起来特别麻烦。之前大家都靠改Nginx配置解决,但用插件其实是更优雅的方案。


一、核心实现思路

APISIX的插件可以插入到Nginx的多个执行阶段,我们需要在header_filter(响应头处理阶段)拦截4xx/5xx的错误响应,先修改响应头为统一格式(比如application/json),再在body_filter(响应体处理阶段)把原响应体替换成我们定义的统一结构,这样就能实现全量错误响应归一化。


二、具体实现步骤

1. 调整项目文件结构

在你现有独立模式的项目里,新增plugins目录存放自定义插件代码:

.
├── conf/
│   ├── apisix.yaml
│   └── config.yaml
├── plugins/
│   └── unified-error-response.lua
└── compose.yaml

2. 编写自定义插件代码

创建plugins/unified-error-response.lua,这个插件会自动拦截所有错误响应并转换成统一JSON格式:

local core = require("apisix.core")

local plugin_name = "unified-error-response"

-- 插件配置的Schema(支持自定义默认错误信息、Content-Type)
local schema = {
    type = "object",
    properties = {
        default_msg = {type = "string", default = "请求处理失败"},
        content_type = {type = "string", default = "application/json; charset=utf-8"}
    },
    additionalProperties = false
}

local _M = {
    version = 0.1,
    priority = 1000, -- 优先级设高,确保在其他插件之后执行,拦截最终响应
    name = plugin_name,
    schema = schema,
}

-- 校验插件配置的合法性
function _M.check_schema(conf)
    return core.schema.check(schema, conf)
end

-- 在header_filter阶段修改响应头
function _M.header_filter(conf, ctx)
    local status = ngx.status
    -- 只处理4xx/5xx的错误响应
    if status >= 400 and status < 600 then
        -- 替换Content-Type为统一格式
        ngx.header.content_type = conf.content_type
        -- 清除原响应的Content-Length,避免新响应体长度不匹配
        ngx.header.content_length = nil
        -- 标记需要替换响应体
        ctx.need_unify_error = true
    end
end

-- 在body_filter阶段替换响应体
function _M.body_filter(conf, ctx)
    if not ctx.need_unify_error then
        return
    end

    local status = ngx.status
    -- 构建统一的错误响应结构
    local unified_res = {
        code = status,
        message = conf.default_msg,
        -- 可选:保留原错误信息作为详情
        original_detail = core.response.get_body() or ""
    }

    -- 替换原响应体为统一JSON
    ngx.arg[1] = core.json.encode(unified_res)
    ngx.arg[2] = true -- 标记响应体处理完成,截断后续内容
end

return _M

3. 配置APISIX加载自定义插件

(1)修改conf/config.yaml

添加插件加载配置,告诉APISIX要启用这个自定义插件:

deployment:
  role: data_plane
  role_data_plane:
    config_provider: yaml
plugins:
  - unified-error-response # 加入自定义插件名
# 可选:全局默认配置
plugin_attr:
  unified-error-response:
    default_msg: "全局统一错误提示"

(2)修改compose.yaml

plugins目录挂载到APISIX容器里,让容器能读取到插件代码:

name: apisix-standalone
services:
  apisix:
    image: apache/apisix:3.13.0-ubuntu
    stdin_open: true
    tty: true
    volumes:
      - ./conf/config.yaml:/usr/local/apisix/conf/config.yaml
      - ./conf/apisix.yaml:/usr/local/apisix/conf/apisix.yaml
      - ./plugins/:/usr/local/apisix/plugins/ # 新增挂载插件目录
    ports:
      - "9080:9080/tcp"
      - "9443:9443/tcp"
    networks:
      - apisix
  httpbin:
    image: kennethreitz/httpbin:latest
    ports:
      - "3000:80/tcp"
    networks:
      - apisix
networks:
  apisix:
    driver: bridge

4. 启用插件(两种方式)

方式1:全局启用(所有路由生效)

conf/config.yaml里添加全局规则,让所有请求都经过这个插件:

# 与deployment同级添加
global_rules:
  - id: 1
    plugins:
      unified-error-response:
        default_msg: "服务请求异常,请稍后重试"

方式2:单个路由启用

conf/apisix.yaml的指定路由里添加插件配置:

upstreams:
  - id: httpbin_internal
    nodes: "httpbin:80": 1
    type: roundrobin
routes:
  - id: base_internal
    uri: /anything
    upstream_id: httpbin_internal
    plugins:
      unified-error-response:
        default_msg: "接口请求失败,请检查参数"
  - id: apisix_status
    uri: /apisix_status/*
    upstream_id: httpbin_internal
    plugins:
      serverless-post-function:
        phase: access
        functions:
          - |
            return function(conf, ctx)
                local core = require("apisix.core")
                local status_code = 200
                local matched = ngx.re.match(ngx.var.uri, "^/apisix_status/([2-5][0-9]{2})$")
                if matched then
                    status_code = tonumber(matched[1])
                end
                core.response.exit(status_code)
            end
      unified-error-response: {} # 使用插件默认配置

三、测试验证

启动服务:

docker compose up -d

测试404错误

curl localhost:9080 -i

预期响应(统一JSON格式):

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
...
Server: APISIX/3.13.0

{"code":404,"message":"服务请求异常,请稍后重试","original_detail":"{\"error_msg\":\"404 Route Not Found\"}"}

测试500错误

curl localhost:9080/apisix_status/500 -i

预期响应:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json; charset=utf-8
...
Server: APISIX/3.13.0

{"code":500,"message":"服务请求异常,请稍后重试","original_detail":"<html> ... 原HTML内容 ... </html>"}

四、注意事项

  1. 插件优先级:插件的priority值越大,同一阶段的执行顺序越靠后。我们设为1000是为了确保在其他插件的响应处理逻辑之后执行,能拦截到最终的错误响应。
  2. 响应体替换:在body_filter阶段,ngx.arg[1]是当前的响应体块,ngx.arg[2]是响应是否结束的标记。我们直接替换为新的JSON并标记结束,能确保响应体被完全覆盖。
  3. 灵活定制:你可以根据需求修改插件代码,比如去掉original_detail字段、添加更多自定义字段,或者针对不同错误码返回不同的提示信息。

如果不想写自定义插件,也可以用serverless-post-function插件在对应阶段实现临时逻辑,但自定义插件更适合全局复用和长期维护~

火山引擎 最新活动