Skip to content

Add content-encoding predicate to StreamDecoderFactory - #6684

Merged
ikhoon merged 4 commits into
line:mainfrom
AlexKolpa:encoding-predicate
Apr 6, 2026
Merged

Add content-encoding predicate to StreamDecoderFactory#6684
ikhoon merged 4 commits into
line:mainfrom
AlexKolpa:encoding-predicate

Conversation

@AlexKolpa

Copy link
Copy Markdown
Contributor

Motivation:

For snappy encoding the client passes x-snappy-framed as accept-encoding header. While this is recognized, some frameworks use just snappy (the frame format is implied), and will return that as the content-encoding (this is the case for Netty for example). This mismatch leads the decoder to miss this encoding, resulting in it passing the non-decoded message through. By adding a predicate check to each StreamDecoderFactory, this can be made more flexible by letting each decoder factory specify what it is able to handle.

Modifications:

  • Added matchesEncodingHeaderValue to StreamDecoderFactory
  • DecodingClient builds accept-encoding header from Set of StreamDecoderFactorys provided.
  • DefaultHttpDecodedResponse checks returned content-encoding using matchesEncodingHeaderValue for each StreamDecoderFactory

Result:

Instead of using the `encodingHeaderValue` output which is used for the `accept-encoding` header, each factory can now specify a predicate to check `content-encoding` header values in the response against (defaulting to the output of `encodingHeaderValue`). This way, when the response does not return the exact encoding value (which is the case for `x-snappy-framed` being passed as `accept-encoding`, while receiving `snappy` as `content-encoding`), the client still handles it correctly.
@CLAassistant

CLAassistant commented Mar 27, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23abcd55-2f04-4361-90ee-b1e439355e10

📥 Commits

Reviewing files that changed from the base of the PR and between 0313cd2 and c682835.

📒 Files selected for processing (1)
  • core/src/main/java/com/linecorp/armeria/client/encoding/StreamDecoderFactory.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/src/main/java/com/linecorp/armeria/client/encoding/StreamDecoderFactory.java

📝 Walkthrough

Walkthrough

Decoder factory storage moved from a keyed Map to an iterable Set; decoder selection now iterates factories and uses a new supportsContentEncoding(String) hook. The SNAPPY factory was relaxed to accept both snappy and x-snappy-framed content-encoding values. Tests updated accordingly.

Changes

