Skip to content

feat: API response compression & payload optimization (#129)#393

Open
sinatragianpaolo-oc wants to merge 2 commits intorohitdash08:mainfrom
sinatragianpaolo-oc:feat/api-compression
Open

feat: API response compression & payload optimization (#129)#393
sinatragianpaolo-oc wants to merge 2 commits intorohitdash08:mainfrom
sinatragianpaolo-oc:feat/api-compression

Conversation

@sinatragianpaolo-oc
Copy link

@sinatragianpaolo-oc sinatragianpaolo-oc commented Mar 13, 2026

Adds gzip compression for JSON/text responses via Flask after_request hook to fix #129.

Uses Python's built-in gzip — no extra dependencies. Skips small payloads (< 512 bytes), non-compressible content types, and clients that don't send Accept-Encoding: gzip.


Review fixes (latest commit)

  1. Streaming responses skipped in _maybe_compress — added a direct_passthrough check before get_data(). Calling get_data() on a direct_passthrough / stream_with_context response buffers the entire stream in memory, defeating the purpose of streaming. Streaming responses should be compressed at the proxy layer (e.g. nginx gzip_proxied). A comment in the code explains the rationale.

  2. X-Compression-Ratio gated on app.debug — the header reveals payload size information that could assist compression-based side-channel attacks (e.g. BREACH) in production. Now only emitted when current_app.debug is True.

  3. test_large_json_is_compressed rewritten — the original test created 30 expense rows via POST (slow, DB-dependent, fragile). Replaced with a lightweight route registered on the test app that returns a large in-memory JSON body directly — no DB interaction, runs in milliseconds.

New tests:

  • test_streaming_response_skipped — uses MagicMock with direct_passthrough=True, asserts get_data() is never called
  • test_x_compression_ratio_absent_in_production — toggles app.debug = False, asserts header absent
  • test_x_compression_ratio_present_in_debug — toggles app.debug = True, asserts header present with valid ratio

Closes #129

- app/compression.py: gzip middleware via after_request hook
- Zero new dependencies (uses Python built-in gzip module)
- Compresses JSON/text responses when client sends Accept-Encoding: gzip
- Skips: small payloads (<512B), already-encoded, non-compressible types
- Vary: Accept-Encoding + X-Compression-Ratio headers
- 8 tests covering all compression scenarios

Closes rohitdash08#129
…t compression test

Fix 1 — Skip direct_passthrough (streaming) responses
  _maybe_compress now checks response.direct_passthrough before calling
  get_data(). Buffering a streaming response in memory defeats the purpose
  of streaming and can exhaust RAM on large payloads. Streaming responses
  should be compressed at the proxy layer (e.g. nginx gzip_proxied).
  Comment in code explains the rationale.

Fix 2 — X-Compression-Ratio gated on app.debug
  The header reveals payload size information that could assist
  compression-based side-channel attacks (e.g. BREACH) in production.
  Now only emitted when current_app.debug is True.

Fix 3 — Replace slow DB-seeding test with mock-based test
  test_large_json_is_compressed previously created 30 expenses via POST
  (slow, fragile, DB-dependent). Replaced with a route registered on the
  test app that returns a large in-memory JSON body directly — no DB
  interaction, runs in milliseconds.

New / updated tests:
  - test_large_json_is_compressed: mock route, no DB, fast
  - test_streaming_response_skipped: asserts get_data() never called on
    direct_passthrough responses (uses MagicMock)
  - test_x_compression_ratio_absent_in_production: debug=False → no header
  - test_x_compression_ratio_present_in_debug: debug=True → header present
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API response compression & payload optimization

1 participant