Skip to content

fix(server): Compression Plugin (fetch) response invalid status#929

Merged
dinwwwh merged 1 commit into
mainfrom
fix/server/compression-plugin-not-working-with-error-response
Aug 25, 2025
Merged

fix(server): Compression Plugin (fetch) response invalid status#929
dinwwwh merged 1 commit into
mainfrom
fix/server/compression-plugin-not-working-with-error-response

Conversation

@dinwwwh
Copy link
Copy Markdown
Member

@dinwwwh dinwwwh commented Aug 25, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Correctly preserves status and status text on compressed responses.
    • Ensures gzip-compressed error responses include proper Content-Encoding and body content.
    • Improves compatibility of compressed error handling across clients.
  • Tests
    • Added test coverage for gzip-compressed error responses.

@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 25, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
orpc Ready Ready Preview Comment Aug 25, 2025 8:38am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 25, 2025

Walkthrough

Adds a test for gzip-compressing error responses and adjusts CompressionPlugin’s Response construction to explicitly set status, statusText, and headers without spreading the original Response, preserving compression logic.

Changes

Cohort / File(s) Summary of changes
Compression plugin implementation
packages/server/src/adapters/fetch/compression-plugin.ts
Refactors Response creation: no longer spreads original Response; explicitly sets status, statusText, and uses computed compressed headers; retains compression logic and header adjustments (remove content-length, set content-encoding).
Compression plugin tests
packages/server/src/adapters/fetch/compression-plugin.test.ts
Adds test validating gzip compression for error responses (ORPCError), including status, content-encoding header, and decompressed body containing error code.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant CP as CompressionPlugin
  participant H as RPCHandler

  C->>CP: HTTP Request (Accept-Encoding: gzip)
  CP->>H: Invoke handler
  H-->>CP: Throws ORPCError (e.g., UNAUTHORIZED)
  rect rgba(230,240,255,0.5)
    note over CP: Capture error response
    CP->>CP: Serialize error body
    CP->>CP: Gzip compress body
    CP->>C: HTTP Response (status from error, Content-Encoding: gzip)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • unnoq/orpc#869 — Earlier changes to the same CompressionPlugin and its tests; this PR refines Response construction and extends tests introduced there.

Suggested labels

size:L

Poem

A puff of gzip in the night,
Errors bundled good and tight—
I thump my paw: “Status set!”
Headers crisp, the bytes all met.
With whoosh and whirr the packets spin,
A rabbit grins—compressed within. 🐇🌀

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/server/compression-plugin-not-working-with-error-response

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 25, 2025
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @unnoq, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue within the server's Compression Plugin where the HTTP status code of a response, particularly error responses, was not being correctly preserved after compression. The changes ensure that the original status and status text are explicitly carried over to the compressed response, preventing invalid status propagation.

Highlights

  • Fix Response Status Propagation: The "CompressionPlugin" now explicitly copies the "status" and "statusText" from the original response when creating a new compressed response. This addresses a bug where spreading the entire response object could lead to incorrect status codes, especially for error responses.
  • Add Error Response Compression Test: A new test case has been introduced to "compression-plugin.test.ts" to specifically verify that error responses, such as those from "ORPCError", are correctly compressed and that their original HTTP status code is accurately reflected in the final response.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly fixes a bug in the compression plugin where the HTTP status of an error response was not being preserved. The change properly passes the status and statusText when creating the new compressed Response. Additionally, a new test case is introduced to verify the fix and prevent regressions. The changes are solid, and I have one minor suggestion to enhance the new test's robustness.

Comment thread packages/server/src/adapters/fetch/compression-plugin.test.ts
@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Aug 25, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@929

@orpc/client

npm i https://pkg.pr.new/@orpc/client@929

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@929

@orpc/experimental-durable-event-iterator

npm i https://pkg.pr.new/@orpc/experimental-durable-event-iterator@929

@orpc/hey-api

npm i https://pkg.pr.new/@orpc/hey-api@929

@orpc/interop

npm i https://pkg.pr.new/@orpc/interop@929

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@929

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@929

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@929

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@929

@orpc/otel

npm i https://pkg.pr.new/@orpc/otel@929

@orpc/react

npm i https://pkg.pr.new/@orpc/react@929

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@929

@orpc/experimental-react-swr

npm i https://pkg.pr.new/@orpc/experimental-react-swr@929

@orpc/server

npm i https://pkg.pr.new/@orpc/server@929

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@929

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@929

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@929

@orpc/standard-server-aws-lambda

npm i https://pkg.pr.new/@orpc/standard-server-aws-lambda@929

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@929

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@929

@orpc/standard-server-peer

npm i https://pkg.pr.new/@orpc/standard-server-peer@929

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@929

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@929

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@929

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@929

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@929

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@929

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@929

commit: 9430899

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/server/src/adapters/fetch/compression-plugin.ts (1)

114-118: Add Vary: Accept-Encoding to prevent cache misdeliveries.

When the payload is compressed, caches must vary on Accept-Encoding; otherwise, a compressed response can be served to a client that didn’t ask for it. Please ensure Vary includes Accept-Encoding (append if present, set if missing).

Apply this diff within the same block:

       const compressedBody = response.body.pipeThrough(new CompressionStream(encoding))
       const compressedHeaders = new Headers(response.headers)
       compressedHeaders.delete('content-length') // CompressionStream will change the content length
       compressedHeaders.set('content-encoding', encoding)
