{"record":{"id":"df40e5a002cb3c44","repo":"w7corp/easywechat","slug":"400","errorCode":"400","errorMessage":"Response body is not valid xml.","messagePattern":"Response body is not valid xml\\.","errorType":"exception","errorClass":"BadResponseException","httpStatus":null,"severity":"error","filePath":"src/Kernel/HttpClient/Response.php","lineNumber":109,"sourceCode":"     * @throws BadResponseException\n     */\n    public function toArray(?bool $throw = null): array\n    {\n        $throw ??= $this->throw;\n\n        if ('' === $content = $this->response->getContent($throw)) {\n            throw new BadResponseException('Response body is empty.');\n        }\n\n        $contentType = $this->getHeaderLine('content-type', $throw);\n\n        if (str_contains($contentType, 'text/xml')\n            || str_contains($contentType, 'application/xml')\n            || str_starts_with($content, '<xml>')) {\n            try {\n                return Xml::parse($content) ?? [];\n            } catch (Throwable $e) {\n                throw new BadResponseException('Response body is not valid xml.', 400, $e);\n            }\n        }\n\n        return $this->response->toArray($throw);\n    }\n\n    public function toJson(?bool $throw = null): string|false\n    {\n        return json_encode($this->toArray($throw), JSON_UNESCAPED_UNICODE);\n    }\n\n    /**\n     * {@inheritdoc}\n     *\n     * @throws BadMethodCallException\n     */\n    public function toStream(?bool $throw = null)\n    {","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/Kernel/HttpClient/Response.php#L91-L127","documentation":"Response::toArray() routes to Xml::parse when the content-type is text/xml or application/xml, or the body starts with '<xml>'; if parsing throws, the error is wrapped as BadResponseException('Response body is not valid xml.', 400, $e) with the original parse failure chained as previous. The request itself succeeded — the payload arrived but is not well-formed XML (truncation, HTML error page, stray BOM/whitespace).","triggerScenarios":"A v2 API (XML) response truncated by a proxy/gateway timeout, an HTML error page whose content-type or first bytes look XML-ish, or a payload cut off by body-size limits — Xml::parse then fails on the malformed markup.","commonSituations":"Nginx/CDN error pages replacing WeChat's response, response_gzip/truncation issues, BOM characters from intermediary layers, WeChat transient error pages.","solutions":["Inspect the previous exception ($e->getPrevious()) and log the raw body — the wrapped error names the exact libxml failure","If a proxy is in front, verify it is not timing out or rewriting error responses (502/504 pages in place of WeChat XML)","Retry the request: malformed bodies from transient gateway errors usually do not recur"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    $data = $response->toArray();\n} catch (\\Symfony\\Component\\HttpClient\\Exception\\BadResponseException $e) {\n    if (str_contains($e->getMessage(), 'not valid xml')) {\n        $raw = $response->getContent(false);\n        log_malformed_xml($raw, $e->getPrevious()?->getMessage());\n        // transient gateway corruption is common: retry once\n        $response = $api->get($sameUrl);\n        $data = $response->toArray();\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Log the raw body whenever XML parsing fails; the chained previous exception names the libxml error","Monitor proxy/gateway error rates on WeChat API routes","Keep response-size limits generous enough that legitimate XML bodies are never truncated"],"tags":["php","easywechat","http-client","xml","response-handling"],"backgroundTag":"xml-parse-error","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}