Skip to content

Commit d1fc8d7

Browse files
authored
fix(caching): Support page caching of GET requests (#1197)
1 parent 7f7292a commit d1fc8d7

File tree

6 files changed

+89
-4
lines changed

6 files changed

+89
-4
lines changed

graphql.services.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ services:
6868
class: Drupal\graphql\GraphQL\Execution\ExecutorFactory
6969
arguments: ['@service_container']
7070

71+
# Deny caching of POST requests in the dynamic and standard page cache.
72+
graphql.request_policy.deny_query:
73+
public: false
74+
class: Drupal\graphql\Cache\RequestPolicy\GetOnly
75+
tags:
76+
- { name: page_cache_request_policy }
77+
- { name: dynamic_page_cache_request_policy }
78+
7179
# Upcasting for graphql query request parameters.
7280
graphql.route_enhancer.query:
7381
class: Drupal\graphql\Routing\QueryRouteEnhancer

src/Cache/RequestPolicy/GetOnly.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Drupal\graphql\Cache\RequestPolicy;
4+
5+
use Drupal\Core\PageCache\RequestPolicyInterface;
6+
use Symfony\Component\HttpFoundation\Request;
7+
8+
/**
9+
* Contains a request policy that prevents caching of non GET GraphQL requests.
10+
*/
11+
class GetOnly implements RequestPolicyInterface {
12+
13+
/**
14+
* {@inheritdoc}
15+
*/
16+
public function check(Request $request): ?string {
17+
if ($request->attributes->has('_graphql') && $request->getMethod() !== Request::METHOD_GET) {
18+
return static::DENY;
19+
}
20+
return NULL;
21+
}
22+
23+
}

src/RouteProvider.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public function routes(): array {
6666
])
6767
->addOptions([
6868
'_auth' => $auth,
69-
'no_cache' => TRUE,
7069
'default_url_options' => ['path_processing' => FALSE],
7170
'parameters' => ['graphql_server' => ['type' => 'entity:graphql_server']],
7271
]);

tests/src/Kernel/Framework/ResultTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Drupal\Tests\graphql\Kernel\Framework;
44

55
use Drupal\Tests\graphql\Kernel\GraphQLTestBase;
6+
use Symfony\Component\HttpFoundation\Request;
67

78
/**
89
* Test the whole query result pipeline.
@@ -29,10 +30,14 @@ protected function setUp(): void {
2930
$this->setUpSchema($schema);
3031

3132
$this->mockResolver('Query', 'root', 'test');
33+
34+
$this->configureCachePolicy(900);
3235
}
3336

3437
/**
3538
* Test a simple query result.
39+
*
40+
* @coversClass \Drupal\graphql\Cache\RequestPolicy\DenyPost
3641
*/
3742
public function testQuery(): void {
3843
$result = $this->query('query { root }');
@@ -43,6 +48,24 @@ public function testQuery(): void {
4348
'root' => 'test',
4449
],
4550
], json_decode($result->getContent(), TRUE));
51+
$this->assertTrue($result->isCacheable());
52+
$this->assertEquals('max-age=900, public', $result->headers->get('Cache-Control'));
53+
}
54+
55+
/**
56+
* Test a simple POST query result.
57+
*
58+
* @coversClass \Drupal\graphql\Cache\RequestPolicy\DenyPost
59+
*/
60+
public function testPostQuery(): void {
61+
$result = $this->query('query { root }', NULL, [], NULL, FALSE, Request::METHOD_POST);
62+
$this->assertSame(200, $result->getStatusCode());
63+
$this->assertSame([
64+
'data' => [
65+
'root' => 'test',
66+
],
67+
], json_decode($result->getContent(), TRUE));
68+
$this->assertFalse($result->isCacheable());
4669
}
4770

4871
/**
@@ -67,6 +90,7 @@ public function testBatchedQueries(): void {
6790
],
6891
],
6992
], json_decode($result->getContent(), TRUE));
93+
$this->assertFalse($result->isCacheable());
7094
}
7195

7296
}

tests/src/Kernel/GraphQLTestBase.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace Drupal\Tests\graphql\Kernel;
44

55
use Drupal\Core\Cache\Cache;
6+
use Drupal\Core\PageCache\ChainRequestPolicy;
7+
use Drupal\Core\PageCache\RequestPolicy\NoSessionOpen;
8+
use Drupal\graphql\Cache\RequestPolicy\GetOnly;
69
use Drupal\graphql\GraphQL\ResolverBuilder;
710
use Drupal\KernelTests\KernelTestBase;
811
use Drupal\language\Entity\ConfigurableLanguage;
@@ -87,6 +90,26 @@ protected function setUp(): void {
8790
$this->builder = new ResolverBuilder();
8891
}
8992

93+
/**
94+
* Configures core's cache policy.
95+
*
96+
* Modifies the DefaultRequestPolicy classes, which always add in the
97+
* CommandLineOrUnsafeMethod policy which will always result in DENY in a
98+
* Kernel test because we're running via the command line.
99+
*
100+
* @param int $max_age
101+
* Max age to cache responses.
102+
*/
103+
protected function configureCachePolicy(int $max_age = 900): void {
104+
$this->container->set('dynamic_page_cache_request_policy', (new ChainRequestPolicy())
105+
->addPolicy(new GetOnly()));
106+
$this->container->set('page_cache_request_policy', (new ChainRequestPolicy())
107+
->addPolicy(new NoSessionOpen($this->container->get('session_configuration')))
108+
->addPolicy(new GetOnly()));
109+
// Turn on caching.
110+
$this->config('system.performance')->set('cache.page.max_age', $max_age)->save();
111+
}
112+
90113
/**
91114
* Returns the default cache maximum age for the test.
92115
*/

tests/src/Traits/HttpRequestTrait.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ trait HttpRequestTrait {
3131
* The query extensions.
3232
* @param bool $persisted
3333
* Flag if the query is actually the identifier of a persisted query.
34+
* @param string $method
35+
* Method, GET or POST.
3436
*
3537
* @return \Symfony\Component\HttpFoundation\Response
3638
* The http response object.
3739
*/
38-
protected function query($query, $server = NULL, array $variables = [], array $extensions = NULL, $persisted = FALSE) {
40+
protected function query($query, $server = NULL, array $variables = [], array $extensions = NULL, $persisted = FALSE, string $method = Request::METHOD_GET) {
3941
$server = $server ?: $this->server;
4042
if (!($server instanceof Server)) {
4143
throw new \LogicException('Invalid server.');
@@ -46,10 +48,16 @@ protected function query($query, $server = NULL, array $variables = [], array $e
4648
// If the persisted flag is true, then instead of sending the full query to
4749
// the server we only send the query id.
4850
$query_key = $persisted ? 'queryId' : 'query';
49-
$request = Request::create($endpoint, 'GET', [
51+
$data = [
5052
$query_key => $query,
5153
'variables' => $variables,
52-
] + $extensions);
54+
] + $extensions;
55+
if ($method === Request::METHOD_GET) {
56+
$request = Request::create($endpoint, $method, $data);
57+
}
58+
else {
59+
$request = Request::create($endpoint, $method, [], [], [], [], json_encode($data));
60+
}
5361

5462
return $this->container->get('http_kernel')->handle($request);
5563
}

0 commit comments

Comments
 (0)