Skip to content

Commit e847841

Browse files
committed
229: [GraphQL caching] Add support for queries via HTTP GET
- Address review comments - Add customer validator interface for graphql requests
1 parent 8b43593 commit e847841

File tree

47 files changed

+450
-404
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+450
-404
lines changed

app/code/Magento/GraphQl/Controller/GraphQl.php

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
use Magento\Framework\App\RequestInterface;
1313
use Magento\Framework\App\ResponseInterface;
1414
use Magento\Framework\GraphQl\Exception\ExceptionFormatter;
15-
use Magento\Framework\GraphQl\Exception\GraphQlRequestException;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
1616
use Magento\Framework\GraphQl\Query\QueryProcessor;
1717
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
1818
use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface;
@@ -24,7 +24,6 @@
2424
* Front controller for web API GraphQL area.
2525
*
2626
* @api
27-
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2827
*/
2928
class GraphQl implements FrontControllerInterface
3029
{
@@ -49,12 +48,12 @@ class GraphQl implements FrontControllerInterface
4948
private $queryProcessor;
5049

5150
/**
52-
* @var \Magento\Framework\GraphQl\Exception\ExceptionFormatter
51+
* @var ExceptionFormatter
5352
*/
5453
private $graphQlError;
5554

5655
/**
57-
* @var \Magento\Framework\GraphQl\Query\Resolver\ContextInterface
56+
* @var ContextInterface
5857
*/
5958
private $resolverContext;
6059

@@ -73,8 +72,8 @@ class GraphQl implements FrontControllerInterface
7372
* @param SchemaGeneratorInterface $schemaGenerator
7473
* @param SerializerInterface $jsonSerializer
7574
* @param QueryProcessor $queryProcessor
76-
* @param \Magento\Framework\GraphQl\Exception\ExceptionFormatter $graphQlError
77-
* @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $resolverContext
75+
* @param ExceptionFormatter $graphQlError
76+
* @param ContextInterface $resolverContext
7877
* @param HttpRequestProcessor $requestProcessor
7978
* @param QueryFields $queryFields
8079
*/
@@ -109,30 +108,24 @@ public function dispatch(RequestInterface $request) : ResponseInterface
109108
$statusCode = 200;
110109
try {
111110
/** @var Http $request */
112-
if ($this->isHttpVerbValid($request)) {
113-
$this->requestProcessor->processHeaders($request);
114-
$data = $this->getDataFromRequest($request);
115-
$query = isset($data['query']) ? $data['query'] : '';
116-
$variables = isset($data['variables']) ? $data['variables'] : null;
117-
118-
// We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema
119-
// Temporal coupling is required for performance optimization
120-
$this->queryFields->setQuery($query, $variables);
121-
$schema = $this->schemaGenerator->generate();
122-
123-
$result = $this->queryProcessor->process(
124-
$schema,
125-
$query,
126-
$this->resolverContext,
127-
isset($data['variables']) ? $data['variables'] : []
128-
);
129-
} else {
130-
$errorMessage = __('Mutation requests allowed only for POST requests');
131-
$result['errors'] = [
132-
$this->graphQlError->create(new GraphQlRequestException($errorMessage))
133-
];
134-
$statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS;
135-
}
111+
$this->requestProcessor->validateRequest($request);
112+
$this->requestProcessor->processHeaders($request);
113+
114+
$data = $this->getDataFromRequest($request);
115+
$query = $data['query'] ?? '';
116+
$variables = $data['variables'] ?? null;
117+
118+
// We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema
119+
// Temporal coupling is required for performance optimization
120+
$this->queryFields->setQuery($query, $variables);
121+
$schema = $this->schemaGenerator->generate();
122+
123+
$result = $this->queryProcessor->process(
124+
$schema,
125+
$query,
126+
$this->resolverContext,
127+
$data['variables'] ?? []
128+
);
136129
} catch (\Exception $error) {
137130
$result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : [];
138131
$result['errors'][] = $this->graphQlError->create($error);
@@ -148,37 +141,22 @@ public function dispatch(RequestInterface $request) : ResponseInterface
148141
/**
149142
* Get data from request body or query string
150143
*
151-
* @param Http $request
144+
* @param RequestInterface $request
152145
* @return array
153146
*/
154-
private function getDataFromRequest(Http $request) : array
147+
private function getDataFromRequest(RequestInterface $request) : array
155148
{
149+
/** @var Http $request */
156150
if ($request->isPost()) {
157151
$data = $this->jsonSerializer->unserialize($request->getContent());
158-
} else {
152+
} elseif ($request->isGet()) {
159153
$data = $request->getParams();
160154
$data['variables'] = isset($data['variables']) ?
161155
$this->jsonSerializer->unserialize($data['variables']) : null;
156+
} else {
157+
return [];
162158
}
163159

164160
return $data;
165161
}
166-
167-
/**
168-
* Check if request is using correct verb for query or mutation
169-
*
170-
* @param Http $request
171-
* @return boolean
172-
*/
173-
private function isHttpVerbValid(Http $request)
174-
{
175-
$requestData = $this->getDataFromRequest($request);
176-
$query = $requestData['query'] ?? '';
177-
178-
// The easiest way to determine mutations without additional parsing
179-
if ($request->isSafeMethod() && strpos(trim($query), 'mutation') === 0) {
180-
return false;
181-
}
182-
return true;
183-
}
184162
}

app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php

Lines changed: 0 additions & 37 deletions
This file was deleted.

app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/StoreProcessor.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,10 @@ public function __construct(StoreManagerInterface $storeManager)
3636
* Handle the value of the store and set the scope
3737
*
3838
* @param string $headerValue
39-
* @param HttpRequestInterface $request
4039
* @return void
4140
* @throws GraphQlInputException
42-
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
4341
*/
44-
public function processHeaderValue(string $headerValue, HttpRequestInterface $request) : void
42+
public function processHeaderValue(string $headerValue) : void
4543
{
4644
if ($headerValue) {
4745
$storeCode = ltrim(rtrim($headerValue));

app/code/Magento/GraphQl/Controller/HttpHeaderProcessorInterface.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
namespace Magento\GraphQl\Controller;
99

10-
use Magento\Framework\App\HttpRequestInterface;
11-
1210
/**
1311
* Use this interface to implement a processor for each entry of a header in an HTTP GraphQL request.
1412
*/
@@ -21,8 +19,7 @@ interface HttpHeaderProcessorInterface
2119
* to enforce required headers like "application/json"
2220
*
2321
* @param string $headerValue
24-
* @param HttpRequestInterface $request
2522
* @return void
2623
*/
27-
public function processHeaderValue(string $headerValue, HttpRequestInterface $request) : void;
24+
public function processHeaderValue(string $headerValue) : void;
2825
}

app/code/Magento/GraphQl/Controller/HttpRequestProcessor.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@ class HttpRequestProcessor
1919
*/
2020
private $headerProcessors = [];
2121

22+
/**
23+
* @var HttpRequestValidatorInterface[] array
24+
*/
25+
private $requestValidators = [];
26+
2227
/**
2328
* @param HttpHeaderProcessorInterface[] $graphQlHeaders
29+
* @param HttpRequestValidatorInterface[] $requestValidators
2430
*/
25-
public function __construct(array $graphQlHeaders = [])
31+
public function __construct(array $graphQlHeaders = [], array $requestValidators = [])
2632
{
2733
$this->headerProcessors = $graphQlHeaders;
34+
$this->requestValidators = $requestValidators;
2835
}
2936

3037
/**
@@ -36,7 +43,20 @@ public function __construct(array $graphQlHeaders = [])
3643
public function processHeaders(Http $request) : void
3744
{
3845
foreach ($this->headerProcessors as $headerName => $headerClass) {
39-
$headerClass->processHeaderValue((string)$request->getHeader($headerName), $request);
46+
$headerClass->processHeaderValue((string)$request->getHeader($headerName));
47+
}
48+
}
49+
50+
/**
51+
* Validate HTTP request
52+
*
53+
* @param Http $request
54+
* @return void
55+
*/
56+
public function validateRequest(Http $request) : void
57+
{
58+
foreach ($this->requestValidators as $requestValidator) {
59+
$requestValidator->validate($request);
4060
}
4161
}
4262
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Controller\HttpRequestValidator;
9+
10+
use Magento\Framework\App\HttpRequestInterface;
11+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
12+
use Magento\GraphQl\Controller\HttpRequestValidatorInterface;
13+
14+
/**
15+
* Processes the "Content-Type" header entry
16+
*/
17+
class ContentTypeValidator implements HttpRequestValidatorInterface
18+
{
19+
/**
20+
* Handle the mandatory application/json header
21+
*
22+
* @param HttpRequestInterface $request
23+
* @return void
24+
* @throws GraphQlInputException
25+
*/
26+
public function validate(HttpRequestInterface $request) : void
27+
{
28+
$headerName = 'Content-Type';
29+
$requiredHeaderValue = 'application/json';
30+
31+
$headerValue = (string)$request->getHeader($headerName);
32+
if ($request->isPost()
33+
&& strpos($headerValue, $requiredHeaderValue) === false
34+
) {
35+
throw new GraphQlInputException(
36+
new \Magento\Framework\Phrase('Request content type must be application/json')
37+
);
38+
}
39+
}
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Controller\HttpRequestValidator;
9+
10+
use Magento\Framework\App\HttpRequestInterface;
11+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
12+
use Magento\Framework\App\Request\Http;
13+
use Magento\GraphQl\Controller\HttpRequestValidatorInterface;
14+
15+
/**
16+
* Validator to check HTTP verb for Graphql requests
17+
*/
18+
class HttpVerbValidator implements HttpRequestValidatorInterface
19+
{
20+
/**
21+
* Check if request is using correct verb for query or mutation
22+
*
23+
* @param HttpRequestInterface $request
24+
* @return void
25+
* @throws GraphQlInputException
26+
*/
27+
public function validate(HttpRequestInterface $request) : void
28+
{
29+
/** @var Http $request */
30+
if (false === $request->isPost()) {
31+
$query = $request->getParam('query', '');
32+
// The easiest way to determine mutations without additional parsing
33+
if (strpos(trim($query), 'mutation') === 0) {
34+
throw new GraphQlInputException(
35+
new \Magento\Framework\Phrase('Mutation requests allowed only for POST requests')
36+
);
37+
}
38+
}
39+
}
40+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Controller;
9+
10+
use Magento\Framework\App\HttpRequestInterface;
11+
12+
/**
13+
* Use this interface to implement a validator for a Graphql HTTP requests
14+
*/
15+
interface HttpRequestValidatorInterface
16+
{
17+
/**
18+
* Perform validation of request
19+
*
20+
* @param HttpRequestInterface $request
21+
* @return void
22+
*/
23+
public function validate(HttpRequestInterface $request) : void;
24+
}

app/code/Magento/GraphQl/etc/graphql/di.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
<type name="Magento\GraphQl\Controller\HttpRequestProcessor">
2929
<arguments>
3030
<argument name="graphQlHeaders" xsi:type="array">
31-
<item name="Content-Type" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\ContentTypeProcessor</item>
3231
<item name="Store" xsi:type="object">Magento\GraphQl\Controller\HttpHeaderProcessor\StoreProcessor</item>
3332
</argument>
33+
<argument name="requestValidators" xsi:type="array">
34+
<item name="ContentTypeValidator" xsi:type="object">Magento\GraphQl\Controller\HttpRequestValidator\ContentTypeValidator</item>
35+
<item name="VerbValidator" xsi:type="object">Magento\GraphQl\Controller\HttpRequestValidator\HttpVerbValidator</item>
36+
</argument>
3437
</arguments>
3538
</type>
3639
</config>

0 commit comments

Comments
 (0)