Skip to content

feat: pluggable ServeMuxOptions, HTTP marshalers, and zstd compression#88

Merged
ankurs merged 3 commits intomainfrom
feat/serve-mux-options-zstd
May 2, 2026
Merged

feat: pluggable ServeMuxOptions, HTTP marshalers, and zstd compression#88
ankurs merged 3 commits intomainfrom
feat/serve-mux-options-zstd

Conversation

@ankurs
Copy link
Copy Markdown
Member

@ankurs ankurs commented May 1, 2026

Summary

  • New exported RegisterServeMuxOption(opt runtime.ServeMuxOption) and the typed convenience RegisterHTTPMarshaler(mime, m) in core. initHTTP appends registered options after core's built-ins, so callers can plug in custom marshalers, tune the default runtime.JSONPb for runtime.MIMEWildcard, register WithMiddlewares, override the error handler, etc.
  • Init-only pattern (no mutex) matching the existing config-function convention used by interceptors. Docstring spells out which option types are last-write-wins (WithMarshalerOption, WithErrorHandler, WithRoutingErrorHandler, WithIncomingHeaderMatcher) vs additive (WithMiddlewares, WithMetadata, WithForwardResponseOption), and warns that overriding WithIncomingHeaderMatcher disables HTTPHeaderPrefixes wiring.
  • Companion change: zstd HTTP compression. gzhttp already supports zstd natively via EnableZstd / PreferZstd; pass them through and expose DisableZstdCompression (default false) and PreferZstd (default true). Behavior is backwards compatible — gzip-only clients still get gzip; both flags are no-ops when DisableHTTPCompression=true.
  • Extracted newHTTPCompressionWrapper into compression.go so wrapper construction is testable without standing up the full gateway.

vtprotoCodec and the gRPC wire layer are untouched.

Test plan

  • make build
  • make test (race + cover, 80.6% in core/, 75.9% in core/config/)
  • make lint (0 issues, govulncheck clean)
  • make bench — codec/transport benches unchanged territory; no regression
  • make doc regenerated README.md and config/README.md
  • New serve_mux_options_test.go: marshaler round-trip via Accept, WithMiddlewares stacks with spanRouteMiddleware, RegisterHTTPMarshaler("application/json", custom) overrides the built-in JSON marshaler when opted in, reset helper.
  • New compression_test.go: Accept-Encoding negotiation across gzip, zstd (zstd preferred), gzip-only, zstd-disabled fallback, no-Accept-Encoding (no compression), PreferZstd=false (gzip wins on equal q), and below-MinSize (uncompressed).

Manual smoke

Wire core.RegisterHTTPMarshaler("application/x-test", &fakeMarshaler{}) from a service PreStart, then curl -H 'Accept: application/x-test' … returns the fake-marshaled body. curl -H 'Accept-Encoding: zstd' --compressed … against any sufficiently-large gateway response returns Content-Encoding: zstd.

Follow-up

Docs how-to lands in a separate docs.coldbrew.cloud PR.

Summary by CodeRabbit

  • New Features

    • Added RegisterHTTPMarshaler and RegisterServeMuxOption functions to customize HTTP gateway configuration
    • Added DisableZstdCompression and PreferZstd configuration options to control HTTP response compression with zstd support
  • Documentation

    • Updated API reference with new registration functions and compression configuration options

Expose two new exported functions for HTTP gateway extension:

- RegisterServeMuxOption(opt runtime.ServeMuxOption) — appends arbitrary
  grpc-gateway options that initHTTP passes to runtime.NewServeMux.
- RegisterHTTPMarshaler(mime, m) — typed convenience wrapping
  runtime.WithMarshalerOption.

Registered options are applied AFTER core's built-ins (incoming-header
matcher derived from HTTPHeaderPrefixes, application/proto and
application/protobuf marshalers, spanRouteMiddleware), so:

