AJAX calls break on Drupal 10.2 when using restore client ip + redirect module

Created on 1 February 2024, 10 months ago
Updated 29 April 2024, 7 months ago

Problem/Motivation

After updating to Drupal 10.2 we found that certain AJAX requests were failing only on environments that were sitting behind Cloudflare.

There error manifested itself by:

  • AJAX requests that returned 200 locally return a 301 redirect, with the X-Drupal-Route-Normalizer header set
  • The redirect's location would include a change to the ajax page state libraries parameter
  • Following that redirect returned a second redirect response which contained 0 as the ajax page state libraries parameter
  • The final response would include garabage files in the add_css ajax response

From debugging I can see that it was triggered by 3 things:

  1. a change in https://www.drupal.org/node/3389367
  2. the middleware from this module running after the ajax page state middleware
  3. the redirect module redirect attempting to normalizing the route.

Steps to reproduce

Enable restore client ip in an environment with cloudflare module enabled, requests coming from cloudflare, and redirect module enabled
Enable AJAX pagination for any view
Observe the AJAX calls redirect twice and then break.

Debugging observations

The initial AJAX request comes in with the parameter compressed - e.g &ajax_page_state[libraries]=eJx9kVFygzAMRC-kwBl6Ek....

Drupal\Core\StackMiddleware\AjaxPageState uncompresses / parses this and sets it to the query object as uncompressed https://git.drupalcode.org/project/drupal/-/blob/10.2.2/core/lib/Drupal/...

Drupal\cloudflare\CloudFlareMiddleware runs after this middleware and calls overrideGlobals on the request object as part of restore the client ip functionality https://git.drupalcode.org/project/cloudflare/-/blob/2.0.0-alpha1/src/Cl...

overrideGlobals sets the QUERY_STRING param on the request server - which at this point unintentionally changes it from compressed to the uncompressed string https://github.com/symfony/symfony/blob/v6.4.3/src/Symfony/Component/Htt...

Drupal\redirect\EventSubscriber\RouteNormalizerRequestSubscriber uses this param to check if the route has been normalized. https://git.drupalcode.org/project/redirect/-/blob/8.x-1.9/src/EventSubs...

As at this point the query string now contains the uncompressed ajax page state, and the original uri contains the compressed state, so the EventSubcruber sets a redirect using the uncompressed state as the query string.

The second request is recieved with uncompressed page state, e.g. ajax_page_state[libraries]=admin_toolbar/toolbar.tree,admin_toolbar/to...

The same flow follows, only this time Drupal\Core\StackMiddleware\AjaxPageState can not parse the param and sets it as empty

Various bad things follow with the incorrect page state.

Proposed resolution

This is not specifically an issue with Cloudflare module, I am just reporting it here as its where the initial bad state is introduced. As far as I know there is nothing specifically wrong with calling overrideGlobals on a request, and the same thing may being triggered by combinations of other modules.

However if there's no issue with what each middleware is doing, then its a case of priority orders needing to change.

I have tested both increasing the cloud flare priority to 501, and also separately decreasing AjaxPageState down to 299 and both resolved the issue and I didn't notice any unintentional side effects. This would still leave you open to the bug if any other code / modules call overrideGlobals.

Remaining tasks

User interface changes

API changes

Data model changes

🐛 Bug report
Status

Fixed

Version

2.0

Component

Code

Created by

🇳🇿New Zealand ericgsmith

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024