{"record":{"id":"cafecdf5706b0b3c","repo":"apache/shenyu","slug":"request-body-must-not-be-empty","errorCode":null,"errorMessage":"Request body must not be empty","messagePattern":"Request body must not be empty","errorType":"validation","errorClass":"ShenyuException","httpStatus":null,"severity":"error","filePath":"shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/protocol/OpenAiProtocolAdapter.java","lineNumber":119,"sourceCode":"    public static ChatCompletionRequest toChatCompletionRequest(final String requestBody, final boolean stream) {\n        return toChatCompletionRequest(requestBody, stream, null);\n    }\n\n    /**\n     * Parse raw request body directly into ChatCompletionRequest, preserving modeled fields.\n     * When fallbackConfig is provided, its non-null fields (model, temperature, maxTokens)\n     * override the client request values, matching the original ChatModel-based fallback behavior\n     * where the fallback ChatModel's config takes precedence.\n     *\n     * @param requestBody     the raw JSON request body in OpenAI Chat Completions format\n     * @param stream          whether this is a streaming request\n     * @param fallbackConfig  the fallback config whose non-null fields override client values\n     * @return a ChatCompletionRequest with modeled fields preserved\n     */\n    public static ChatCompletionRequest toChatCompletionRequest(final String requestBody,\n            final boolean stream, final AiCommonConfig fallbackConfig) {\n        if (Objects.isNull(requestBody) || requestBody.isEmpty()) {\n            throw new ShenyuException(\"Request body must not be empty\");\n        }\n        final JsonNode root = parseStrict(requestBody);\n        if (Objects.isNull(root) || !root.isObject()) {\n            throw new ShenyuException(\"Invalid request body: expected a JSON object\");\n        }\n        final ObjectNode mutableRoot = (ObjectNode) root;\n\n        if (Objects.nonNull(fallbackConfig)) {\n            if (Objects.nonNull(fallbackConfig.getModel()) && !fallbackConfig.getModel().isEmpty()) {\n                mutableRoot.put(FIELD_MODEL, fallbackConfig.getModel());\n            }\n            if (Objects.nonNull(fallbackConfig.getTemperature())) {\n                mutableRoot.put(FIELD_TEMPERATURE, fallbackConfig.getTemperature());\n            }\n            if (Objects.nonNull(fallbackConfig.getMaxTokens())) {\n                mutableRoot.put(FIELD_MAX_TOKENS, fallbackConfig.getMaxTokens());\n                mutableRoot.put(FIELD_MAX_COMPLETION_TOKENS, fallbackConfig.getMaxTokens());\n            }","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/apache/shenyu/blob/567142e07261b3e615ae8850b30f4421f455cc5d/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/protocol/OpenAiProtocolAdapter.java#L101-L137","documentation":"OpenAiProtocolAdapter.toChatCompletionRequest parses an incoming OpenAI-compatible chat completion request body. The very first check rejects a null or empty string body with ShenyuException('Request body must not be empty'), since there is no JSON to model a request from.","triggerScenarios":"Calling toChatCompletionRequest(requestBody, stream, fallbackConfig) with null or \"\" as the body — typically when the gateway plugin forwarded a request whose body was never read or was stripped.","commonSituations":"Client sent an empty POST body to the AI proxy route, body already consumed upstream so it reads as empty, streaming requests where the raw body was not buffered.","solutions":["Return HTTP 400 to the client and require a non-empty JSON body on the chat completions endpoint","Read the request body via ServerWebExchange/ServerHttpRequest properly before adapting","Check that no intermediate plugin consumed or discarded the request body","Add an explicit empty-body check/handler in the AI plugin before calling the adapter"],"exampleFix":"// before\nString body = \"\"; // never populated\nOpenAiProtocolAdapter.toChatCompletionRequest(body, stream, config);\n// after\nif (body == null || body.isBlank()) { return badRequest(\"body required\"); }\nChatCompletionRequest req = OpenAiProtocolAdapter.toChatCompletionRequest(body, stream, config);","handlingStrategy":"validation","validationCode":"if (requestBody == null || requestBody.isBlank()) {\n    return ServerResponse.badRequest(\"request body required\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    ChatCompletionRequest req = OpenAiProtocolAdapter.toChatCompletionRequest(body, stream, config);\n} catch (ShenyuException e) {\n    if (e.getMessage().contains(\"must not be empty\")) {\n        return ResponseEntity.badRequest().body(\"request body must not be empty\");\n    }\n    throw e;\n}","preventionTips":["Enforce a non-empty JSON body on AI proxy routes","Buffer/read the body exactly once before adapting","Check no upstream plugin consumed the body","Return 400 with a clear message to clients sending empty bodies"],"tags":["ai","http","validation"],"backgroundTag":"empty-required-field","analyzedSha":"567142e07261b3e615ae8850b30f4421f455cc5d","analyzedAt":"2026-09-12T10:08:21.293Z","contentChangedAt":"2026-09-12T10:08:21.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}