Cohort / File(s) Summary
Decoding client core
core/src/main/java/com/linecorp/armeria/client/encoding/DecodingClient.java
Switched internal decoder storage to an immutable Set<StreamDecoderFactory>; build default Accept-Encoding by joining factories' encodingHeaderValue(); select supported factories by calling supportsContentEncoding(...).
Decoded response handling
core/src/main/java/com/linecorp/armeria/internal/common/encoding/DefaultHttpDecodedResponse.java
Constructor now accepts Iterable<? extends StreamDecoderFactory>; Content-Encoding lookup iterates factories and picks the first where supportsContentEncoding(contentEncoding) is true; removed prior map/Ascii lowercasing lookup.
Factory interface
core/src/main/java/com/linecorp/armeria/client/encoding/StreamDecoderFactory.java
Added default method supportsContentEncoding(String encodingValue) (null-checked, case-insensitive equality by default) to allow custom matching logic.
Snappy factory
core/src/main/java/com/linecorp/armeria/common/encoding/StreamDecoderFactories.java
SNAPPY enum constant overrides supportsContentEncoding(...) to accept both "snappy" and "x-snappy-framed" (case-insensitive).
Tests — decoded response
core/src/test/java/com/linecorp/armeria/common/encoding/DefaultHttpDecodedResponseTest.java
Updated fixtures/mocks to provide Set<StreamDecoderFactory> instead of Map; adjusted mocks to stub supportsContentEncoding(...) where needed.
Tests — decoding client
core/src/test/java/com/linecorp/armeria/client/encoding/DecodingClientTest.java
Added /snappy-encoding endpoint and snappyDecoding() test to verify relaxed SNAPPY content-encoding handling when using DecodingClient with the snappy factory.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant DecodingClient as DecodingClient
  participant ResponseHandler as DefaultHttpDecodedResponse
  participant Factories as StreamDecoderFactory[s]
  participant Decoder as StreamDecoder

  Client->>DecodingClient: Send request
  DecodingClient->>DecodingClient: Compute Accept-Encoding from factories
  DecodingClient->>ResponseHandler: Receive HttpResponse (with Content-Encoding)
  ResponseHandler->>Factories: iterate & call supportsContentEncoding(contentEncoding)
  Factories-->>ResponseHandler: matching factory (first match)
  ResponseHandler->>Decoder: create decoder from factory
  ResponseHandler->>ResponseHandler: remove Content-Encoding/Content-Length headers
  ResponseHandler->>Client: deliver decoded HttpData
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
I hopped through headers, light and snappy,
Swapped keys for sets — no lookups sappy.
I listen for "snappy" or "x-" with glee,
Decode the stream, then back to the lea.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a content-encoding predicate to StreamDecoderFactory to enable flexible encoding matching.
Description check ✅ Passed The description is related to the changeset, explaining the motivation (snappy encoding mismatch), modifications made, and the result; it directly addresses the linked issue.
Linked Issues check ✅ Passed The PR successfully addresses issue #6681 by adding a content-encoding predicate method to StreamDecoderFactory, enabling flexible matching of encoding values like both 'snappy' and 'x-snappy-framed'.
Out of Scope Changes check ✅ Passed All changes are directly related to addressing the snappy encoding mismatch issue: adding the predicate method, updating decoder selection logic, and adding test coverage for snappy decoding.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@core/src/main/java/com/linecorp/armeria/client/encoding/StreamDecoderFactory.java`:
- Around line 54-56: Add the `@UnstableApi` annotation to the newly added default
method matchesEncodingHeaderValue and make it null-safe by validating the input
with Objects.requireNonNull(encodingValue, "encodingValue") before using it;
keep the existing call to encodingHeaderValue() and then compare via
equalsIgnoreCase as before. Ensure the import for java.util.Objects is present
and the `@UnstableApi` import is added so the signature and annotation compile.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a8435a9f-2d31-46f1-88b7-91b3ccfa3793

📥 Commits

Reviewing files that changed from the base of the PR and between 61c0fbf and 8d120ef.

📒 Files selected for processing (5)
  • core/src/main/java/com/linecorp/armeria/client/encoding/DecodingClient.java
  • core/src/main/java/com/linecorp/armeria/client/encoding/StreamDecoderFactory.java
  • core/src/main/java/com/linecorp/armeria/common/encoding/StreamDecoderFactories.java
  • core/src/main/java/com/linecorp/armeria/internal/common/encoding/DefaultHttpDecodedResponse.java
  • core/src/test/java/com/linecorp/armeria/common/encoding/DefaultHttpDecodedResponseTest.java

Comment thread core/src/main/java/com/linecorp/armeria/client/encoding/StreamDecoderFactory.java Outdated
@codecov

codecov Bot commented Mar 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.95%. Comparing base (8150425) to head (c682835).
⚠️ Report is 398 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6684      +/-   ##
============================================
- Coverage     74.46%   73.95%   -0.51%     
- Complexity    22234    24040    +1806     
============================================
  Files          1963     2171     +208     
  Lines         82437    90191    +7754     
  Branches      10764    11831    +1067     
============================================
+ Hits          61385    66705    +5320     
- Misses        15918    17884    +1966     
- Partials       5134     5602     +468     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jrhee17 jrhee17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me - what do you think of also adding a test case to confirm snappy is handled properly?

// Use only supported encodings.
final String acceptEncodingHeader = String.join(",", availableFactories.keySet());
final String acceptEncodingHeader = availableFactories.stream()
.map(StreamDecoderFactory::encodingHeaderValue)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this may rewrite snappy to x-snappy-framed - there's probably no harm in this though

*/
String encodingHeaderValue();

default boolean matchesEncodingHeaderValue(String encodingValue) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted that DecodingClient only uses this filter (and not DecodingService). I'm unsure if we would want consistent behavior also in the service at this stage - it may be fine to add it if needed later on.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For full consistency I believe EncodingService should also be updated to ensure both incoming and outgoing messages handle the accept-encoding/content-encoding consistently. For the issue at hand I think just the client suffices, but I'll leave it up to you. Let me know if you want me to add it to the service as well!

*/
String encodingHeaderValue();

default boolean matchesEncodingHeaderValue(String encodingValue) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but in retrospect I think it may be better to omit header from the method name since it may be ambiguous whether the input is the entire header value, or the encoding value.

What do you think of renaming to supportsContentEncoding?

@AlexKolpa

Copy link
Copy Markdown
Contributor Author

Thank you for the review! I've addressed the feedback and added a test for the DecodingClient to validate the behavior.

@jrhee17 jrhee17 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍 👍

@jrhee17 jrhee17 added this to the 1.38.0 milestone Mar 31, 2026

@ikhoon ikhoon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍 👍

@minwoox minwoox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! 👍

@ikhoon
ikhoon merged commit e86cf86 into line:main Apr 6, 2026
16 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relax handling of snappy content-encoding response header

5 participants