Problem/Motivation

The existing constraints cover request context well (path, route, HTTP method, query string, domain) but none of them inspect the HTTP request headers.
A growing number of Drupal sites operate in a decoupled or headless architecture where the same backend serves multiple consumers: a web frontend, a mobile app, a kiosk, a third-party integration, etc. These consumers often identify themselves through a custom request header (e.g. X-Client-Type: mobile-app) or through standard headers like Accept or Accept-Language.
In this context, some consumers trigger significantly heavier server-side processing than others. For example, a mobile app may request a JSON:API resource that resolves deep paragraph nesting and several entity references in a single call, while the same route hit by the web frontend only partially hydrates the same data. The HTTP method is identical (GET), the route is the same, but the memory pressure is very different depending on who is calling.
None of the existing constraints can express "apply this policy only when the request comes from consumer X", without coupling to a path or a query string that the consumer may not control.

Proposed resolution

Create a new constraint plugin based on the value of an HTTP request header.
Configuration form:

  • Header name (text field, e.g. X-Consumer, Accept)
  • Match mode (select, one of: contains / starts with / ends with / matches exactly / regex)
  • Header value (text field)

The `evaluate()` method would:

  1. Fetch the current request from the request_stack service
  2. Read the configured header from the request
  3. Compare its value against the configured string using the selected match mode:
    • contains — str_contains($value, $pattern)
    • starts with — str_starts_with($value, $pattern)
    • ends with — str_ends_with($value, $pattern)
    • matches exactly — strict string equality
    • regex — preg_match($pattern, $value)
  4. Return TRUE if the header is present and the value matches

The negate option (already built into the base plugin) handles the inverse case out of the box: e.g. "apply to all requests except those coming from the internal crawler".

Use case example

A decoupled Drupal site exposes a JSON:API endpoint consumed by several mobile clients (X-Consumer: mobile-android, X-Consumer: mobile-ios). Both trigger deep paragraph resolution that hits the 128M default limit.
A single policy covers both clients without listing each variant explicitly:

  • Header name: X-Consumer
  • Match mode: starts with
  • Value: mobile
  • Memory: 256M

A second policy targets only a legacy integration client that sends X-Consumer: legacy-erp-v1:

  • Header name: X-Consumer
  • Match mode: ends with
  • Value: erp-v1
  • Memory: 512M

This avoids duplicating policies per client variant and keeps the config readable and maintainable as new consumers are onboarded.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

vbouchet created an issue. See original summary.

vbouchet’s picture

Status: Active » Fixed

Implemented

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.