Problem/Motivation

RouteProvider::getRouteCollectionForRequest() currently caches routes by Path and Query. This works in most cases, but we hit an edge case in Domain Access, where we cannot modify the homepage context per domain because the route cache ignores the host.

The render cache does not have this issue.

Remaining tasks

Change the $cid generation method.

Old:

    // Cache both the system path as well as route parameters and matching
    // routes.
    $cid = 'route:' . $request->getPathInfo() . ':' .  $request->getQueryString();

Proposed:

    // Cache based on the uri to ensure proper matching of all paths.
    $cid = 'route:' . $request->getUri();

User interface changes

None.

API changes

$cid changes for the RouteProvider. Internal only. Not an API.

Comments

agentrickard created an issue. See original summary.

agentrickard’s picture

Status: Active » Needs review
StatusFileSize
new901 bytes

And a patch.

bjaxelsen’s picture

+1

Status: Needs review » Needs work

The last submitted patch, 2: 2662196-route-cache-1.patch, failed testing.

Crell’s picture

Based on the test failure error message, I think it's likely there's a test that's hard-coded to the old logic that we will need to update.

Otherwise, I am +1 on this change.

The one possible downside is that, if domain access is installed, it will result in a considerable increase in the number of cache entries for the routing cache. However, that is the goal here because we DO need the cache to be different in at least some of those cases. For non-DA sites I don't expect it to have any measurable impact.

agentrickard’s picture

Historically (since D5), the page cache has always used full URL, which is why we never had this problem before.

Looking at the test fail now.

agentrickard’s picture

Status: Needs work » Needs review
StatusFileSize
new3.35 KB

And a new patch.

agentrickard’s picture

Note that handling path aliases is not required for this change. That's a separate issue.

Crell’s picture

Status: Needs review » Reviewed & tested by the community

I'm good here. Thanks, Ken!

catch’s picture

Status: Reviewed & tested by the community » Needs review

This will also affect sites with mixed http/https, http://www and http://, and domain language negotiation won't it?

Looks like a good case for #2551419: Abstract RenderCache into a separate service that is capable of cache redirects in a non-render array-specific way to me - then we'd be able to modify the cid only on sites that actually need it. Not saying we necessarily need to postpone on that, but I'd like to see a @todo and confirm whether the other cases I mentioned would also be affected at least.

berdir’s picture

Yes, I guess this will result in more cache variants on some sites, but not redirecting www/non-www is a bad practice anyway and I guess we'll see more and more https only sites that redirect http://, with letsencrypt and so on.

I guess this is OK.

However, note that I think it should be relatively easy to solve in domain.module itself. It just needs to make sure that the response has the url.site context, which would result in exactly the same behavior. I guess we could argue that since Drupal by default doesn't care about the domain, it would be the responsibiity of a module that changes that to ensure the right cache contexs?

dawehner’s picture

