Skip to content

fix: skip compression for 206 and Content-Range responses#300

Merged
uhop merged 3 commits into
koajs:masterfrom
naveentehrpariya:fix-range-response-compression
Jun 16, 2026
Merged

fix: skip compression for 206 and Content-Range responses#300
uhop merged 3 commits into
koajs:masterfrom
naveentehrpariya:fix-range-response-compression

Conversation

@naveentehrpariya

@naveentehrpariya naveentehrpariya commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Problem

koa-compress compresses 206 Partial Content responses even though their Content-Range header describes unencoded byte offsets. After compression the body shrinks but Content-Range still refers to the pre-compression range, so a range-aware client stitching parts together receives corrupted data.

The same class of bug was recently fixed in hono/compress and @fastify/compress.

Fix

Two new guards in the early-exit block (lib/index.js):

// don't compress partial content: Content-Range describes unencoded byte offsets
+ctx.response.status === 206 ||
ctx.response.get('Content-Range') ||

Tests

New Range Responses describe block in __tests__/index.js with two cases:

  1. 206 Partial Content — no Content-Encoding, Content-Range preserved
  2. 200 with Content-Range header — same
npm test  →  53 passed (51 existing + 2 new), 0 failed

Summary by Sourcery

Skip response compression when serving partial or ranged content to avoid corrupting Content-Range semantics.

Bug Fixes:

  • Prevent compression of 206 Partial Content responses so that byte ranges remain consistent with Content-Range headers.
  • Avoid compressing 200 OK responses that include a Content-Range header to ensure clients receive correctly ranged bodies.

Tests:

  • Add tests verifying that 206 Partial Content responses are not compressed and preserve their Content-Range header and body.
  • Add tests ensuring 200 responses with a Content-Range header are not compressed despite gzip being accepted.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented compression for range/partial-content responses by skipping compression when the response is 206 Partial Content, when a Content-Range header is present, or when the content type is multipart/byteranges.
  • Tests
    • Added coverage to confirm range responses are not compressed for both 206 replies and 200 responses that include Content-Range, including proper local server setup/teardown after each test.

A 206 Partial Content response carries a Content-Range header whose
byte offsets refer to the original unencoded payload. Compressing
such a response leaves Content-Range describing the wrong byte range,
corrupting data for clients that reassemble partial responses.

Add two guards to the early-exit block:
- +ctx.response.status === 206
- ctx.response.get('Content-Range')
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fef7dfc9-f531-403b-8709-27b484de7ca8

📥 Commits

Reviewing files that changed from the base of the PR and between 520d7b8 and cb7fd19.

📒 Files selected for processing (1)
  • lib/index.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/index.js

📝 Walkthrough

Walkthrough

This PR extends the Koa compression middleware to skip compression for responses related to byte ranges. The middleware now detects responses with HTTP 206 status (Partial Content) or a Content-Range header and bypasses compression to preserve the range metadata. The implementation adds two guard conditions to the early-exit logic in the middleware, and a test suite validates that both 206 responses and 200 responses carrying Content-Range headers are not compressed.

sequenceDiagram
  participant Client
  participant KoaServer
  participant CompressionMiddleware
  Client->>KoaServer: HTTP request
  KoaServer->>CompressionMiddleware: response context (status, headers, body)
  CompressionMiddleware->>CompressionMiddleware: check status/Content-Range/Content-Type
  alt is 206 or has Content-Range or multipart/byteranges
    CompressionMiddleware->>KoaServer: bypass compression (no Content-Encoding)
  else
    CompressionMiddleware->>KoaServer: apply compression (set Content-Encoding)
  end
  KoaServer->>Client: HTTP response (with/without Content-Encoding)
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: skipping compression for 206 status codes and Content-Range header responses.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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.

@sourcery-ai

sourcery-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds early-exit guards to skip compression for HTTP 206 Partial Content and any responses carrying a Content-Range header, and introduces tests to verify that these range responses are not compressed and preserve headers/body intact.

File-Level Changes

Change Details Files
Skip compression for partial content and range responses to avoid corrupting range-aware clients.
  • Extend early-return conditions in the compression middleware to bail out when response status is 206 Partial Content.
  • Extend early-return conditions in the compression middleware to bail out when the response carries a Content-Range header, regardless of status.
  • Keep the existing compression decision flow intact aside from the new guards, ensuring other behaviors are unchanged.
lib/index.js
Add tests ensuring 206 and Content-Range responses are not compressed and remain consistent.
  • Introduce a new Range Responses describe block to group range-related compression tests.
  • Add a test asserting that 206 Partial Content responses with Content-Range are not compressed and preserve status, headers, and body.
  • Add a test asserting that 200 OK responses carrying a Content-Range header are also not compressed and preserve status, headers, and body.
__tests__/index.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b7fadb4) to head (cb7fd19).

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #300   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         1           
  Lines          175       179    +4     
  Branches        30        33    +3     
=========================================
+ Hits           175       179    +4     

☔ View full report in Codecov by Harness.
📢 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.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In the early-exit condition, consider using ctx.status instead of +ctx.response.status for the 206 check to match the style used elsewhere and avoid unnecessary coercion.
  • You might want to be explicit that any non-empty Content-Range header (regardless of status) disables compression by checking for truthiness (Boolean(ctx.response.get('Content-Range'))) and possibly adding that behavior to the comment for future maintainers.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the early-exit condition, consider using `ctx.status` instead of `+ctx.response.status` for the 206 check to match the style used elsewhere and avoid unnecessary coercion.
- You might want to be explicit that any non-empty `Content-Range` header (regardless of status) disables compression by checking for truthiness (`Boolean(ctx.response.get('Content-Range'))`) and possibly adding that behavior to the comment for future maintainers.

## Individual Comments

### Comment 1
<location path="lib/index.js" line_range="105-107" />
<code_context>
       ctx.compress === false ||
       ctx.request.method === 'HEAD' ||
       emptyBodyStatuses.has(+ctx.response.status) ||
+      // don't compress partial content: Content-Range describes unencoded byte offsets
+      +ctx.response.status === 206 ||
+      ctx.response.get('Content-Range') ||
       ctx.response.get('Content-Encoding') ||
       // forced compression or implied
</code_context>
<issue_to_address>
**suggestion:** Consider also guarding against multipart/byteranges responses, which can carry per-part Content-Range headers without a top-level Content-Range

Right now the guard relies on status 206 and a top-level `Content-Range` header to skip compression. In a `multipart/byteranges` response, the top-level header may be absent while each part still has its own `Content-Range`, and those offsets are defined over the uncompressed body. It would be safer to also bypass compression when `ctx.response.type` (or the `Content-Type` header) indicates `multipart/byteranges`.

```suggestion
      // don't compress partial content: Content-Range describes unencoded byte offsets
      +ctx.response.status === 206 ||
      ctx.response.get('Content-Range') ||
      // multipart/byteranges can carry per-part Content-Range headers over the uncompressed body
      ctx.response.type === 'multipart/byteranges' ||
      /^multipart\/byteranges\b/i.test(ctx.response.get('Content-Type') || '') ||
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread lib/index.js
@uhop uhop self-assigned this Jun 15, 2026
@uhop uhop merged commit f25828a into koajs:master Jun 16, 2026
8 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.

2 participants