- Last-write-wins options (WithMarshalerOption for a given MIME,
  WithErrorHandler, WithRoutingErrorHandler, WithIncomingHeaderMatcher)
  let users intentionally override core's defaults.
- Additive options (WithMiddlewares, WithMetadata,
  WithForwardResponseOption) stack with core's.

The docstring warns that overriding WithIncomingHeaderMatcher disables
the HTTPHeaderPrefixes wiring.

Init-only pattern (no mutex), matching the existing config-function
convention used by the interceptors package.

Companion change: zstd HTTP compression. gzhttp already supports zstd
natively via EnableZstd / PreferZstd; pass those options through and
expose two new flags:

- DisableZstdCompression (default false) — set true to fall back to
  gzip-only.
- PreferZstd (default true) — when both gzip and zstd are advertised in
  Accept-Encoding, prefer zstd.

Both are no-ops when DisableHTTPCompression is true. Behavior is
backwards compatible: gzip-only clients continue to receive gzip.

Extracted newHTTPCompressionWrapper into compression.go so the wrapper
construction is testable without standing up the full gateway.

Tests cover: marshaler round-trip via Accept, middleware stacking,
JSON-marshaler override via re-registration, the test reset helper,
and Accept-Encoding negotiation across zstd/gzip/identity, including
the below-MinSize case.
Copilot AI review requested due to automatic review settings May 1, 2026 17:32
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Warning

Rate limit exceeded

@ankurs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 23 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b70b02f8-04d3-496a-907e-8d43ab1d73d7

📥 Commits

Reviewing files that changed from the base of the PR and between d9df6cc and be0c403.

📒 Files selected for processing (2)
  • serve_mux_options.go
  • serve_mux_options_test.go
📝 Walkthrough

Walkthrough

Adds configurable zstd/gzip HTTP compression, a builder newHTTPCompressionWrapper, and a startup-time registration API for runtime.ServeMuxOption and HTTP marshalers (RegisterServeMuxOption, RegisterHTTPMarshaler). Tests exercise compression negotiation and serve-mux option registration/precedence.

Changes

Cohort / File(s) Summary
Configuration
config/config.go, config/README.md
Added Config.DisableZstdCompression bool and Config.PreferZstd bool with env tags and docs; updated config doc anchors.
Compression Infrastructure
compression.go, compression_test.go, core.go
Added newHTTPCompressionWrapper(cfg) to build gzip/zstd wrappers honoring min-size, disable and preference flags; updated core to use the builder; added tests for encoding negotiation and min-size behavior.
Serve Mux Options Registration
serve_mux_options.go, serve_mux_options_test.go
Added package-level registration for runtime.ServeMuxOption, RegisterServeMuxOption, convenience RegisterHTTPMarshaler, and test helpers (registeredServeMuxOptions, resetServeMuxOptionsForTest); tests verify stacking, precedence, and reset.
Documentation
README.md
Documented RegisterHTTPMarshaler and RegisterServeMuxOption and updated several function anchor links.

Sequence Diagram(s)

sequenceDiagram
    participant Startup as Startup
    participant Registry as ServeMuxRegistry
    participant Core as Core.Init
    participant ServeMux as runtime.ServeMux
    participant Handler as AppHandler
    participant Compression as CompressionWrapper
    participant Client as Client

    rect rgba(200,200,255,0.5)
    Startup->>Registry: RegisterServeMuxOption(...)\nRegisterHTTPMarshaler(mime, marshaler)
    Registry-->>Core: registeredServeMuxOptions() (at init)
    end

    rect rgba(200,255,200,0.5)
    Client->>ServeMux: HTTP request (Accept-Encoding, Accept)
    ServeMux->>Compression: wrap handler using newHTTPCompressionWrapper(cfg)
    Compression->>Handler: invoke underlying handler
    Handler-->>Compression: response body
    Compression-->>ServeMux: set Content-Encoding (gzip/zstd) or none
    ServeMux-->>Client: HTTP response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • vestor
  • fajran
  • kevinjom

Poem

🐰
I hop where headers dance and sway,
gzip skims, zstd hops away,
Marshalers line the gateway door,
Options stacked—then one hops more,
A crunchy response, bright as day.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% 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 'feat: pluggable ServeMuxOptions, HTTP marshalers, and zstd compression' directly and comprehensively summarizes the three main features added in this changeset: pluggable ServeMux options, HTTP marshaler registration, and zstd compression support.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/serve-mux-options-zstd

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
Review rate limit: 0/1 reviews remaining, refill in 48 minutes and 23 seconds.

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds extension points to the HTTP grpc-gateway setup and expands HTTP response compression support, enabling services to plug in additional runtime.ServeMuxOptions (including custom marshalers/middleware/error handlers) and to negotiate zstd alongside gzip.

Changes:

  • Introduces exported registration hooks for grpc-gateway ServeMuxOptions and HTTP marshalers, applied after core’s built-in gateway options.
  • Adds zstd HTTP compression configuration knobs and factors wrapper construction into a dedicated helper for testability.
  • Updates generated/readme docs and adds targeted tests for ServeMux option behavior and compression negotiation.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
serve_mux_options.go Adds global registry + exported helpers to register ServeMux options and marshalers.
serve_mux_options_test.go Verifies marshaler selection/override and middleware stacking behavior.
core.go Applies registered ServeMux options during gateway init; switches to testable compression wrapper factory.
compression.go Extracts gzhttp wrapper construction with gzip/zstd negotiation flags.
compression_test.go Tests gzip/zstd negotiation, disabling, preference, and min-size behavior.
config/config.go Adds DisableZstdCompression and PreferZstd config fields.
config/README.md Regenerates config documentation to include new fields/line links.
README.md Regenerates exported API docs to include new registration functions.
Comments suppressed due to low confidence (1)

core.go:531

  • The handler variable is still named gzipHandler, but it now wraps gzip and optional zstd compression. Renaming it to something encoding-agnostic (e.g., compressionHandler) would make the control flow clearer and avoid future confusion while reading initHTTP.
	promHandler := promhttp.Handler()
	gzipHandler := http.Handler(tracingWrapper(mux))
	if !c.config.DisableHTTPCompression {
		wrapper, err := newHTTPCompressionWrapper(c.config)
		if err != nil {
			return nil, fmt.Errorf("failed to create compression handler: %w", err)
		}
		gzipHandler = wrapper(gzipHandler)
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread serve_mux_options.go Outdated
Addresses Copilot review feedback on PR #88. The unexported helper
returned the package-level slice directly, which let initHTTP's append
potentially mutate the shared backing array if future callers introduced
spare capacity. Returning a fresh copy is cheap (init-time, called once
per Run) and removes the foot-gun without any caller-visible change.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

core.go:531

  • The gzipHandler variable name is now misleading because the handler can negotiate both gzip and zstd (via newHTTPCompressionWrapper). Renaming it to something like compressionHandler/gatewayHandler would make the code easier to follow when reading the HTTP setup.
	promHandler := promhttp.Handler()
	gzipHandler := http.Handler(tracingWrapper(mux))
	if !c.config.DisableHTTPCompression {
		wrapper, err := newHTTPCompressionWrapper(c.config)
		if err != nil {
			return nil, fmt.Errorf("failed to create compression handler: %w", err)
		}
		gzipHandler = wrapper(gzipHandler)
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread serve_mux_options.go Outdated
Addresses Copilot review feedback on PR #88. The test-only reset helper
was living in serve_mux_options.go and shipping in production builds.
Moved into serve_mux_options_test.go alongside its only callers so it
no longer affects the runtime binary.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ankurs ankurs merged commit 2964bf5 into main May 2, 2026
11 checks passed
@ankurs ankurs deleted the feat/serve-mux-options-zstd branch May 2, 2026 11:54
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.

2 participants