Problem/Motivation

Discovered by @dawehner at #2752325-11: Automatically provide HEAD support when a REST resource supports GET.

\Drupal\Core\PageCache\DefaultRequestPolicy uses \Drupal\Core\PageCache\RequestPolicy\CommandLineOrUnsafeMethod which uses \Symfony\Component\HttpFoundation\Request::isMethodSafe(), which considers both HTTP HEAD and GET safe. This is correct.

However, the Page Cache only caches based on URL and format.

Hence a cached HEAD response will be served to GET requests and vice versa.

Proposed resolution

Fix this.

Remaining tasks

  1. Fix.
  2. Tests

User interface changes

None.

API changes

None.

Data model changes

None.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Issue summary: View changes
Status: Active » Needs review
Issue tags: +Novice, +php-novice, +DevDaysMilan
FileSize
627 bytes

This still needs tests.

dawehner’s picture

+1 for this fix

Wim Leers’s picture

Status: Needs review » Needs work

Great!

NW for tests.

The last submitted patch, 2: page_cache_vary_by_method-2753741-2.patch, failed testing.

dawehner’s picture

Urgs, we hardcode the cache ID in there :( \Drupal\system\Tests\Cache\PageCacheTagsTestBase::verifyPageCache

Wim Leers’s picture

That's easy to fix. Just add 'GET' to $cid_parts.

znerol’s picture

Back in D7 the page cache expected that a full response is always generated. The response body was removed upon delivery. This is a bit like gzip encoding works: Always store the response encoded but if a client does not understand that, then unzip upon delivery.

If I'm not completely mistaken, this is also how the Symfony HttpCache works regarding HEAD requests. So I'm not completely convinced that this is a problem.

znerol’s picture

In fact HttpFoundation\Response::prepare() completely takes care of that.

znerol’s picture

I checked what Varnish does in this case. Upon a cache-miss, Varnish fetches from the backend using GET, even if the client did a HEAD request.

I think that application code (such as REST resources) should not care at all about whether a request was GET or HEAD, but instead always deliver the whole response. Symfony will take care of removing the body upon prepare() if necessary. Also if the site is behind a reverse proxy, we can assume that HEAD requests will not reach to the backend at all. Therefore there is no point in differentiate between GET and HEAD on controller level.

I'm tempted to close this as won't fix. What do you think @Wim?

Wim Leers’s picture

I think it'd then be valuable to convert this issue to a test-only issue to encode what you wrote in a test, to ensure that expectation is never broken.

dawehner’s picture

Symfony will take care of removing the body upon prepare() if necessary.

Does it? I tried it on the command line and saw an actual response body, do we miss something by bypassing HTTP kernel?

I think it'd then be valuable to convert this issue to a test-only issue to encode what you wrote in a test, to ensure that expectation is never broken.

+1

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.

Wim Leers’s picture

Title: Page Cache caches all safe HTTP methods (GET+HEAD), but generates the same cache ID for either! » Page Cache caches all safe HTTP methods (GET+HEAD), but generates the same cache ID for either: add test coverage to prove this is correct
Category: Bug report » Task
Priority: Major » Normal
Status: Needs work » Needs review
Issue tags: -Needs tests, -Novice, -php-novice
FileSize
2.6 KB

Does it?

It does.

Here's the requested test coverage.

Status: Needs review » Needs work

The last submitted patch, 14: 2753741-14.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review

DrupalCI fail.

00:23:07.242 FATAL: Unable to delete script file /tmp/hudson5428935582779370971.sh
00:23:07.243 hudson.remoting.RequestAbortedException: java.io.IOException: Connection aborted: org.jenkinsci.remoting.nio.NioChannelHub$MonoNioTransport@4706f286[name=9727c389-7a16-46da-9b56-ceb571011d62]

Retesting.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

I'm still confused about what I've seen, but well, we have a test which proves the opposite.

alexpott’s picture

It works for me... not sure what @dawehner is seeing...

curl --head -i http://drupal8alt.dev

HTTP/1.1 200 OK
Date: Fri, 14 Oct 2016 00:00:30 GMT
Server: Apache/2.4.18 (Unix) PHP/7.0.7 LibreSSL/2.2.7
X-Content-Type-Options: nosniff
X-Powered-By: PHP/7.0.7
Cache-Control: must-revalidate, no-cache, private
X-Drupal-Dynamic-Cache: HIT
X-UA-Compatible: IE=edge
Content-language: en
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Drupal-Cache-Tags: block_view config:block.block.stark_admin config:block.block.stark_branding config:block.block.stark_local_actions config:block.block.stark_local_tasks config:block.block.stark_login config:block.block.stark_messages config:block.block.stark_page_title config:block.block.stark_tools config:block_list config:system.menu.admin config:system.menu.tools config:system.site config:user.role.anonymous config:user.settings rendered
X-Drupal-Cache-Contexts: languages:language_interface route theme url.path url.query_args user.permissions user.roles:authenticated
Expires: Sun, 19 Nov 1978 05:00:00 GMT
X-Generator: Drupal 8 (https://www.drupal.org)
X-Drupal-Cache: MISS
Content-Type: text/html; charset=UTF-8

curl -i http://drupal8alt.dev

HTTP/1.1 200 OK
Date: Fri, 14 Oct 2016 00:00:34 GMT
Server: Apache/2.4.18 (Unix) PHP/7.0.7 LibreSSL/2.2.7
X-Content-Type-Options: nosniff
X-Powered-By: PHP/7.0.7
Cache-Control: must-revalidate, no-cache, private
X-Drupal-Dynamic-Cache: HIT
X-UA-Compatible: IE=edge
Content-language: en
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Drupal-Cache-Tags: block_view config:block.block.stark_admin config:block.block.stark_branding config:block.block.stark_local_actions config:block.block.stark_local_tasks config:block.block.stark_login config:block.block.stark_messages config:block.block.stark_page_title config:block.block.stark_tools config:block_list config:system.menu.admin config:system.menu.tools config:system.site config:user.role.anonymous config:user.settings rendered
X-Drupal-Cache-Contexts: languages:language_interface route theme url.path url.query_args user.permissions user.roles:authenticated
Expires: Sun, 19 Nov 1978 05:00:00 GMT
X-Generator: Drupal 8 (https://www.drupal.org)
X-Drupal-Cache: MISS
Vary: Accept-Encoding
Content-Length: 5866
Content-Type: text/html; charset=UTF-8
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed cae82ac to 8.3.x and 781e927 to 8.2.x. Thanks!

  • alexpott committed cae82ac on 8.3.x
    Issue #2753741 by Wim Leers, dawehner, znerol: Page Cache caches all...

  • alexpott committed 781e927 on 8.2.x
    Issue #2753741 by Wim Leers, dawehner, znerol: Page Cache caches all...

Status: Fixed » Closed (fixed)

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