Skip to content

Commit fce51e9

Browse files
authored
PWA-1049: Prerender.io support for UPWARD-PHP (#3)
* PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP * PWA-1049: Prerender.io support for UPWARD-PHP
1 parent b4ff16f commit fce51e9

File tree

11 files changed

+790
-16
lines changed

11 files changed

+790
-16
lines changed

Controller/Upward.php

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
use Magento\Framework\App\RequestInterface;
1212
use Magento\Framework\App\ResponseInterface;
1313
use Magento\Framework\HTTP\PhpEnvironment\Response;
14-
use Zend\Http\Response\Stream;
14+
use Laminas\Http\Response\Stream;
15+
use Magento\UpwardConnector\Model\Prerender;
1516

1617
class Upward implements FrontControllerInterface
1718
{
@@ -26,13 +27,24 @@ class Upward implements FrontControllerInterface
2627
private $upwardFactory;
2728

2829
/**
30+
* @var Prerender
31+
*/
32+
private $prerender;
33+
34+
/**
35+
* Upward constructor.
2936
* @param Response $response
3037
* @param UpwardControllerFactory $upwardFactory
38+
* @param Prerender $prerender
3139
*/
32-
public function __construct(Response $response, UpwardControllerFactory $upwardFactory)
33-
{
40+
public function __construct(
41+
Response $response,
42+
UpwardControllerFactory $upwardFactory,
43+
Prerender $prerender
44+
) {
3445
$this->response = $response;
3546
$this->upwardFactory = $upwardFactory;
47+
$this->prerender = $prerender;
3648
}
3749

3850
/**
@@ -43,8 +55,14 @@ public function __construct(Response $response, UpwardControllerFactory $upwardF
4355
*/
4456
public function dispatch(RequestInterface $request)
4557
{
46-
/** @var \Zend\Http\Response $upwardResponse */
47-
$upwardResponse = $this->upwardFactory->create($request)();
58+
$prerenderedResponse = null;
59+
if ($this->prerender->shouldShowPrerenderedPage($request)) {
60+
/** @var \Laminas\Http\Response $prerenderedResponse */
61+
$prerenderedResponse = $this->prerender->getPrerenderedPageResponse($request);
62+
}
63+
64+
/** @var \Laminas\Http\Response $upwardResponse */
65+
$upwardResponse = $prerenderedResponse ? $prerenderedResponse : $this->upwardFactory->create($request)();
4866
$content = $upwardResponse instanceof Stream ? $upwardResponse->getBody() : $upwardResponse->getContent();
4967

5068
$this->response->setHeaders($upwardResponse->getHeaders());

Model/Prerender.php

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
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\UpwardConnector\Model;
9+
10+
use Magento\Framework\App\RequestInterface;
11+
use Magento\Framework\App\Config\ScopeConfigInterface;
12+
use Magento\Store\Model\ScopeInterface;
13+
use Magento\Framework\Escaper;
14+
use Psr\Log\LoggerInterface;
15+
use Laminas\Http\Exception;
16+
use Laminas\Http\ClientFactory;
17+
use Laminas\Http\Client\Adapter\Curl;
18+
19+
class Prerender
20+
{
21+
const XML_PATH_WEB_UPWARD_PRERENDER = 'web/upward/prerender_enabled';
22+
const XML_PATH_WEB_UPWARD_PRERENDER_TOKEN = 'web/upward/prerender_token';
23+
const XML_PATH_WEB_UPWARD_PRERENDER_URL = 'web/upward/prerender_url';
24+
const XML_PATH_WEB_UPWARD_PRERENDER_CRAWLERS = 'web/upward/prerender_crawlers';
25+
const XML_PATH_WEB_UPWARD_PRERENDER_ALLOWED_LIST = 'web/upward/prerender_allowed_list';
26+
const XML_PATH_WEB_UPWARD_PRERENDER_BLOCKED_LIST = 'web/upward/prerender_blocked_list';
27+
28+
/**
29+
* @var ScopeConfigInterface
30+
*/
31+
private $config;
32+
33+
/**
34+
* @var ClientFactory
35+
*/
36+
private $clientFactory;
37+
38+
/**
39+
* @var LoggerInterface
40+
*/
41+
private $logger;
42+
43+
/**
44+
* @var Escaper
45+
*/
46+
private $escaper;
47+
48+
/**
49+
* @param ScopeConfigInterface $config
50+
* @param ClientFactory $clientFactory
51+
* @param LoggerInterface $logger
52+
* @param Escaper $escaper
53+
*/
54+
public function __construct(
55+
ScopeConfigInterface $config,
56+
ClientFactory $clientFactory,
57+
LoggerInterface $logger,
58+
Escaper $escaper
59+
) {
60+
$this->config = $config;
61+
$this->clientFactory = $clientFactory;
62+
$this->logger = $logger;
63+
$this->escaper = $escaper;
64+
}
65+
66+
/**
67+
* Send the request to prerender services to get prerendered html content.
68+
*
69+
* @param RequestInterface $request
70+
* @return \Laminas\Http\Response|false
71+
*/
72+
public function getPrerenderedPageResponse(RequestInterface $request)
73+
{
74+
$headers = [
75+
'User-Agent' => $request->getServer('HTTP_USER_AGENT'),
76+
];
77+
if ($this->getPrerenderToken()) {
78+
$headers['X-Prerender-Token'] = $this->getPrerenderToken();
79+
}
80+
81+
$protocol = $request->isSecure() ? 'https' : 'http';
82+
83+
$host = $request->getHttpHost();
84+
$path = $request->getRequestUri();
85+
// Fix '//' 404 error
86+
if ($path === '/') {
87+
$path = '';
88+
}
89+
90+
$url = $this->escaper->escapeUrl($this->getPrerenderUrl() . $protocol . '://' . $host . $path);
91+
92+
$config = [
93+
'adapter' => Curl::class,
94+
'curloptions' => [
95+
CURLOPT_MAXREDIRS => 10,
96+
CURLOPT_TIMEOUT => 50,
97+
CURLOPT_FOLLOWLOCATION => true
98+
]
99+
];
100+
101+
try {
102+
$client = $this->clientFactory->create();
103+
$client->setUri($url);
104+
$client->setOptions($config);
105+
$client->setHeaders($headers);
106+
107+
return $client->send();
108+
} catch (Exception\RuntimeException | Exception\InvalidArgumentException $e) {
109+
$this->logger->critical($e);
110+
111+
return false;
112+
}
113+
}
114+
115+
/**
116+
* Check if resources should be prerendered.
117+
*
118+
* @param RequestInterface $request
119+
* @return bool
120+
*/
121+
public function shouldShowPrerenderedPage(RequestInterface $request)
122+
{
123+
if (!$this->getPrerenderUrl() ||
124+
!$this->config->getValue(static::XML_PATH_WEB_UPWARD_PRERENDER, scopeInterface::SCOPE_STORE)
125+
) {
126+
return false;
127+
}
128+
$requestUri = $request->getRequestUri();
129+
$referer = $request->getHeader('referer');
130+
131+
if (!$request->isGet()) {
132+
return false;
133+
}
134+
135+
if (!$this->isInAllowedList($requestUri)) {
136+
return false;
137+
}
138+
139+
// we also check for a blocked referer
140+
$uris = array_filter([$requestUri, ($referer ? $referer : '')]);
141+
if ($this->isInBlockedList($uris)) {
142+
return false;
143+
}
144+
145+
if (!$this->isCrawlerUserAgent($request)) {
146+
return false;
147+
}
148+
149+
return true;
150+
}
151+
152+
/**
153+
* Get prerender token from configuration.
154+
*
155+
* @return string|null
156+
*/
157+
private function getPrerenderToken()
158+
{
159+
return $this->config->getValue(static::XML_PATH_WEB_UPWARD_PRERENDER_TOKEN);
160+
}
161+
162+
/**
163+
* Get prerender url from configuration.
164+
*
165+
* @return string|null
166+
*/
167+
private function getPrerenderUrl()
168+
{
169+
return $this->config->getValue(static::XML_PATH_WEB_UPWARD_PRERENDER_URL);
170+
}
171+
172+
/**
173+
* Check if user agent is crawler bot.
174+
*
175+
* @param RequestInterface $request
176+
* @return bool
177+
*/
178+
private function isCrawlerUserAgent(RequestInterface $request)
179+
{
180+
$userAgent = strtolower($request->getServer('HTTP_USER_AGENT'));
181+
if (!$userAgent) {
182+
return false;
183+
}
184+
185+
$bufferAgent = $request->getServer('X-BUFFERBOT');
186+
187+
// prerender if _escaped_fragment_ is in the query string
188+
if ($bufferAgent || $request->getQuery('_escaped_fragment_')) {
189+
return true;
190+
}
191+
192+
$crawlerUserAgents = $this->getList(
193+
(string) $this->config->getValue(self::XML_PATH_WEB_UPWARD_PRERENDER_CRAWLERS)
194+
);
195+
196+
foreach ($crawlerUserAgents as $crawlerUserAgent) {
197+
if (strpos(strtolower($userAgent), strtolower($crawlerUserAgent)) !== false) {
198+
return true;
199+
}
200+
}
201+
202+
return false;
203+
}
204+
205+
/**
206+
* Checks if uri is in allowed list.
207+
*
208+
* @param string $requestUri
209+
* @return bool
210+
*/
211+
private function isInAllowedList(string $requestUri)
212+
{
213+
$allowedList = $this->getList(
214+
(string) $this->config->getValue(self::XML_PATH_WEB_UPWARD_PRERENDER_ALLOWED_LIST)
215+
);
216+
217+
return empty($allowedList) || $this->isListed([$requestUri], $allowedList);
218+
}
219+
220+
/**
221+
* Checks if uri is in blocked list.
222+
*
223+
* @param array $uris
224+
* @return bool
225+
*/
226+
private function isInBlockedList(array $uris)
227+
{
228+
$blockedList = $this->getList(
229+
(string) $this->config->getValue(self::XML_PATH_WEB_UPWARD_PRERENDER_BLOCKED_LIST)
230+
);
231+
232+
return !empty($blockedList) && $this->isListed($uris, $blockedList);
233+
}
234+
235+
/**
236+
* Transforms string from configuration to the array.
237+
*
238+
* @param string $list
239+
* @return string[] array
240+
*/
241+
private function getList(string $list)
242+
{
243+
return array_filter(
244+
array_map(
245+
'trim',
246+
preg_split("/(\r\n|\n)/", $list ?? '')
247+
)
248+
);
249+
}
250+
251+
/**
252+
* Checks if provided uri is listed in the list from configuration.
253+
*
254+
* @param array $needles
255+
* @param array $list
256+
* @return bool
257+
*/
258+
private function isListed(array $needles, array $list)
259+
{
260+
foreach ($list as $pattern) {
261+
foreach ($needles as $needle) {
262+
if (fnmatch($pattern, $needle)) {
263+
return true;
264+
}
265+
}
266+
}
267+
268+
return false;
269+
}
270+
}

Model/System/Message/Security.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@
66

77
namespace Magento\UpwardConnector\Model\System\Message;
88

9-
use Magento\Store\Model\Store;
10-
119
class Security implements \Magento\Framework\Notification\MessageInterface
1210
{
1311
/**
14-
* @return string
12+
* @inheritdoc
1513
*/
1614
public function getIdentity()
1715
{
1816
return 'security';
1917
}
2018

2119
/**
22-
* @return bool
20+
* @inheritdoc
2321
*/
2422
public function isDisplayed()
2523
{
2624
return false;
2725
}
2826

2927
/**
28+
* Retrieve message text
29+
*
3030
* @return \Magento\Framework\Phrase
3131
*/
3232
public function getText()
@@ -35,7 +35,7 @@ public function getText()
3535
}
3636

3737
/**
38-
* @return int
38+
* @inheritdoc
3939
*/
4040
public function getSeverity()
4141
{

Plugin/Magento/Framework/App/AreaList.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@ public function __construct(ScopeConfigInterface $scopeConfig)
3131
}
3232

3333
/**
34+
* Add pwa area code by front name
35+
*
3436
* @param \Magento\Framework\App\AreaList $subject
3537
* @param string|null $result
3638
* @param string $frontName
3739
*
3840
* @return string|null
41+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
3942
*/
4043
public function afterGetCodeByFrontName(
4144
\Magento\Framework\App\AreaList $subject,

0 commit comments

Comments
 (0)