Improve Docker API response parsing

- Refactor into separate methods for cleaner code
- Better handling of chunked transfer encoding
- Add more debug logging to diagnose parsing failures
- Log body preview when JSON not found
This commit is contained in:
GitHub Copilot
2026-01-24 12:25:08 +00:00
parent bf54d44c7e
commit 83b6503501
+48 -20
View File
@@ -65,37 +65,65 @@ class DockerStatsCollector:
chunks.append(chunk)
sock.close()
return b"".join(chunks)
except (OSError, TimeoutError):
except (OSError, TimeoutError) as e:
log.debug("Socket error for %s: %s", path, e)
return None
response = await loop.run_in_executor(None, _sync_request)
if response is None:
log.debug("No response from Docker socket for %s", path)
return None
# Parse HTTP response - find JSON body after headers
return self._parse_docker_response(path, response)
def _parse_docker_response(self, path: str, response: bytes) -> dict | list | None:
"""Parse HTTP response from Docker socket."""
try:
response_str = response.decode("utf-8", errors="replace")
# Split headers and body
if "\r\n\r\n" in response_str:
_, body = response_str.split("\r\n\r\n", 1)
else:
body = response_str
# Handle chunked encoding - find the JSON object/array
if body.startswith("{") or body.startswith("["):
json_str = body
else:
# Skip chunk size line in chunked encoding
lines = body.split("\r\n")
for line in lines:
if line.startswith("{") or line.startswith("["):
json_str = line
break
else:
if "\r\n\r\n" not in response_str:
log.debug("No header separator in response for %s", path)
return None
return json.loads(json_str)
except (json.JSONDecodeError, ValueError):
_, body = response_str.split("\r\n\r\n", 1)
body = body.strip()
# Try direct parse first
if body.startswith("{") or body.startswith("["):
first_line = body.split("\r\n")[0] if "\r\n" in body else body
return json.loads(first_line)
# Handle chunked transfer encoding
lines = body.split("\r\n")
for i, raw_line in enumerate(lines):
stripped = raw_line.strip()
if stripped.startswith("{") or stripped.startswith("["):
result = self._try_parse_chunked_json(stripped, lines[i + 1:])
if result is not None:
return result
log.debug("Could not find JSON in response for %s, body preview: %s", path, body[:200] if body else "(empty)")
return None
except (json.JSONDecodeError, ValueError) as e:
log.debug("JSON parse error for %s: %s", path, e)
return None
def _try_parse_chunked_json(self, first_part: str, remaining_lines: list[str]) -> dict | list | None:
"""Try to parse JSON that may be split across chunked encoding."""
try:
return json.loads(first_part)
except json.JSONDecodeError:
pass
# Try joining remaining lines (skip chunk size markers)
json_parts = [first_part]
for raw_line in remaining_lines:
part = raw_line.strip()
if part and not part.isalnum(): # Skip hex chunk sizes
json_parts.append(part)
try:
return json.loads("".join(json_parts))
except json.JSONDecodeError:
return None
async def _discover_containers(self, service_names: list[str]) -> dict[str, str]: