Skip to content

Add support for the HTTP QUERY method#57973

Open
magnogouveia wants to merge 3 commits into
rails:mainfrom
magnogouveia:add-http-query-method
Open

Add support for the HTTP QUERY method#57973
magnogouveia wants to merge 3 commits into
rails:mainfrom
magnogouveia:add-http-query-method

Conversation

@magnogouveia

Copy link
Copy Markdown

Summary

Registers the HTTP QUERY method (RFC 10008, June 2026, Proposed Standard) as a known HTTP method and wires it through Action Pack:

# config/routes.rb
query "search", to: "search#index"
match "filter", to: "search#filter", via: :query

# app code
request.query?                # => true
request.request_method_symbol # => :query

# integration tests
query "/search", params: { filters: { status: "active" } }, as: :json

Motivation

QUERY is registered in the IANA HTTP Method Registry as safe and idempotent. It behaves like GET but carries the query in the request content, for queries too large or structured for a URL query string: complex filters, search endpoints, GraphQL, JSON-RPC.

Rails currently rejects the method before it reaches application code: request_method raises ActionController::UnknownHttpMethod (rendered as a 405), request.get? raises instead of returning false, and match ..., via: :query draws a route that can never match.

Proposed beforehand on the rails-core forum by Muhammet Dilmaç in Proposal: Support for the HTTP QUERY method (RFC 10008); this implements that proposal. Node.js core parses QUERY since 21.7.2, nginx supports it in recent mainline, and Spring has an open PR (spring-projects/spring-framework#34993).

Detail

  • ActionDispatch::Request: RFC10008 constant appended to HTTP_METHODS, plus a query? predicate (Rack::Request::Helpers does not define one).
  • Journey: QUERY added to VerbMatchers::VERBS.
  • Mapper: query verb helper alongside get/post/patch/put/delete.
  • Integration tests: query request helper. No ActionController::TestCase wrapper, mirroring options; process(:index, method: "QUERY") works there.
  • Forgery protection: QUERY requests are exempt, like GET and HEAD. HTML forms cannot issue QUERY requests, and cross-origin QUERY requests always require a CORS preflight (QUERY is not a CORS-safelisted method), so the forgery surface is smaller than GET's. The exemption is a self-contained hunk with its own tests and is easy to drop if a token-required default is preferred.

Body parameter parsing needs no changes: parsing is keyed on Content-Type rather than the request method, so JSON and form-encoded QUERY bodies already parse into params.

No behavior changes for existing applications: QUERY requests previously raised before reaching any application code, so no framework default is needed.

Out of scope here: the Accept-Query response header, content-based cache keys for QUERY responses (RFC 10008 §2.7), resources integration, method override, and a Rack::Request#query? upstream addition.

Note on servers: Puma only allows the eight standard methods by default and answers QUERY with a 501 unless its supported_http_methods option is extended. That is a deployment concern outside Rails; this change makes Rails ready for the method without changing any server behavior.

Checklist

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

RFC 10008 defines QUERY as a safe and idempotent HTTP method that
conveys the query in the request content instead of the URL query
string. It is registered in the IANA HTTP Method Registry (safe: yes,
idempotent: yes) and is intended for queries too large or structured
for a query string: complex filters, search APIs, GraphQL, and
JSON-RPC style endpoints.

This registers QUERY as a known HTTP method in ActionDispatch::Request
and wires it through the framework:

* Add the RFC10008 constant to ActionDispatch::Request::HTTP_METHODS,
  so QUERY requests no longer raise
  ActionController::UnknownHttpMethod.

* Add ActionDispatch::Request#query?, mirroring the other verb
  predicates (Rack::Request::Helpers does not define one for QUERY).

* Add a QUERY verb matcher to Journey (VerbMatchers::VERBS), so
  `via: :query` routes match without the Unknown fallback.

* Add the `query` routing DSL method to
  ActionDispatch::Routing::Mapper::HttpHelpers:

      query "search", to: "search#index"
      match "filter", to: "search#filter", via: :query

* Add the `query` request helper to integration tests:

      query "/search", params: { filters: { q: "video" } }, as: :json

* Exempt QUERY requests from forgery protection, like GET and HEAD.
  HTML forms cannot issue QUERY requests, and cross-origin QUERY
  requests always require a CORS preflight (QUERY is not a
  CORS-safelisted method), so the forgery surface is strictly smaller
  than GET's. This exemption is kept in an isolated hunk so it is easy
  to change if the exemption is not wanted.

Body parameter parsing needs no changes: parsing is keyed on
Content-Type rather than on the request method, so JSON and
form-encoded QUERY bodies already parse into params.

There are no behavior changes for existing applications: QUERY
requests previously raised ActionController::UnknownHttpMethod (405)
before reaching any application code.

Note that the application server must also accept the method. Puma
currently allows only the eight standard methods by default; QUERY can
be enabled with its `supported_http_methods` option.

References:

- RFC 10008: https://www.rfc-editor.org/rfc/rfc10008.html
- IANA HTTP Method Registry:
  https://www.iana.org/assignments/http-methods/http-methods.xhtml
- Rails core forum proposal:
  https://discuss.rubyonrails.org/t/proposal-support-for-the-http-query-method-rfc-10008/91255
@seanpdoyle

seanpdoyle commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Both rack/rack#2474 and puma/puma#3973 are related to this proposal.

While those particular PRs might not be the ones to add support for QUERY to Rack and Puma, others eventually will.

Are there additional considerations to incorporate into the framework to support a fragmented and incremental roll-out of support for architectural dependencies (or other Rack web servers that are not puma)?

@magnogouveia

Copy link
Copy Markdown
Author

Thanks for opening those, @seanpdoyle.

This PR doesn't depend on either of them. Rails reads env["REQUEST_METHOD"] directly, so any server that puts QUERY in the Rack env works with this patch today (I verified end-to-end with Puma's supported_http_methods opt-in). If rack/rack#2474 ships Rack::Request#query?, the predicate added here becomes redundant with the same semantics, and we can delegate to Rack once the required Rack version has it.

On incremental roll-out, each layer already degrades on its own:

  • If the server doesn't support QUERY (Puma by default, CloudFront currently), the request is rejected before it reaches Rack (Puma responds 501). Apps that draw query routes still boot and run normally; those routes are just unreachable until the server allows the method.
  • During the transition, apps can draw match "search" => "search#index", via: [:query, :post] and have clients fall back to POST where some hop doesn't forward QUERY yet. This works with the patch as-is, since QUERY goes through the regular verb-matching machinery.

The CHANGELOG entry documents the Puma opt-in for this reason. I can expand the routing guide with a short note on server support (Puma config, nginx mainline, CloudFront gap) and link the Rack/Puma PRs, either in this PR or as a follow-up.

Two things I'd leave as deliberate follow-ups rather than v1 scope: the Accept-Query response header (RFC 10008 §3) for capability discovery, and content-aware cache keys (§2.7), which no mainstream HTTP cache implements yet.

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