Copilot commented on code in PR #13386: URL: https://github.com/apache/apisix/pull/13386#discussion_r3263250766
########## apisix/core/request_json.lua: ########## @@ -0,0 +1,129 @@ +-- +-- 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 config_local = require("apisix.core.config_local") +local log = require("apisix.core.log") +local core_json = require("apisix.core.json") +local require = require +local pcall = pcall + + +local DEFAULT_JSON_LIB = "qjson" +local json_libs = { + cjson = true, + qjson = true, + simdjson = true, +} + +local qjson +local simdjson_parser + +local _M = {} + + +local function configured_json_lib() + local local_conf = config_local.local_conf() + local name = local_conf and local_conf.apisix + and local_conf.apisix.request_body_json_lib + or DEFAULT_JSON_LIB + + if not json_libs[name] then + log.warn("invalid apisix.request_body_json_lib: ", name, + ", fallback to ", DEFAULT_JSON_LIB) + return DEFAULT_JSON_LIB + end + + return name +end + + +local function normalize_result(ok, res, err) + if not ok then + return nil, res + end + + return res, err +end + + +local function qjson_module() + if qjson then + return qjson + end + + local ok, mod = pcall(require, "qjson") + if not ok then + return nil, mod + end + + qjson = mod + return qjson +end + + +local function simdjson_decode(str) + if not simdjson_parser then + local ok, simdjson = pcall(require, "resty.simdjson") + if not ok then + return nil, simdjson + end + + local parser, err = simdjson.new() + if not parser then + return nil, err + end + simdjson_parser = parser + end + + return normalize_result(pcall(simdjson_parser.decode, simdjson_parser, str)) +end + + +function _M.decode(str) + local name = configured_json_lib() + if name == "cjson" then + return core_json.decode(str) + end + + if name == "simdjson" then + return simdjson_decode(str) + end + + local mod, err = qjson_module() + if not mod then + return nil, err + end + + return normalize_result(pcall(mod.decode, str)) Review Comment: The default value of `request_body_json_lib` is `qjson`, and `_M.decode` will call `pcall(require, "qjson")` and return `nil, err` if it cannot be loaded. `core.request.get_request_body_table` (apisix/core/request.lua:374) is the only JSON entry point for *all* JSON request bodies in APISIX (not just AI plugins, despite what the new docs say). If qjson is not installed in the environment (older deployments, custom builds, etc.), every JSON request body will now fail to parse with "could not parse JSON request body: ..." instead of silently using cjson as before. Consider falling back to cjson when the configured library cannot be loaded, the way the invalid-name path falls back to the default, rather than failing the request. ########## apisix/core/request_json.lua: ########## @@ -0,0 +1,129 @@ +-- +-- 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 config_local = require("apisix.core.config_local") +local log = require("apisix.core.log") +local core_json = require("apisix.core.json") +local require = require +local pcall = pcall + + +local DEFAULT_JSON_LIB = "qjson" +local json_libs = { + cjson = true, + qjson = true, + simdjson = true, +} + +local qjson +local simdjson_parser + +local _M = {} + + +local function configured_json_lib() + local local_conf = config_local.local_conf() + local name = local_conf and local_conf.apisix + and local_conf.apisix.request_body_json_lib + or DEFAULT_JSON_LIB + + if not json_libs[name] then + log.warn("invalid apisix.request_body_json_lib: ", name, + ", fallback to ", DEFAULT_JSON_LIB) + return DEFAULT_JSON_LIB + end + + return name +end + + +local function normalize_result(ok, res, err) + if not ok then + return nil, res + end + + return res, err +end + + +local function qjson_module() + if qjson then + return qjson + end + + local ok, mod = pcall(require, "qjson") + if not ok then + return nil, mod + end + + qjson = mod + return qjson +end + + +local function simdjson_decode(str) + if not simdjson_parser then + local ok, simdjson = pcall(require, "resty.simdjson") + if not ok then + return nil, simdjson + end + + local parser, err = simdjson.new() + if not parser then + return nil, err + end + simdjson_parser = parser + end + + return normalize_result(pcall(simdjson_parser.decode, simdjson_parser, str)) Review Comment: The simdjson parser is created once at module load and cached in the module-level `simdjson_parser` variable, then shared across every request handled by this worker. This is unsafe for two reasons: 1. lua-resty-simdjson's parser holds C-level state and the table it returns from `decode()` is backed by parser-owned memory that is invalidated/overwritten by the next `decode()` call. Because `get_request_body_table` callers can yield (e.g., the body itself is read via `_M.get_body()` and downstream plugins call `ngx.sleep`, cosocket I/O, etc.), a second request decoding into the same parser while the first is still using the returned table will silently corrupt the first request's parsed body. 2. The parser is never destroyed (`parser:destroy()` is never called), so reloading the module or worker shutdown leaks the underlying parser. This should either allocate a per-request parser (and destroy it after use), use the parser's "decode + clone" path that copies the result out before returning, or otherwise document that the returned table must be fully consumed before any yield. ########## apisix/core/request_json.lua: ########## @@ -0,0 +1,129 @@ +-- +-- 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 config_local = require("apisix.core.config_local") +local log = require("apisix.core.log") +local core_json = require("apisix.core.json") +local require = require +local pcall = pcall + + +local DEFAULT_JSON_LIB = "qjson" +local json_libs = { + cjson = true, + qjson = true, + simdjson = true, +} + +local qjson +local simdjson_parser + +local _M = {} + + +local function configured_json_lib() + local local_conf = config_local.local_conf() + local name = local_conf and local_conf.apisix + and local_conf.apisix.request_body_json_lib + or DEFAULT_JSON_LIB + + if not json_libs[name] then + log.warn("invalid apisix.request_body_json_lib: ", name, + ", fallback to ", DEFAULT_JSON_LIB) + return DEFAULT_JSON_LIB + end + + return name +end + + +local function normalize_result(ok, res, err) + if not ok then + return nil, res + end + + return res, err +end + + +local function qjson_module() + if qjson then + return qjson + end + + local ok, mod = pcall(require, "qjson") + if not ok then + return nil, mod + end + + qjson = mod + return qjson Review Comment: When `pcall(require, "qjson")` fails the second return value (`mod`) is the error string raised by `require`, which is then bubbled up as an error tuple `nil, err` to callers like `get_request_body_table`. That ends up wrapped as `"could not parse JSON request body: <require error>"`, which is confusing because the failure is a missing module, not malformed JSON. Consider returning a more descriptive error such as `"failed to load qjson: ..."` and/or falling back to cjson here. ########## t/core/request.t: ########## @@ -542,3 +545,161 @@ same table: true decode_count: 1 after set_body model: claude decode_count: 2 + + + +=== TEST 18: request_json selects configured JSON library +--- config + location /t { + content_by_lua_block { + local config_local = require("apisix.core.config_local") + local orig_local_conf = config_local.local_conf + local orig_qjson = package.loaded["qjson"] + local orig_simdjson = package.loaded["resty.simdjson"] + local orig_request_json = package.loaded["apisix.core.request_json"] + + package.loaded["qjson"] = { + decode = function() + return {lib = "qjson"} + end, + encode = function(data) + return "qjson:" .. data.lib + end, + } + + package.loaded["resty.simdjson"] = { + new = function() + return { + decode = function() + return {lib = "simdjson"} + end, + } + end, + } + + local function load_with(lib) + config_local.local_conf = function() + return {apisix = {request_body_json_lib = lib}} + end + package.loaded["apisix.core.request_json"] = nil + return require("apisix.core.request_json") + end + + local request_json = load_with("qjson") + local decoded = request_json.decode("{}") + local encoded = request_json.encode({lib = "body"}) + ngx.say("qjson decode: ", decoded.lib) + ngx.say("qjson encode: ", encoded) + + request_json = load_with("simdjson") + decoded = request_json.decode("{}") + encoded = request_json.encode({lib = "body"}) + ngx.say("simdjson decode: ", decoded.lib) + ngx.say("simdjson encode: ", encoded) + + request_json = load_with("cjson") + decoded = request_json.decode('{"lib":"cjson"}') + encoded = request_json.encode({lib = "body"}) + ngx.say("cjson decode: ", decoded.lib) + ngx.say("cjson encode: ", encoded) + + config_local.local_conf = orig_local_conf + package.loaded["apisix.core.request_json"] = orig_request_json + package.loaded["qjson"] = orig_qjson + package.loaded["resty.simdjson"] = orig_simdjson Review Comment: TEST 18 monkey-patches `config_local.local_conf` and the `package.loaded` slots for `qjson`/`resty.simdjson`/`apisix.core.request_json`, but if the `content_by_lua_block` errors before the restoration block at lines 606–609 runs (e.g. an assertion failure inside the inlined code), those globals stay mutated and will corrupt subsequent tests in the same worker. Wrap the body in `pcall` (or use a `local function finalize()` invoked from `ngx.on_abort`/`finally`-style helper) so the cleanup always runs. ########## docs/en/latest/plugins/ai-proxy.md: ########## @@ -42,6 +42,17 @@ The `ai-proxy` Plugin simplifies access to LLM and embedding models by transform In addition, the Plugin also supports logging LLM request information in the access log, such as token usage, model, time to the first response, and more. These log entries are also consumed by logging plugins such as `http-logger` and `kafka-logger`. These options do not affect `error.log`. +## Request Body JSON Library + +APISIX uses `apisix.request_body_json_lib` to select the JSON library for request body parsing through `core.request.get_request_body_table`, which is shared by `ai-proxy` and other AI Plugins. It also controls JSON encoding for AI upstream request bodies. + +```yaml title="conf/config.yaml" +apisix: + request_body_json_lib: qjson +``` + +Valid values are `cjson`, `simdjson`, and `qjson`. The default is `qjson`. When `simdjson` is configured, APISIX uses `simdjson` to decode request bodies and `cjson` to encode AI upstream request bodies. Review Comment: The documentation says the selector is "shared by `ai-proxy` and other AI Plugins". In fact `request_json.decode` is wired into `core.request.get_request_body_table` (apisix/core/request.lua:374), which is used by many non-AI plugins (body-transformer, request-validation, openid-connect, etc.) for any JSON body. Document the broader scope so operators understand that changing this value affects all JSON request-body parsing, not just AI features. Same comment applies to the Chinese version at docs/zh/latest/plugins/ai-proxy.md. ########## apisix/core/request_json.lua: ########## @@ -0,0 +1,129 @@ +-- +-- 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 config_local = require("apisix.core.config_local") +local log = require("apisix.core.log") +local core_json = require("apisix.core.json") +local require = require +local pcall = pcall + + +local DEFAULT_JSON_LIB = "qjson" +local json_libs = { + cjson = true, + qjson = true, + simdjson = true, +} + +local qjson +local simdjson_parser + +local _M = {} + + +local function configured_json_lib() + local local_conf = config_local.local_conf() + local name = local_conf and local_conf.apisix + and local_conf.apisix.request_body_json_lib + or DEFAULT_JSON_LIB + + if not json_libs[name] then + log.warn("invalid apisix.request_body_json_lib: ", name, + ", fallback to ", DEFAULT_JSON_LIB) + return DEFAULT_JSON_LIB + end + + return name Review Comment: `configured_json_lib()` is called on every `decode`/`encode` invocation, which means `config_local.local_conf()` is consulted and the string is compared on every JSON parse of a request body or every AI upstream encode. `request_body_json_lib` is a static, process-level setting (it is in apisix/cli/config.lua defaults and only re-read on reload). Resolve it once at module load (and/or memoize), instead of per-call, to avoid the per-request lookup overhead in a hot path. ########## apisix/core/request_json.lua: ########## @@ -0,0 +1,129 @@ +-- +-- 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 config_local = require("apisix.core.config_local") +local log = require("apisix.core.log") +local core_json = require("apisix.core.json") +local require = require +local pcall = pcall + + +local DEFAULT_JSON_LIB = "qjson" +local json_libs = { + cjson = true, + qjson = true, + simdjson = true, +} + +local qjson +local simdjson_parser + +local _M = {} + + +local function configured_json_lib() + local local_conf = config_local.local_conf() + local name = local_conf and local_conf.apisix + and local_conf.apisix.request_body_json_lib + or DEFAULT_JSON_LIB + + if not json_libs[name] then + log.warn("invalid apisix.request_body_json_lib: ", name, + ", fallback to ", DEFAULT_JSON_LIB) + return DEFAULT_JSON_LIB + end + + return name +end + + +local function normalize_result(ok, res, err) + if not ok then + return nil, res + end + + return res, err +end + + +local function qjson_module() + if qjson then + return qjson + end + + local ok, mod = pcall(require, "qjson") + if not ok then + return nil, mod + end + + qjson = mod + return qjson +end + + +local function simdjson_decode(str) + if not simdjson_parser then + local ok, simdjson = pcall(require, "resty.simdjson") + if not ok then + return nil, simdjson + end + + local parser, err = simdjson.new() + if not parser then + return nil, err + end + simdjson_parser = parser + end + + return normalize_result(pcall(simdjson_parser.decode, simdjson_parser, str)) +end + + +function _M.decode(str) + local name = configured_json_lib() + if name == "cjson" then + return core_json.decode(str) + end + + if name == "simdjson" then + return simdjson_decode(str) + end + + local mod, err = qjson_module() + if not mod then + return nil, err + end + + return normalize_result(pcall(mod.decode, str)) +end + + +function _M.encode(data) + if configured_json_lib() == "qjson" then + local mod, err = qjson_module() + if not mod then + return nil, err + end + + return normalize_result(pcall(mod.encode, data)) + end + + return core_json.encode(data) Review Comment: Behavior in `simdjson` mode is asymmetric and surprising: `decode` uses simdjson but `encode` silently falls back to cjson (not even to the same library family). This is documented as intentional, but a user who sets `request_body_json_lib: simdjson` to get simdjson performance for AI upstream encoding will quietly get cjson encoding. Consider either renaming the option to clarify it is decode-only, or splitting it into separate decode/encode options. ########## apisix/cli/config.lua: ########## @@ -29,6 +29,7 @@ local _M = { enable_server_tokens = true, extra_lua_path = "", extra_lua_cpath = "", + request_body_json_lib = "qjson", Review Comment: The PR adds `lua-qjson` and `lua-resty-simdjson` as hard rockspec dependencies and changes the default of `request_body_json_lib` to `qjson`. This silently switches the JSON parser used for all JSON request bodies (every JSON-content-type POST/PUT that any plugin reads via `core.request.get_request_body_table`) from cjson to qjson on upgrade. Differences in qjson's handling of edge cases (large integers, NaN/Inf, surrogate pairs, duplicate keys, empty objects vs. arrays, sparse arrays, etc.) versus cjson can break existing schemas, validators, and downstream plugins in subtle ways. Consider keeping the default at `cjson` for the initial release of this feature and only opting users in explicitly, with a follow-up to switch the default once the alternatives have soaked. ########## apisix/plugins/ai-transport/http.lua: ########## @@ -19,6 +19,7 @@ -- Provides HTTP client lifecycle management for AI provider requests. local core = require("apisix.core") Review Comment: `apisix/plugins/ai-transport/http.lua` still imports `core` (line 21) and uses it elsewhere, but the encoder now uses `request_json.encode` directly while several other call sites in this directory (e.g. `aws-eventstream.lua`, `sse.lua`) continue to call `core.json.encode`/`core.json.decode` for body or chunk handling. This split makes it non-obvious to future maintainers which JSON paths participate in the configurable library and which do not. Add a short comment near the import explaining why request bodies go through `request_json` while other JSON usage stays on `core.json`, or migrate all transport-side body JSON to the same selector. ########## t/core/request.t: ########## @@ -542,3 +545,161 @@ same table: true decode_count: 1 after set_body model: claude decode_count: 2 + + + +=== TEST 18: request_json selects configured JSON library +--- config + location /t { + content_by_lua_block { + local config_local = require("apisix.core.config_local") + local orig_local_conf = config_local.local_conf + local orig_qjson = package.loaded["qjson"] + local orig_simdjson = package.loaded["resty.simdjson"] + local orig_request_json = package.loaded["apisix.core.request_json"] + + package.loaded["qjson"] = { + decode = function() + return {lib = "qjson"} + end, + encode = function(data) + return "qjson:" .. data.lib + end, + } + + package.loaded["resty.simdjson"] = { + new = function() + return { + decode = function() + return {lib = "simdjson"} + end, + } + end, + } + + local function load_with(lib) + config_local.local_conf = function() + return {apisix = {request_body_json_lib = lib}} + end + package.loaded["apisix.core.request_json"] = nil + return require("apisix.core.request_json") + end + + local request_json = load_with("qjson") + local decoded = request_json.decode("{}") + local encoded = request_json.encode({lib = "body"}) + ngx.say("qjson decode: ", decoded.lib) + ngx.say("qjson encode: ", encoded) + + request_json = load_with("simdjson") + decoded = request_json.decode("{}") + encoded = request_json.encode({lib = "body"}) + ngx.say("simdjson decode: ", decoded.lib) + ngx.say("simdjson encode: ", encoded) + + request_json = load_with("cjson") + decoded = request_json.decode('{"lib":"cjson"}') + encoded = request_json.encode({lib = "body"}) + ngx.say("cjson decode: ", decoded.lib) + ngx.say("cjson encode: ", encoded) + + config_local.local_conf = orig_local_conf + package.loaded["apisix.core.request_json"] = orig_request_json + package.loaded["qjson"] = orig_qjson + package.loaded["resty.simdjson"] = orig_simdjson + } + } +--- response_body +qjson decode: qjson +qjson encode: qjson:body +simdjson decode: simdjson +simdjson encode: {"lib":"body"} +cjson decode: cjson +cjson encode: {"lib":"body"} + + + +=== TEST 19: ai transport encoders use request_json +--- config + location /t { + content_by_lua_block { + local request_json = require("apisix.core.request_json") + local orig_encode = request_json.encode + local orig_http = package.loaded["resty.http"] + local orig_aws_config = package.loaded["resty.aws.config"] + local orig_aws = package.loaded["resty.aws"] + local orig_sign = package.loaded["resty.aws.request.sign"] + local orig_transport = package.loaded["apisix.plugins.ai-transport.http"] + local orig_auth_aws = package.loaded["apisix.plugins.ai-transport.auth-aws"] + request_json.encode = function() + return "encoded-by-request-json" + end + + package.loaded["resty.http"] = { + new = function() + return { + set_timeout = function() end, + connect = function() return true end, + request = function(_, params) + ngx.say("http body: ", params.body) + return {headers = {}, status = 200} + end, + close = function() end, + } + end, + } + + package.loaded["apisix.plugins.ai-transport.http"] = nil + local transport = require("apisix.plugins.ai-transport.http") + local res, err = transport.request({ + host = "127.0.0.1", + port = 80, + path = "/", + body = {model = "test"}, + }, 1000) + if not res then + ngx.say(err) + end + + package.loaded["resty.aws.config"] = {} + package.loaded["resty.aws"] = function() + return { + Credentials = function(_, opts) + return opts + end, + } + end + package.loaded["resty.aws.request.sign"] = function(_, req) + ngx.say("aws body: ", req.body) + return {headers = {Authorization = "signed"}} + end + + package.loaded["apisix.plugins.ai-transport.auth-aws"] = nil + local auth_aws = require("apisix.plugins.ai-transport.auth-aws") + local sign_err = auth_aws.sign_request({ + method = "POST", + host = "bedrock-runtime.us-east-1.amazonaws.com", + port = 443, + path = "/model/test/converse", + headers = {}, + body = {model = "test"}, + }, { + access_key_id = "ak", + secret_access_key = "sk", + }, "us-east-1") + if sign_err then + ngx.say(sign_err) + end + + request_json.encode = orig_encode + package.loaded["resty.http"] = orig_http + package.loaded["resty.aws.config"] = orig_aws_config + package.loaded["resty.aws"] = orig_aws + package.loaded["resty.aws.request.sign"] = orig_sign + package.loaded["apisix.plugins.ai-transport.http"] = orig_transport + package.loaded["apisix.plugins.ai-transport.auth-aws"] = orig_auth_aws + } + } +--- response_body +http body: encoded-by-request-json +aws body: encoded-by-request-json Review Comment: TEST 19 mocks `package.loaded["resty.aws"]` as a function returning a table, but real `resty.aws` is itself a callable module exposing additional symbols accessed at module load time (e.g. `resty.aws.config` is required separately at the top of `auth-aws.lua`). The test only re-requires `auth-aws.lua` after the mocks are installed, but does not isolate `apisix.core.request_json`'s own module cache from TEST 18, which mutates `package.loaded["apisix.core.request_json"]` and the global mocks for `qjson`/`resty.simdjson`. If tests run in a different order or in isolation the assertions may silently pass for the wrong reason. Consider snapshotting and restoring `package.loaded["apisix.core.request_json"]` and re-loading it explicitly in TEST 19 as well. ########## apisix/core/request_json.lua: ########## @@ -0,0 +1,129 @@ +-- +-- 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 config_local = require("apisix.core.config_local") +local log = require("apisix.core.log") +local core_json = require("apisix.core.json") +local require = require +local pcall = pcall + + +local DEFAULT_JSON_LIB = "qjson" +local json_libs = { + cjson = true, + qjson = true, + simdjson = true, +} + +local qjson +local simdjson_parser + +local _M = {} + + +local function configured_json_lib() + local local_conf = config_local.local_conf() + local name = local_conf and local_conf.apisix + and local_conf.apisix.request_body_json_lib + or DEFAULT_JSON_LIB + + if not json_libs[name] then + log.warn("invalid apisix.request_body_json_lib: ", name, + ", fallback to ", DEFAULT_JSON_LIB) + return DEFAULT_JSON_LIB Review Comment: `log.warn` here will be emitted on every single request whenever an invalid value is configured (because `configured_json_lib()` runs per decode/encode). In a typo'd deployment this floods the error log. Validate the value once at startup (the schema already restricts it to an enum, so this branch is reachable mainly via direct programmatic misuse) or rate-limit the warning. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
