Skip to content

pandaproxy: fix unmatched-route 404 body shape#30417

Open
nguyen-andrew wants to merge 3 commits intoredpanda-data:devfrom
nguyen-andrew:fix-sr-404
Open

pandaproxy: fix unmatched-route 404 body shape#30417
nguyen-andrew wants to merge 3 commits intoredpanda-data:devfrom
nguyen-andrew:fix-sr-404

Conversation

@nguyen-andrew
Copy link
Copy Markdown
Member

@nguyen-andrew nguyen-andrew commented May 7, 2026

When a request hits an HTTP path/method that isn't registered with
pandaproxy::server (REST proxy or schema registry), Redpanda returns
Seastar's built-in 404 body {"message": "Not found", "code": 404}.
Schema-registry clients parse a different envelope (error_code rather
than code), so any client logic that inspects 404 bodies breaks on
this shape.

Real pandaproxy handlers already emit the
{"error_code": <int>, "message": "..."} envelope via
pandaproxy::json::error_body. The fix adds a default handler that
emits the same shape and registers it on Seastar's routes table in
pandaproxy::server::start(). Both REST proxy and schema registry
share pandaproxy::server, so the fix lands on both subsystems with a
single registration site.

The three commits:

  1. pandaproxy: add default_404_handler for unmatched-route 404 body shape
    New default_404_handler class plus C++ unit test exercising the
    body-shape contract in isolation.
  2. pandaproxy: register default_404_handler in server::start()
    Wires the handler so Seastar dispatches to it for any unmatched
    (method, path) combination. Owned via unique_ptr because
    Seastar's add_default_handler does not take ownership (per
    seastar/include/seastar/http/routes.hh:136).
  3. tests/rptest: cover unmatched-route 404 shape on REST proxy and SR
    Ducktape assertions on both ports (8082 and 8081) lock in the wire
    format end-to-end and prove the wiring at the cluster level.

Jira: https://redpandadata.atlassian.net/browse/CORE-16251

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v26.1.x
  • v25.3.x
  • v25.2.x

Release Notes

Bug Fixes

  • REST proxy and schema registry now return
    {"error_code": 404, "message": "..."} for unmatched routes,
    matching the envelope clients parse.

Copilot AI review requested due to automatic review settings May 7, 2026 23:26
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

This PR standardizes the JSON error envelope returned for unmatched routes (unregistered path/method) in Pandaproxy-backed HTTP servers (REST proxy and Schema Registry) by registering a Seastar default handler that emits Pandaproxy’s canonical {"error_code": <int>, "message": "..."} body.

Changes:

  • Add a default_404_handler that returns the Pandaproxy/SR-compatible 404 envelope for unmatched routes.
  • Register the default handler in pandaproxy::server::start() so it applies to both REST proxy and Schema Registry.
  • Add unit + rptest coverage to lock the 404 body shape end-to-end for both ports.

Reviewed changes

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

Show a summary per file
File Description
tests/rptest/tests/schema_registry_test.py Adds an rptest asserting SR unmatched-route 404 returns error_code envelope.
tests/rptest/tests/pandaproxy_test.py Adds an rptest asserting REST proxy unmatched-route 404 returns error_code envelope.
src/v/pandaproxy/test/default_404_handler.cc Adds a C++ unit test validating the default handler’s JSON envelope.
src/v/pandaproxy/test/BUILD Registers the new C++ unit test target.
src/v/pandaproxy/server.h Stores the default handler to ensure it outlives Seastar routes.
src/v/pandaproxy/server.cc Registers the default handler on the Seastar routes table during startup.
src/v/pandaproxy/default_404_handler.h Introduces the default 404 handler implementation.
src/v/pandaproxy/BUILD Exposes the new handler header in the pandaproxy library.

Comment thread src/v/pandaproxy/default_404_handler.h Outdated
Comment thread src/v/pandaproxy/default_404_handler.h Outdated
The handler emits the {"error_code": 404, "message": "HTTP 404 Not
Found"} envelope that schema registry and REST proxy clients expect,
rather than Seastar's fallback {"message": "Not found", "code": 404}.
Real pandaproxy handlers already emit this shape via
pandaproxy::json::error_body; this class lets the unmatched-route path
do the same once it is wired into pandaproxy::server in a follow-up
commit.

The handler is added with a unit test before any wiring so its body
contract is exercised in isolation. Wiring lands separately so the
server-side change is visible on its own commit.
Wires the new handler so Seastar's routes table dispatches to it for
any (method, path) combination that no registered route matches.
Without this, Seastar's built-in routes::handle() short-circuits with
its own JSON body that uses {"code": 404} instead of the
{"error_code": 404} shape SR clients expect, breaking 404-fallback
paths in external SR client serializers.

The handler is owned by pandaproxy::server via unique_ptr because
Seastar's add_default_handler does not take ownership (per
seastar/include/seastar/http/routes.hh:136). Declared before _server
so it is destroyed after, giving the strongest lifetime guarantee for
the routes table's raw pointer to it.

Both REST proxy and schema registry use pandaproxy::server, so this
fixes the body shape for both subsystems with a single registration.
Asserts that GET on a bogus path returns HTTP 404 with the
{"error_code": 404, "message": "..."} envelope clients expect, rather
than Seastar's fallback {"message": "Not found", "code": 404}. Two
assertions, one per port:

- Schema registry (8081) in schema_registry_test.py
- REST proxy (8082) in pandaproxy_test.py

Both subsystems use pandaproxy::server, so the fix in
pandaproxy::server::start() lands on both. Locking in both wire
formats end-to-end is the test that proves the default_404_handler
registration works for both consumers. Removing the
add_default_handler line would regress the body shape and fail both
assertions in CI.
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