Seq shows System.InvalidOperationException: Response Content-Length mismatch: too many bytes written on GET /api/Profile/images/{id}, thrown from ResponseCompressionBody.FinishCompressionAsync(). Anonymous users; the visitor gets a 500 instead of an avatar.
Confirmed
Both failing objects are already-compressed formats, and the body came out LARGER than the declared Content-Length:
Object
Stored type
Declared
Written
Delta
6a5d78ca…
image/png
658,063
737,280
+12.04%
69da67d2…
image/avif
163,409
163,840
+0.26%
Verified live that these responses really are compressed:
Compressing an image cannot shrink it and routinely grows it (framing overhead on incompressible data; worse at CompressionLevel.Fastest). The response declares Content-Length from the stored object, so the overflow makes Kestrel abort.
Also worth noting
The duplicated Vary: Accept-Encoding is two layers compressing: Traefik already runs a gzip-compress middleware on this router (spikersoft-infrastructure/spikersoft-backend/docker-stack.yml:107,116) AND ASP.NET has AddResponseCompression. Text responses were being compressed twice. Not harmful on its own, but redundant CPU on every request — worth deciding which layer should own it.
Fix
options.ExcludedMimeTypes for image/video/audio/font and the common already-compressed binaries. ExcludedMimeTypes wins over MimeTypes, so it stays correct however the default set evolves. Traefik still compresses text at the edge, so nothing loses compression.
Covered by ResponseCompressionExclusionTests, which drives the real ResponseCompressionProvider (not the options object) so it asserts the actual verdict.
Seq shows `System.InvalidOperationException: Response Content-Length mismatch: too many bytes written` on `GET /api/Profile/images/{id}`, thrown from `ResponseCompressionBody.FinishCompressionAsync()`. Anonymous users; the visitor gets a 500 instead of an avatar.
## Confirmed
Both failing objects are already-compressed formats, and the body came out LARGER than the declared Content-Length:
| Object | Stored type | Declared | Written | Delta |
|---|---|---|---|---|
| `6a5d78ca…` | image/png | 658,063 | 737,280 | **+12.04%** |
| `69da67d2…` | image/avif | 163,409 | 163,840 | **+0.26%** |
Verified live that these responses really are compressed:
```
$ curl -sI -H 'Accept-Encoding: br, gzip' https://api.spikersoft.com/api/Profile/images/<id>
content-encoding: gzip
content-type: image/avif
vary: Accept-Encoding
vary: Accept-Encoding <-- duplicated
```
Compressing an image cannot shrink it and routinely grows it (framing overhead on incompressible data; worse at `CompressionLevel.Fastest`). The response declares Content-Length from the stored object, so the overflow makes Kestrel abort.
## Also worth noting
The duplicated `Vary: Accept-Encoding` is two layers compressing: Traefik already runs a `gzip-compress` middleware on this router (`spikersoft-infrastructure/spikersoft-backend/docker-stack.yml:107,116`) AND ASP.NET has `AddResponseCompression`. Text responses were being compressed twice. Not harmful on its own, but redundant CPU on every request — worth deciding which layer should own it.
## Fix
`options.ExcludedMimeTypes` for image/video/audio/font and the common already-compressed binaries. ExcludedMimeTypes wins over MimeTypes, so it stays correct however the default set evolves. Traefik still compresses text at the edge, so nothing loses compression.
Covered by `ResponseCompressionExclusionTests`, which drives the real `ResponseCompressionProvider` (not the options object) so it asserts the actual verdict.
Resolved in spikersoft-backend PR #483. Verified against origin/master:
options.ExcludedMimeTypes configured at SpikerSoft.Api/Program.cs:129-140, covering image/*, video/*, audio/*, font/*, zip, gzip, 7z, pdf and model/gltf-binary, with the rationale written inline at :114-128.
ResponseCompressionExclusionTests.cs drives the realResponseCompressionProvider (:58, :66) rather than asserting against a config literal, and covers both directions: the types that were 500-ing and the ones that must stay compressed.
The Traefik double-compression paragraph in the ticket was framed as "worth deciding" rather than acceptance, and is deliberately untouched. If you want it settled, it needs its own ticket.
One durability note for whoever touches this next: the test's excluded-type list at :41-52 is a hand-copied literal with no link back to Program.cs, so the two can drift silently. Adding a type to Program.cs without updating the test would pass.
Closing.
Resolved in spikersoft-backend PR #483. Verified against `origin/master`:
- `options.ExcludedMimeTypes` configured at `SpikerSoft.Api/Program.cs:129-140`, covering `image/*`, `video/*`, `audio/*`, `font/*`, zip, gzip, 7z, pdf and `model/gltf-binary`, with the rationale written inline at `:114-128`.
- `ResponseCompressionExclusionTests.cs` drives the **real** `ResponseCompressionProvider` (`:58`, `:66`) rather than asserting against a config literal, and covers both directions: the types that were 500-ing and the ones that must stay compressed.
The Traefik double-compression paragraph in the ticket was framed as "worth deciding" rather than acceptance, and is deliberately untouched. If you want it settled, it needs its own ticket.
One durability note for whoever touches this next: the test's excluded-type list at `:41-52` is a hand-copied literal with no link back to `Program.cs`, so the two can drift silently. Adding a type to `Program.cs` without updating the test would pass.
Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Seq shows
System.InvalidOperationException: Response Content-Length mismatch: too many bytes writtenonGET /api/Profile/images/{id}, thrown fromResponseCompressionBody.FinishCompressionAsync(). Anonymous users; the visitor gets a 500 instead of an avatar.Confirmed
Both failing objects are already-compressed formats, and the body came out LARGER than the declared Content-Length:
6a5d78ca…69da67d2…Verified live that these responses really are compressed:
Compressing an image cannot shrink it and routinely grows it (framing overhead on incompressible data; worse at
CompressionLevel.Fastest). The response declares Content-Length from the stored object, so the overflow makes Kestrel abort.Also worth noting
The duplicated
Vary: Accept-Encodingis two layers compressing: Traefik already runs agzip-compressmiddleware on this router (spikersoft-infrastructure/spikersoft-backend/docker-stack.yml:107,116) AND ASP.NET hasAddResponseCompression. Text responses were being compressed twice. Not harmful on its own, but redundant CPU on every request — worth deciding which layer should own it.Fix
options.ExcludedMimeTypesfor image/video/audio/font and the common already-compressed binaries. ExcludedMimeTypes wins over MimeTypes, so it stays correct however the default set evolves. Traefik still compresses text at the edge, so nothing loses compression.Covered by
ResponseCompressionExclusionTests, which drives the realResponseCompressionProvider(not the options object) so it asserts the actual verdict.Resolved in spikersoft-backend PR #483. Verified against
origin/master:options.ExcludedMimeTypesconfigured atSpikerSoft.Api/Program.cs:129-140, coveringimage/*,video/*,audio/*,font/*, zip, gzip, 7z, pdf andmodel/gltf-binary, with the rationale written inline at:114-128.ResponseCompressionExclusionTests.csdrives the realResponseCompressionProvider(:58,:66) rather than asserting against a config literal, and covers both directions: the types that were 500-ing and the ones that must stay compressed.The Traefik double-compression paragraph in the ticket was framed as "worth deciding" rather than acceptance, and is deliberately untouched. If you want it settled, it needs its own ticket.
One durability note for whoever touches this next: the test's excluded-type list at
:41-52is a hand-copied literal with no link back toProgram.cs, so the two can drift silently. Adding a type toProgram.cswithout updating the test would pass.Closing.