+      // Ensure proper caching semantics for intermediate caches/proxies
+      const vary = compressedHeaders.get('vary')
+      if (vary) {
+        const tokens = vary.split(',').map(v => v.trim().toLowerCase())
+        if (!tokens.includes('accept-encoding')) {
+          compressedHeaders.set('vary', `${vary}, Accept-Encoding`)
+        }
+      } else {
+        compressedHeaders.set('vary', 'Accept-Encoding')
+      }
🧹 Nitpick comments (3)
packages/server/src/adapters/fetch/compression-plugin.ts (2)

99-106: Honor q-values in Accept-Encoding; avoid choosing encodings with q=0.

The current parsing ignores quality factors and may select gzip even when a client explicitly sets gzip;q=0. Consider parsing q-values and only allowing encodings with q>0 (also respecting * and identity semantics).

Apply this diff to the selection logic:

-      const acceptEncoding = options.request.headers
-        .get('accept-encoding')
-        ?.split(',')
-        .map(enc => enc.trim().split(';')[0]!)
-
-      const encoding = this.encodings.find(enc => acceptEncoding?.includes(enc))
+      const accepted = parseAcceptEncoding(options.request.headers.get('accept-encoding'))
+      const encoding = this.encodings.find(enc => accepted.has(enc))

And add this helper (place it after the other helpers in this file):

function parseAcceptEncoding(
  header: string | null,
): Set<(typeof ORDERED_SUPPORTED_ENCODINGS)[number]> {
  const allowed = new Set<(typeof ORDERED_SUPPORTED_ENCODINGS)[number]>()
  if (!header) return allowed // no header => do not compress

  let starQ: number | undefined
  for (const token of header.split(',')) {
    const [nameRaw, ...params] = token.trim().split(';')
    if (!nameRaw) continue
    const name = nameRaw.toLowerCase()
    let q = 1
    for (const p of params) {
      const [k, v] = p.split('=').map(s => s.trim())
      if (k === 'q' && v) {
        const parsed = Number(v)
        if (!Number.isNaN(parsed)) q = parsed
      }
    }
    if (name === '*') {
      starQ = q
      continue
    }
    if (q > 0 && (name === 'gzip' || name === 'deflate')) {
      allowed.add(name)
    }
  }

  // If '*' is allowed, mark all supported encodings as allowed
  if (starQ !== undefined && starQ > 0) {
    for (const enc of ORDERED_SUPPORTED_ENCODINGS) allowed.add(enc)
  }

  return allowed
}

Follow-up test idea: add a case with accept-encoding: gzip;q=0, deflate;q=1 and expect deflate.


55-68: Event Stream “never compress” policy: confirm intent vs. content-disposition exception.

Docs say Event Iterator responses are never compressed regardless of filter. The current check skips compression only when content-type starts with text/event-stream and there is no content-disposition. If content-disposition is present, compression may still occur, which contradicts the doc.

If the intent is “never compress SSE”, simplify to:

-      if (!hasContentDisposition && contentType?.startsWith('text/event-stream')) {
+      if (contentType?.startsWith('text/event-stream')) {
         return false
       }
packages/server/src/adapters/fetch/compression-plugin.test.ts (1)

622-647: Great coverage for compressed error responses; consider asserting JSON shape, content-length removal, and Vary.

The test validates status propagation and gzip encoding correctly. Two small improvements:

  • Parse the decompressed body as JSON and assert the code field explicitly (stronger than substring).
  • Assert content-length is absent on compressed responses.
  • If you adopt the Vary header suggestion, assert it here too.

Apply this diff:

-    const decompressed = response?.body?.pipeThrough(new DecompressionStream('gzip'))
-    const text = await new Response(decompressed).text()
-    expect(text).toContain(error.code)
-    expect(response?.headers.get('content-encoding')).toBe('gzip')
-    expect(response?.status)).toBe(error.status)
+    const decompressed = response?.body?.pipeThrough(new DecompressionStream('gzip'))
+    const json = await new Response(decompressed!).json()
+    expect(json.code).toBe(error.code)
+    expect(response?.headers.get('content-encoding')).toBe('gzip')
+    expect(response?.headers.get('content-length')).toBeNull()
+    // If Vary is added per plugin suggestion:
+    // expect(response?.headers.get('vary')?.toLowerCase()).toContain('accept-encoding')
+    expect(response?.status).toBe(error.status)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b953b88 and 9430899.

📒 Files selected for processing (2)
  • packages/server/src/adapters/fetch/compression-plugin.test.ts (2 hunks)
  • packages/server/src/adapters/fetch/compression-plugin.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/server/src/adapters/fetch/compression-plugin.test.ts (2)
packages/server/src/builder.ts (1)
  • os (336-352)
packages/server/src/adapters/fetch/compression-plugin.ts (1)
  • CompressionPlugin (46-129)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint
  • GitHub Check: publish-commit
🔇 Additional comments (2)
packages/server/src/adapters/fetch/compression-plugin.ts (1)

121-125: Fix: preserve status and statusText on compressed responses (good change).

Explicitly propagating status and statusText when constructing the new Response fixes the invalid-status bug for error responses (e.g., ORPCError 4xx/5xx). Looks correct and aligns with the new test.

packages/server/src/adapters/fetch/compression-plugin.test.ts (1)

1-1: Importing ORPCError is fine.

Using ORPCError from @orpc/client in this server-side test is acceptable and keeps the test close to real usage.

@dinwwwh dinwwwh merged commit 117b56b into main Aug 25, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant