feat: pluggable ServeMuxOptions, HTTP marshalers, and zstd compression#88
feat: pluggable ServeMuxOptions, HTTP marshalers, and zstd compression#88
Conversation
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.
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds configurable zstd/gzip HTTP compression, a builder Changes
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Review rate limit: 0/1 reviews remaining, refill in 48 minutes and 23 seconds.Comment |
There was a problem hiding this comment.
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.
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.
There was a problem hiding this comment.
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
gzipHandlervariable name is now misleading because the handler can negotiate both gzip and zstd (vianewHTTPCompressionWrapper). Renaming it to something likecompressionHandler/gatewayHandlerwould 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.
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.
There was a problem hiding this comment.
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.
Summary
RegisterServeMuxOption(opt runtime.ServeMuxOption)and the typed convenienceRegisterHTTPMarshaler(mime, m)in core.initHTTPappends registered options after core's built-ins, so callers can plug in custom marshalers, tune the defaultruntime.JSONPbforruntime.MIMEWildcard, registerWithMiddlewares, override the error handler, etc.interceptors. Docstring spells out which option types are last-write-wins (WithMarshalerOption,WithErrorHandler,WithRoutingErrorHandler,WithIncomingHeaderMatcher) vs additive (WithMiddlewares,WithMetadata,WithForwardResponseOption), and warns that overridingWithIncomingHeaderMatcherdisablesHTTPHeaderPrefixeswiring.gzhttpalready supports zstd natively viaEnableZstd/PreferZstd; pass them through and exposeDisableZstdCompression(defaultfalse) andPreferZstd(defaulttrue). Behavior is backwards compatible — gzip-only clients still get gzip; both flags are no-ops whenDisableHTTPCompression=true.newHTTPCompressionWrapperintocompression.goso wrapper construction is testable without standing up the full gateway.vtprotoCodecand the gRPC wire layer are untouched.Test plan
make buildmake test(race + cover, 80.6% incore/, 75.9% incore/config/)make lint(0 issues,govulncheckclean)make bench— codec/transport benches unchanged territory; no regressionmake docregeneratedREADME.mdandconfig/README.mdserve_mux_options_test.go: marshaler round-trip viaAccept,WithMiddlewaresstacks withspanRouteMiddleware,RegisterHTTPMarshaler("application/json", custom)overrides the built-in JSON marshaler when opted in, reset helper.compression_test.go:Accept-Encodingnegotiation acrossgzip, 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 servicePreStart, thencurl -H 'Accept: application/x-test' …returns the fake-marshaled body.curl -H 'Accept-Encoding: zstd' --compressed …against any sufficiently-large gateway response returnsContent-Encoding: zstd.Follow-up
Docs how-to lands in a separate
docs.coldbrew.cloudPR.Summary by CodeRabbit
New Features
RegisterHTTPMarshalerandRegisterServeMuxOptionfunctions to customize HTTP gateway configurationDisableZstdCompressionandPreferZstdconfiguration options to control HTTP response compression with zstd supportDocumentation