Skip to content

Commit df4b4fb

Browse files
committed
always do a hash lookup, even for anonymous requests
1 parent 15972db commit df4b4fb

File tree

7 files changed

+94
-33
lines changed

7 files changed

+94
-33
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
3636
* Moved Varnish 4 and 5 configuration files from `resources/config/varnish-4/`
3737
to `resources/config/varnish/`.
3838
* Changed default Varnish version to 5.
39+
* Removed special case for anonymous users in user context behaviour. Varnish
40+
now does a hash lookup for anonymous users as well.
3941

4042
### NGINX
4143

@@ -49,8 +51,8 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
4951
options array for customization.
5052
* Provide a trait for the event dispatching kernel, instead of a base class.
5153
The trait offers both the addSubscriber and the addListener methods.
52-
* By default, no hash header is sent for anonymous users anymore, to sync
53-
behaviour with Varnish behaviour.
54+
* The user context by default does not use a hardcoded hash for anonymous users
55+
but does a hash lookup. You can still configure a hardcoded hash.
5456

5557
### Testing
5658

doc/varnish-configuration.rst

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,23 @@ User Context
203203
Feature: :doc:`user context hashing <user-context>`
204204

205205
The ``fos_user_context.vcl`` needs the ``user_context_hash_url`` subroutine
206-
that sets a URL to the request lookup URL. The default URL is
206+
that sets the URL to do the hash lookup. The default URL is
207207
``/_fos_user_context_hash`` and you can simply include
208208
``resources/config/varnish-[version]/fos_user_context_url.vcl`` in your
209-
configuration to provide this. If you need a different URL, include a custom
210-
file implementing the ``user_context_hash_url`` subroutine.
209+
configuration to provide this. If you need a different URL, write your own
210+
``user_context_hash_url`` subroutine instead.
211+
212+
.. tip::
213+
214+
The provided VCL to fetch the user hash restarts GET/HEAD requests. It
215+
would be more efficient to do the hash lookup request with curl, using the
216+
`curl Varnish plugin`_. If you can enable curl support, the recommended way
217+
is to implement your own VCL to do a curl request for the hash lookup
218+
instead of using the VCL provided here.
219+
220+
Also note that restarting a GET request leads to Varnish discarding the
221+
body of the request. If you have some special case where you have GET
222+
requests with a body, use curl.
211223

212224
To enable this feature, add the following to ``your_varnish.vcl``:
213225

@@ -265,13 +277,6 @@ To enable this feature, add the following to ``your_varnish.vcl``:
265277
Your backend application needs to respond to the ``application/vnd.fos.user-context-hash``
266278
request with :ref:`a proper user hash <return context hash>`.
267279

268-
.. note::
269-
270-
We do not use ``X-Original-Url`` here, as the header will be sent to the
271-
backend and the header has semantical meaning for some applications, which
272-
would lead to problems. For example, the Microsoft IIS rewriting module
273-
uses it, and consequently Symfony also looks into it to support IIS.
274-
275280
.. tip::
276281

277282
The provided VCL assumes that you want the context hash to be cached, so we
@@ -391,5 +396,6 @@ To enable this feature, add the following to ``your_varnish.vcl``:
391396
.. _banning for Varnish 3: https://www.varnish-software.com/book/3/Cache_invalidation.html#banning
392397
.. _ban lurker: https://www.varnish-software.com/blog/ban-lurker
393398
.. _explained in the Varnish documentation: https://www.varnish-cache.org/trac/wiki/VCLExampleRemovingSomeCookies#RemovingallBUTsomecookies
394-
.. _`builtin VCL`: https://www.varnish-cache.org/trac/browser/bin/varnishd/builtin.vcl?rev=4.0
395-
.. _`default VCL`: https://www.varnish-cache.org/trac/browser/bin/varnishd/default.vcl?rev=3.0
399+
.. _curl Varnish plugin: https://github.com/varnish/libvmod-curl
400+
.. _`builtin VCL`: https://github.com/varnishcache/varnish-cache/blob/5.0/bin/varnishd/builtin.vcl
401+
.. _`default VCL`: https://github.com/varnishcache/varnish-cache/blob/3.0/bin/varnishd/default.vcl

resources/config/varnish-3/fos_user_context.vcl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ sub fos_user_context_recv {
1919
}
2020

