Skip to content

fix: two-layer gzip decompression for all inbound routes - #160

Merged
SudipSinha merged 1 commit into
mainfrom
fix/gzip-middleware-all-paths
Jul 27, 2026
Merged

fix: two-layer gzip decompression for all inbound routes#160
SudipSinha merged 1 commit into
mainfrom
fix/gzip-middleware-all-paths

Conversation

@SudipSinha

@SudipSinha SudipSinha commented Jun 4, 2026

Copy link
Copy Markdown
Member

Summary

Fixes gzip decompression for KServe inference payloads across all three inbound paths:

  • /data/upload (FastAPI)
  • /consumer/kserve/v2 (FastAPI)
  • CloudEvent consumer (POST /) (FastAPI)

Mirrors the two-layer fix from Java TrustyAI service: trustyai-explainability/trustyai-explainability#707

Problem

TrustyAI fails to parse gzip-compressed inference payloads from KServe logger sidecar with error:

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

The gzip magic bytes 0x1F 0x8B are being parsed as JSON. The initial gzip middleware was scoped only to /data/upload, leaving two paths uncovered:

  1. /consumer/kserve/v2 — FastAPI endpoint but outside middleware scope
  2. CloudEvent consumer (POST /) — Knative Eventing bypasses standard HTTP middleware

Design Philosophy

HTTP Content-Encoding is a transport-layer concern, not an application-layer concern. Handling it per-endpoint creates coverage gaps — every new endpoint becomes vulnerable by default until someone remembers to add decompression.

The correct approach is layered defense:

  1. Transport layer first: Enable middleware-level decompression to handle all HTTP requests before they reach endpoint handlers

  2. Application layer fallback: When a framework bypasses the transport layer (Knative Eventing strips Content-Encoding header but leaves body gzipped), add endpoint-specific decompression as a safety net

This ensures:

  • New endpoints are secure by default (middleware handles them)
  • Framework-specific edge cases are covered (application layer catches what middleware misses)
  • No per-endpoint boilerplate

Solution

Two-layer fix:

  1. Middleware layer (FastAPI endpoints):

    • Change GzipRequestMiddleware.DEFAULT_PATHS from ("/data/upload",) to ("*",)
    • All routes now get automatic gzip decompression
  2. Application layer (CloudEvent endpoints):

    • Add decompress_if_gzip() utility in src/endpoints/consumer/gzip_utils.py
    • Magic byte detection (0x1F 0x8B)
    • 100MB decompression bomb guard
    • Graceful fallback on malformed payloads
    • Called from CloudEvent consumer when Content-Encoding header is missing

Changes

File Change
src/middleware/gzip_middleware.py Changed DEFAULT_PATHS to ("*",)
src/endpoints/consumer/gzip_utils.py +59 lines (gzip detection + bomb guard)
src/endpoints/consumer/consumer_endpoint.py +30 lines (CloudEvent gzip handling)
src/endpoints/data/data_upload.py Removed explicit path from middleware call
tests/middleware/test_gzip_middleware_unit.py +45 lines (parametrized route tests)
tests/endpoints/consumer/test_cloud_event_gzip.py +92 lines (4 CloudEvent tests)

Net: +245 / -24 lines

Test Plan

  • uv run pytest tests/middleware/test_gzip_middleware_unit.py -v - All tests pass
  • uv run pytest tests/endpoints/consumer/test_cloud_event_gzip.py -v - All tests pass
  • Verify middleware decompresses on all routes (/data/upload, /consumer/kserve/v2, /)
  • Verify CloudEvent endpoint handles gzip body without Content-Encoding header
  • Cluster validation

Related


🤖 Generated with Claude Code

@SudipSinha SudipSinha self-assigned this Jun 4, 2026
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@SudipSinha, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff8bfbd7-60cb-41ba-aa75-0349fc1e51e9

📥 Commits

Reviewing files that changed from the base of the PR and between 483b8fa and 6afc88e.

📒 Files selected for processing (6)
  • src/trustyai_service/endpoints/consumer/consumer_endpoint.py
  • src/trustyai_service/endpoints/consumer/gzip_utils.py
  • src/trustyai_service/endpoints/data/data_upload.py
  • src/trustyai_service/middleware/gzip_middleware.py
  • tests/endpoints/consumer/test_cloud_event_gzip.py
  • tests/middleware/test_gzip_middleware_unit.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gzip-middleware-all-paths

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.

@SudipSinha
SudipSinha force-pushed the fix/gzip-middleware-all-paths branch 3 times, most recently from 8227c5c to 4639c6e Compare June 8, 2026 19:36
@SudipSinha
SudipSinha marked this pull request as ready for review June 8, 2026 22:07
@SudipSinha SudipSinha added bug Something isn't working python Pull requests that update python code ok-to-test Proceed with CI testing tests high-priority Critical issues that should be addressed soon technical-debt Code that works but needs improvement (untested, deprecated patterns, etc.) labels Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR image build and manifest generation completed successfully!

📦 PR image: quay.io/trustyai/trustyai-service-python-ci:6afc88e5eea07d0840aba425519422748d170d79

🗂️ CI manifests

devFlags:
  manifests:
    - contextDir: config
      sourcePath: ''
      uri: https://api.github.com/repos/trustyai-explainability/trustyai-service-operator-ci/tarball/service-python-6afc88e5eea07d0840aba425519422748d170d79

@SudipSinha SudipSinha added enhancement New feature or request and removed bug Something isn't working labels Jun 18, 2026
@SudipSinha
SudipSinha force-pushed the fix/gzip-middleware-all-paths branch from 4639c6e to 9e39299 Compare June 18, 2026 14:16
@SudipSinha
SudipSinha requested review from RobGeada and ruivieira July 7, 2026 12:54
@@ -478,13 +480,11 @@ async def consume_cloud_event(
)
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=msg)
logger.info("KServe Inference Input %s received.", payload.id)
# if a match is found, the payload is auto-deleted from data

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.

are these comments not a useful thing to keep?

@@ -532,8 +531,6 @@ async def consume_cloud_event(
"message": f"Output payload {payload.id} processed successfully",
}

# Defensive programming: this should never happen due to type annotation
# but adding explicit fallback for type safety

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.

ibid

@@ -516,7 +516,6 @@ async def consume_cloud_event(
)
if partial_input is not None:
if not isinstance(partial_input, KServeInferenceRequest):
# This should never happen - indicates storage interface error

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.

ibid

@SudipSinha
SudipSinha force-pushed the fix/gzip-middleware-all-paths branch 3 times, most recently from b59e0af to 63b4728 Compare July 24, 2026 13:56
@SudipSinha
SudipSinha force-pushed the fix/gzip-middleware-all-paths branch from 63b4728 to 6afc88e Compare July 24, 2026 15:02
@SudipSinha
SudipSinha merged commit b929881 into main Jul 27, 2026
11 checks passed
@SudipSinha
SudipSinha deleted the fix/gzip-middleware-all-paths branch July 27, 2026 10:34
SudipSinha added a commit that referenced this pull request Aug 13, 2026
Addresses review feedback from @RobGeada on PR #160. Three comments
were removed during the gzip middleware refactor that document
non-obvious behavior:

- get_partial_payload() auto-deletes the matched partial on retrieval
- Type guard checks are defensive (should never happen at runtime)
- Final raise is unreachable due to type annotation (explicit fallback)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Sudip Sinha <Sudip.Sinha@RedHat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request high-priority Critical issues that should be addressed soon ok-to-test Proceed with CI testing python Pull requests that update python code technical-debt Code that works but needs improvement (untested, deprecated patterns, etc.) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants