pythoninthegrass Claude Sonnet 4.5 commited on
Commit
8599bf7
·
1 Parent(s): bb04104

Fix ZlibError by removing compression headers after httpx decompression

Browse files

httpx automatically decompresses responses when reading response.content,
but the proxy was forwarding the original Content-Encoding headers. This
caused clients to attempt double-decompression, resulting in ZlibError.

Fixes compression header handling in:
- Non-streaming Anthropic/OpenAI responses
- Cached responses
- Passthrough handler

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. headroom/proxy/server.py +31 -5
headroom/proxy/server.py CHANGED
@@ -1018,9 +1018,14 @@ class HeadroomProxy:
1018
  cached=True,
1019
  )
1020
 
 
 
 
 
 
1021
  return Response(
1022
  content=cached.response_body,
1023
- headers=cached.response_headers,
1024
  media_type="application/json",
1025
  )
1026
 
@@ -1297,10 +1302,15 @@ class HeadroomProxy:
1297
  f"(saved {tokens_saved:,} tokens)"
1298
  )
1299
 
 
 
 
 
 
1300
  return Response(
1301
  content=response.content,
1302
  status_code=response.status_code,
1303
- headers=dict(response.headers),
1304
  )
1305
 
1306
  except Exception as e:
@@ -1411,7 +1421,13 @@ class HeadroomProxy:
1411
  latency_ms=(time.time() - start_time) * 1000,
1412
  cached=True,
1413
  )
1414
- return Response(content=cached.response_body, headers=cached.response_headers)
 
 
 
 
 
 
1415
 
1416
  # Token counting
1417
  tokenizer = get_tokenizer(model)
@@ -1535,10 +1551,15 @@ class HeadroomProxy:
1535
  f"(saved {tokens_saved:,} tokens)"
1536
  )
1537
 
 
 
 
 
 
1538
  return Response(
1539
  content=response.content,
1540
  status_code=response.status_code,
1541
- headers=dict(response.headers),
1542
  )
1543
  except Exception as e:
1544
  self.metrics.record_failed()
@@ -1561,10 +1582,15 @@ class HeadroomProxy:
1561
  content=body,
1562
  )
1563
 
 
 
 
 
 
1564
  return Response(
1565
  content=response.content,
1566
  status_code=response.status_code,
1567
- headers=dict(response.headers),
1568
  )
1569
 
1570
 
 
1018
  cached=True,
1019
  )
1020
 
1021
+ # Remove compression headers from cached response
1022
+ response_headers = dict(cached.response_headers)
1023
+ response_headers.pop("content-encoding", None)
1024
+ response_headers.pop("content-length", None)
1025
+
1026
  return Response(
1027
  content=cached.response_body,
1028
+ headers=response_headers,
1029
  media_type="application/json",
1030
  )
1031
 
 
1302
  f"(saved {tokens_saved:,} tokens)"
1303
  )
1304
 
1305
+ # Remove compression headers since httpx already decompressed the response
1306
+ response_headers = dict(response.headers)
1307
+ response_headers.pop("content-encoding", None)
1308
+ response_headers.pop("content-length", None) # Length changed after decompression
1309
+
1310
  return Response(
1311
  content=response.content,
1312
  status_code=response.status_code,
1313
+ headers=response_headers,
1314
  )
1315
 
1316
  except Exception as e:
 
1421
  latency_ms=(time.time() - start_time) * 1000,
1422
  cached=True,
1423
  )
1424
+
1425
+ # Remove compression headers from cached response
1426
+ response_headers = dict(cached.response_headers)
1427
+ response_headers.pop("content-encoding", None)
1428
+ response_headers.pop("content-length", None)
1429
+
1430
+ return Response(content=cached.response_body, headers=response_headers)
1431
 
1432
  # Token counting
1433
  tokenizer = get_tokenizer(model)
 
1551
  f"(saved {tokens_saved:,} tokens)"
1552
  )
1553
 
1554
+ # Remove compression headers since httpx already decompressed the response
1555
+ response_headers = dict(response.headers)
1556
+ response_headers.pop("content-encoding", None)
1557
+ response_headers.pop("content-length", None) # Length changed after decompression
1558
+
1559
  return Response(
1560
  content=response.content,
1561
  status_code=response.status_code,
1562
+ headers=response_headers,
1563
  )
1564
  except Exception as e:
1565
  self.metrics.record_failed()
 
1582
  content=body,
1583
  )
1584
 
1585
+ # Remove compression headers since httpx already decompressed the response
1586
+ response_headers = dict(response.headers)
1587
+ response_headers.pop("content-encoding", None)
1588
+ response_headers.pop("content-length", None) # Length changed after decompression
1589
+
1590
  return Response(
1591
  content=response.content,
1592
  status_code=response.status_code,
1593
+ headers=response_headers,
1594
  )
1595
 
1596