+++ b/core/modules/system/src/Tests/Routing/RouteProviderTest.php
@@ -454,31 +454,31 @@ public function testRouteCaching() {
-    $path = '/path/add/one';
+    $path = 'http://example.com/path/add/one';
     $request = Request::create($path, 'GET');

This is a bit confusing. Is this really how a request would look like. Would the the path in Request::create() be actually absolute?

bjaxelsen’s picture

@berdir:

I am not sure I can follow how to do that, in \Drupal\Core\Routing\RouteProvider\RouteProvider::getRouteCollectionForRequest() the cache ID is hard-coded to

$cid = 'route:' . $request->getPathInfo() . ':' . $request->getQueryString();

so I don't see how we can inject the url.site context here? If there would be an API for appending an optional context (like the url.site), the problem should be pretty straightforward to fix in the Domain module, but currently I don't see a way for other modules to add the context to the route cache.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

Yeah, sorry, I thought this was about dynamic page cache somehow... misread. Which is the next thing you might run into once this is fixed. But *that* should be fixed by making sure that cache context is added.

Re #12, that seems OK to me. Request::create() uses parse_url() and if there's a host, then it sets the server variables accordingly.

Based on that, setting back to RTBC.

wim leers’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +D8 cacheability

The one possible downside is that, if domain access is installed, it will result in a considerable increase in the number of cache entries for the routing cache.

99% of sites are not accessible over multiple domains, so the actual cost (or rather, reduction in efficiency) will remain lower.

Looks like a good case for #2551419: Abstract RenderCache into a separate service that is capable of cache redirects in a non-render array-specific way

+1

And just generally a good reason for this to use cache contexts instead, which are Drupal's standardized mechanism for this. https://www.drupal.org/developing/api/8/cache/contexts

so I don't see how we can inject the url.site context here? If there would be an API for appending an optional context (like the url.site), the problem should be pretty straightforward to fix in the Domain module, but currently I don't see a way for other modules to add the context to the route cache.

Exactly! The key problem here is that it's hardcoded and cannot be manipulated, even though routing itself can be manipulated. That is the bug.


So given #10 + #13: I think the ability to change the cache contexts by which routes are cached would be the better solution. Executing an alter hook on every request is probably too expensive. But what about a container parameter? Just like we have:

parameters:
  renderer.config:
    required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']

we could also have:

parameters:
  routing:
    cache_contexts: ['url.path', 'url.query_args']

And then the domain module could alter that container parameter when it is installed.

Thoughts?

dawehner’s picture

Sounds like a flexible enough solution. Just to be clear, we should name it something which includes routing and inbound path processing,which is the actual thing, which is dynamic here, not the routing itself.

Crell’s picture

Could domain manipulate that list via code, rather than manually? Right now it documents adding a required render cache context manually. I'd hate to make Domain need to have even more manual configuration. (If it can add both automatically, so much the better.)

wim leers’s picture

Could domain manipulate that list via code, rather than manually?

Of course, using \Drupal\Core\DependencyInjection\ServiceModifierInterface. That's why I said And then the domain module could alter that container parameter when it is installed..

agentrickard’s picture

For context, Domain uses the url.site cache context (since it mirrors our condition, we don't need a separate context).

So long as that cache context can be addressed within the route (and ideally, the path alias) cache handler, we should be fine.

If anyone is interesting in looking at the WIP, it's here -- https://github.com/agentrickard/domain/tree/8.x-1.x/domain_config -- and mentions the manual configuration Crell mentions in #17.

wim leers’s picture

#19 :) :) :)

OT: the README in that branch/repo says: Edit the required_cache_contexts to add domain-awareness, as indicated by the url.site parameter. — that should not even be necessary. See #18. Look at \Drupal\language\LanguageServiceProvider::alter() for an example.

agentrickard’s picture

Noted. Thanks.

agentrickard’s picture

Also OT, but here's the original issue that led to the patch -- https://github.com/agentrickard/domain/issues/166

bjaxelsen’s picture

#15

But what about a container parameter? [...] And then the domain module could alter that container parameter when it is installed.

That sounds sweet to me :-)

bjaxelsen’s picture

I've implemented the \Drupal\Core\DependencyInjection\ServiceModifierInterface as suggested by Wim in #18.

If we run into conflicts with other modules that want to interact with the routing, we might need to sort out a different approach.

Have a look here:
https://github.com/agentrickard/domain/pull/209

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

gapple’s picture

StatusFileSize
new3.35 KB

Got a build failure applying the last patch to 8.1.x, so here's a re-roll

wim leers’s picture

Priority: Normal » Major
Issue tags: +Contributed project blocker

This blocks @agentrickard's domain module.

agentrickard’s picture

I do have a workaround, but it requires overriding the core RouteProvider.

dawehner’s picture

For core itself for now IMHO using the entire URI is quite good. Of course its not the ideal solution, but its fixing an existing bug out there.

gapple’s picture

I didn't document very well why I started applying this patch to a multilingual site, but if I recall correctly this issue also can cause problems for domain based language negotiation, where the same path alias exists in multiple languages.

bjaxelsen’s picture

+1

I think the core change to cache route by URI (patch in #26) is a more simple approach rather than letting Domain implement \Drupal\Core\DependencyInjection\ServiceModifierInterface - even though we can work it out in both ways.

R.Muilwijk’s picture

Stumbled on this issue when trying to change the front page using the front page like this:

services:
  frontpage.overrider:
    class: \Drupal\qeado_dashboard\FrontpageOverrides
    tags:
      - {name: config.factory.override, priority: 5}
<?php
/**
 * @file
 * Contains \Drupal\qeado_dashboard\FrontpageOverrides.
 */

namespace Drupal\qeado_dashboard;

use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Config\ConfigFactoryOverrideInterface;
use Drupal\Core\Config\StorageInterface;

/**
 * Provides a override for the frontpage uri based on property count.
 */
class FrontpageOverrides implements ConfigFactoryOverrideInterface {
  public function loadOverrides($names) {
    $overrides = array();

    if (in_array('system.site', $names) && \Drupal::currentUser()->isAuthenticated()) {
      $query = \Drupal::entityQuery('property');
        $query->condition('uid', \Drupal::currentUser()->id());
        $properties = $query->execute();

      // Count properties.
      if ($properties) {
        if (count($properties) == 1) {
          $property_id = current($properties);
          $overrides['system.site'] = [
            'page' => ['front' => '/property/' . $property_id],
          ];
        }
        else {
          $overrides['system.site'] = [
            'page' => ['front' => '/user/' . \Drupal::currentUser()->id()],
          ];
        }
      }
    }

    return $overrides;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheSuffix() {
    return 'FrontpageOverrider-' . \Drupal::currentUser()->id();
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheableMetadata($name) {
    $metadata = new CacheableMetadata();

    if ($name == 'system.site') {
      $metadata->setCacheContexts(['user']);
      $metadata->setCacheTags(['property_list']);
    }

    return $metadata;
  }

  /**
   * {@inheritdoc}
   */
  public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
    return NULL;
  }

}

Also got stuck because of getRouteCollectionForRequest() for trying this method. I think the fix for me would be to create a KernelEvents::REQUEST subscriber which changes the $request->getPathInfo(). Or a dummy frontpage controller which sends back the proper frontpage.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dawehner’s picture

Added some related issue: #2769421: Allow Inbound PathProcessor's to specify that they should not be cached

The original patch is a really simple solution to the problem. Maybe we should add a follow up with the suggestion of @Wim Leers in #2662196-15: Cache route by Uri and not just Query+Path, but make it easier for domain module.

Let's also correct the used tag. As @agentrickard said, this is not really blocking domain module from working, its just not nice :)

The last submitted patch, 7: 2662196-route-cache-7.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 26: 2662196-route-cache-26.patch, failed testing.

gapple’s picture

Status: Needs work » Needs review
StatusFileSize
new3.37 KB

Re-roll for 8.2.x

Only change appears to be the path of the test class.

gapple’s picture

Created #2799543: Cache route by configured cache contexts as a followup feature request against 8.3.x

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

We have a separate issue to provide a more generic solution, for now this fixes a concrete problem in the wild.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Still not convinced that the quick fix here justifies potentially double the cache entries for mixed http/https sites.

dawehner’s picture

Well, then let's do it properly and mark this issue as duplicate?

catch’s picture

Status: Needs review » Closed (duplicate)
Related issues: +#2799543: Cache route by configured cache contexts

Fair enough, if someone feels really strongly we should do this, they can always re-open.