feat(body): compress request bodies - #2311
Merged
Merged
Conversation
- Add `[Body(Compression = ...)]` so a method can send its body under a content coding. Refit compresses whatever the serializer produced and sets Content-Encoding, so the coding composes with every serialization method. - Add RefitSettings.RequestCompression for a client-wide default. A [Body] parameter naming its own coding overrides it, and None opts one method out. - Use the framework's GZipCompressedContent, BrotliCompressedContent and ZstandardCompressedContent on net11.0, and compress through GZipStream or BrotliStream below it. - Throw PlatformNotSupportedException for a coding this framework cannot produce rather than quietly sending the body uncompressed. gzip is always available, Brotli needs net8.0, Zstandard needs net11.0. Closes #2307
ChrisPulman
approved these changes
Aug 18, 2026
- Add RefitSettings.RequestCompressionOptions so a client can set the compressor's own knobs - window size, strategy, a Zstandard dictionary - which a CompressionLevel cannot express. - Options set for a coding replace the level for that coding; the codings left unset still compress by level. - The options types arrived with .NET 9.0, and Zstandard's with .NET 11.0, so the surface only exists from net9.0 onward.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2311 +/- ##
========================================
Coverage 99.91% 99.91%
========================================
Files 192 194 +2
Lines 10027 10138 +111
Branches 1924 1947 +23
========================================
+ Hits 10018 10129 +111
Partials 9 9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Add generator tests for the coding and level a [Body] parameter declares, including the None case that emits no call and a level declared without a coding. - Add direct tests for the coding-to-token map and for the wrap and the compressor refusing a value that names no coding. - Add options tests for Brotli and Zstandard, so every coding that accepts its own compressor settings is exercised on the targets that have them. - Read an erroneous named argument through its TypedConstant kind rather than its value, so a value the compiler already rejected leaves the defaults in place instead of being read as an int.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What kind of change does this PR introduce?
Feature.
What is the new behavior?
A method can send its request body under a content coding, per endpoint or per client.
[Body(Compression = ...)]sets the coding for one method. Refit compresses whatever the serializer produced and setsContent-Encodingto match, so the coding composes with everyBodySerializationMethod- JSON, JSON Lines, URL-encoded forms and raw strings alike.RefitSettings.RequestCompressionsets a client-wide default. A[Body]parameter naming its own coding overrides it, andRequestCompression.Noneon the parameter opts one method out of a coding the settings turned on.The coding is applied by the framework's own wrappers where they exist. .NET 11.0 uses
GZipCompressedContent,BrotliCompressedContentandZstandardCompressedContent; older targets compress throughGZipStreamorBrotliStream. Both request paths - the source-generated client and the reflection request builder - go through one entry point, so they cannot diverge.RefitSettings.RequestCompressionOptionssets the compressor's own knobs - window size, strategy, a Zstandard dictionary - which aCompressionLevelcannot express. Options set for a coding replace the level for that coding; codings left unset still compress by level. The options types arrived with .NET 9.0 and Zstandard's with .NET 11.0, so this surface only exists from net9.0 onward.An unavailable coding throws rather than sending the body uncompressed.
PlatformNotSupportedExceptionis raised when the request is built, naming what the target framework can do:Content-EncodingRequestCompression.GZipgzipRequestCompression.BrotlibrRequestCompression.ZstandardzstdWhat is the current behavior?
Refit sends every request body uncompressed.
DelegatingHandlerthat re-wraps content on the way out, which cannot tell one endpoint from another without inspecting the URI.Closes #2307
What might this PR break?
None.
[Body]andRefitSettingsleaves bodies uncompressed, so an existing client sends exactly what it sent before.CompressBodyContentcall after the body assignment. It returns the content unchanged when no coding applies, which is what makes the settings-level default reach generated code.Content-Length.Checklist
mainbranchAdditional information
There is no negotiation for a compressed request body, so this is opt-in and the README says to turn it on only against a server known to accept one.
The
.NET 11.0content wrappers are still preview API.RequestCompressionis Refit's own enum, so the coding surface is insulated from them;RequestCompressionOptionsdoes exposeZLibCompressionOptions,BrotliCompressionOptionsandZstandardCompressionOptionsdirectly, which is deliberate - mirroring those knobs into Refit types would be a surface to maintain forever and would drift from the framework.Hand-authored files worth reviewing:
RequestContentCoding.cs(resolution and per-target dispatch),CompressedContent.cs(the pre-net11.0 wrapper),RequestCompressionOptions.cs, andEmitter.Inline.Content.cs(the emitted wrap). ThePublicAPI.txtdiffs are the analyzer's own baseline fixer output.