2121
# Lookup the context hash if there are credentials on the request
22-
# Only do this for cacheable requests. Returning a hash lookup discards the request body.
22+
# Note that the hash lookup discards the request body.
2323
# https://www.varnish-cache.org/trac/ticket/652
2424
if (req.restarts == 0
25-
&& (req.http.cookie || req.http.authorization)
2625
&& (req.request == "GET" || req.request == "HEAD")
2726
) {
2827
# Backup accept header, if set
@@ -31,9 +30,15 @@ sub fos_user_context_recv {
3130
}
3231
set req.http.accept = "application/vnd.fos.user-context-hash";
3332

34-
# Backup original URL
33+
# Backup original URL.
34+
#
35+
# We do not use X-Original-Url here, as the header will be sent to the
36+
# backend and X-Original-Url has semantical meaning for some applications.
37+
# For example, the Microsoft IIS rewriting module uses it, and thus
38+
# frameworks like Symfony also have to handle that header to integrate with IIS.
39+
3540
set req.http.X-Fos-Original-Url = req.url;
36-
41+
3742
call user_context_hash_url;
3843

3944
# Force the lookup, the backend must tell not to cache or vary on all

resources/config/varnish/fos_user_context.vcl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ sub fos_user_context_recv {
1919
}
2020

2121
# Lookup the context hash if there are credentials on the request
22-
# Only do this for cacheable requests. Returning a hash lookup discards the request body.
22+
# Note that the hash lookup discards the request body.
2323
# https://www.varnish-cache.org/trac/ticket/652
2424
if (req.restarts == 0
25-
&& (req.http.cookie || req.http.authorization)
2625
&& (req.method == "GET" || req.method == "HEAD")
2726
) {
2827
# Backup accept header, if set
@@ -31,9 +30,15 @@ sub fos_user_context_recv {
3130
}
3231
set req.http.accept = "application/vnd.fos.user-context-hash";
3332

34-
# Backup original URL
33+
# Backup original URL.
34+
#
35+
# We do not use X-Original-Url here, as the header will be sent to the
36+
# backend and X-Original-Url has semantical meaning for some applications.
37+
# For example, the Microsoft IIS rewriting module uses it, and thus
38+
# frameworks like Symfony also have to handle that header to integrate with IIS.
39+
3540
set req.http.X-Fos-Original-Url = req.url;
36-
41+
3742
call user_context_hash_url;
3843

3944
# Force the lookup, the backend must tell not to cache or vary on all

src/SymfonyCache/UserContextListener.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class UserContextListener implements EventSubscriberInterface
4343
/**
4444
* When creating this listener, you can configure a number of options.
4545
*
46-
* - anonymous_hash: Hash used for anonymous user.
46+
* - anonymous_hash: Hash used for anonymous user. Hash lookup skipped for anonymous if this is set.
4747
* - user_hash_accept_header: Accept header value to be used to request the user hash to the
4848
* backend application. Must match the setup of the backend application.
4949
* - user_hash_header: Name of the header the user context hash will be stored into. Must
@@ -162,7 +162,7 @@ private function getUserHash(HttpKernelInterface $kernel, Request $request)
162162
return $this->userHash;
163163
}
164164

165-
if ($this->isAnonymous($request)) {
165+
if ($this->options['anonymous_hash'] && $this->isAnonymous($request)) {
166166
return $this->userHash = $this->options['anonymous_hash'];
167167
}
168168

tests/Functional/Symfony/EventDispatchingHttpCacheTest.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,11 @@ public function testEventListeners()
5050
$kernel->addSubscriber(new DebugListener());
5151
$kernel->addSubscriber(new PurgeListener());
5252
$kernel->addSubscriber(new RefreshListener());
53-
$kernel->addSubscriber(new UserContextListener());
53+
$kernel->addSubscriber(new UserContextListener([
54+
// avoid having to provide mocking for the hash lookup
55+
// we already test anonymous hash lookup in the UserContextListener unit test
56+
'anonymous_hash' => 'abcdef',
57+
]));
5458

5559
$response = $kernel->handle($request);
5660
$this->assertSame($expectedResponse, $response);

tests/Unit/SymfonyCache/UserContextListenerTest.php

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,60 @@ public function testPassingUserHashNotAllowed($arg, $options)
9898
public function testUserHashAnonymous($arg, $options)
9999
{
100100
$userContextListener = new UserContextListener($arg);
101-
102101
$request = new Request();
103102

104-
$event = new CacheEvent($this->kernel, $request);
105-
106-
$userContextListener->preHandle($event);
107-
$response = $event->getResponse();
108-
109-
$this->assertNull($response);
110103
if ($options['anonymous_hash']) {
104+
$event = new CacheEvent($this->kernel, $request);
105+
$userContextListener->preHandle($event);
106+
111107
$this->assertTrue($request->headers->has($options['user_hash_header']));
112108
$this->assertSame($options['anonymous_hash'], $request->headers->get($options['user_hash_header']));
113109
} else {
114-
$this->assertFalse($request->headers->has($options['user_hash_header']));
110+
$hashRequest = Request::create($options['user_hash_uri'], $options['user_hash_method'], [], [], [], $request->server->all());
111+
$hashRequest->attributes->set('internalRequest', true);
112+
$hashRequest->headers->set('Accept', $options['user_hash_accept_header']);
113+
// Ensure request properties have been filled up.
114+
$hashRequest->getPathInfo();
115+
$hashRequest->getMethod();
116+
117+
$expectedContextHash = 'my_generated_hash';
118+
// Just avoid the response to modify the request object, otherwise it's impossible to test objects equality.
119+
/** @var \Symfony\Component\HttpFoundation\Response|\PHPUnit_Framework_MockObject_MockObject $hashResponse */
120+
$hashResponse = $this->getMockBuilder('\Symfony\Component\HttpFoundation\Response')
121+
->setMethods(['prepare'])
122+
->getMock();
123+
$hashResponse->headers->set($options['user_hash_header'], $expectedContextHash);
124+
125+
$that = $this;
126+
$kernel = $this->kernel
127+
->shouldReceive('handle')
128+
->once()
129+
->with(
130+
\Mockery::on(
131+
function (Request $request) use ($that, $hashRequest) {
132+
// we need to call some methods to get the internal fields initialized
133+
$request->getMethod();
134+
$request->getPathInfo();
135+
$that->assertEquals($hashRequest, $request);
136+
$that->assertCount(0, $request->cookies->all());
137+
138+
return true;
139+
}
140+
)
141+
)
142+
->andReturn($hashResponse)
143+
->getMock();
144+
145+
$event = new CacheEvent($kernel, $request);
146+
$userContextListener->preHandle($event);
147+
148+
$this->assertTrue($request->headers->has($options['user_hash_header']));
149+
$this->assertSame($expectedContextHash, $request->headers->get($options['user_hash_header']));
115150
}
151+
152+
$response = $event->getResponse();
153+
154+
$this->assertNull($response);
116155
}
117156

118157
/**

0 commit comments

Comments
 (0)