w7corp/easywechat · error · BadResponseException
400
400
Error message
Response body is not valid xml.
What it means
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).
Source
Thrown at src/Kernel/HttpClient/Response.php:109
* @throws BadResponseException
*/
public function toArray(?bool $throw = null): array
{
$throw ??= $this->throw;
if ('' === $content = $this->response->getContent($throw)) {
throw new BadResponseException('Response body is empty.');
}
$contentType = $this->getHeaderLine('content-type', $throw);
if (str_contains($contentType, 'text/xml')
|| str_contains($contentType, 'application/xml')
|| str_starts_with($content, '<xml>')) {
try {
return Xml::parse($content) ?? [];
} catch (Throwable $e) {
throw new BadResponseException('Response body is not valid xml.', 400, $e);
}
}
return $this->response->toArray($throw);
}
public function toJson(?bool $throw = null): string|false
{
return json_encode($this->toArray($throw), JSON_UNESCAPED_UNICODE);
}
/**
* {@inheritdoc}
*
* @throws BadMethodCallException
*/
public function toStream(?bool $throw = null)
{View on GitHub (pinned to f0cf0a8b83)
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
Defensive patterns
Strategy: try-catch
Try / catch
try {
$data = $response->toArray();
} catch (\Symfony\Component\HttpClient\Exception\BadResponseException $e) {
if (str_contains($e->getMessage(), 'not valid xml')) {
$raw = $response->getContent(false);
log_malformed_xml($raw, $e->getPrevious()?->getMessage());
// transient gateway corruption is common: retry once
$response = $api->get($sameUrl);
$data = $response->toArray();
} else {
throw $e;
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Nginx/CDN error pages replacing WeChat's response, response_gzip/truncation issues, BOM characters from intermediary layers, WeChat transient error pages.
Related errors
- The type of `xml` must be string or array.
- Response body is empty.
- The type of `json` must be string or array.
- The method "%s" is not supported.
- %s does't implements %s
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/df40e5a002cb3c44.
Report an issue: GitHub.