-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Split X-Magento-Tags HTTP header into multiple headers #12831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
namespace Magento\PageCache\Model\HTTP\Header; | ||
|
||
use Zend\Http\Header\MultipleHeaderInterface; | ||
use Zend\Http\Header\GenericHeader; | ||
use Zend\Http\Header\HeaderValue; | ||
use Zend\Http\Header\Exception; | ||
|
||
class XMagentoTags implements MultipleHeaderInterface | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
protected $value; | ||
|
||
/** | ||
* XMagentoTags constructor. | ||
* | ||
* @param string|null $value | ||
*/ | ||
public function __construct($value = null) | ||
{ | ||
if ($value) { | ||
HeaderValue::assertValid($value); | ||
$this->value = $value; | ||
} | ||
} | ||
|
||
/** | ||
* Create X-Magento-Tags header from a given header line | ||
* | ||
* @param string $headerLine The header line to parse. | ||
* | ||
* @return self | ||
* @throws Exception\InvalidArgumentException If the name field in the given header line does not match. | ||
*/ | ||
public static function fromString($headerLine) | ||
{ | ||
list($name, $value) = GenericHeader::splitHeaderLine($headerLine); | ||
|
||
// check to ensure proper header type for this factory | ||
if (strtolower($name) !== 'x-magento-tags') { | ||
throw new Exception\InvalidArgumentException( | ||
sprintf( | ||
'Invalid header line for X-Magento-Tags string: "%s"', | ||
$name | ||
) | ||
); | ||
} | ||
|
||
// @todo implementation details | ||
$header = new static($value); | ||
|
||
return $header; | ||
} | ||
|
||
/** | ||
* Cast multiple header objects to a single string header | ||
* | ||
* @param array $headers | ||
* @throws Exception\InvalidArgumentException | ||
* @return string | ||
*/ | ||
public function toStringMultipleHeaders(array $headers) | ||
{ | ||
$name = $this->getFieldName(); | ||
$values = array($this->getFieldValue()); | ||
foreach ($headers as $header) { | ||
if (!$header instanceof static) { | ||
throw new Exception\InvalidArgumentException( | ||
'This method toStringMultipleHeaders was expecting an array of headers of the same type' | ||
); | ||
} | ||
$values[] = $header->getFieldValue(); | ||
} | ||
|
||
return $name . ': ' . implode(',', $values) . "\r\n"; | ||
} | ||
|
||
/** | ||
* Get the header name | ||
* | ||
* @return string | ||
*/ | ||
public function getFieldName() | ||
{ | ||
return 'X-Magento-Tags'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
/** | ||
* Get the header value | ||
* | ||
* @return string | ||
*/ | ||
public function getFieldValue() | ||
{ | ||
return $this->value; | ||
} | ||
|
||
/** | ||
* Return the header as a string | ||
* | ||
* @return string | ||
*/ | ||
public function toString() | ||
{ | ||
return 'X-Magento-Tags: ' . $this->getFieldValue(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
|
||
namespace Magento\PageCache\Model\HTTP\PhpEnvironment; | ||
|
||
use Magento\Framework\HTTP\PhpEnvironment\Response as Subject; | ||
use Magento\PageCache\Model\Config; | ||
use Zend\Http\HeaderLoader; | ||
use Magento\PageCache\Model\HTTP\Header\XMagentoTags; | ||
|
||
class ResponsePlugin | ||
{ | ||
/** | ||
* Approximately 8kb in length | ||
* | ||
* @var int | ||
*/ | ||
private $requestSize = 8000; | ||
|
||
/** | ||
* PageCache configuration | ||
* | ||
* @var Config | ||
*/ | ||
private $config; | ||
|
||
/** | ||
* ResponsePlugin constructor. | ||
* | ||
* @param Config $config | ||
*/ | ||
public function __construct( | ||
Config $config | ||
) { | ||
$this->config = $config; | ||
} | ||
|
||
/** | ||
* Special case for handling X-Magento-Tags header | ||
* splits very long header into multiple headers | ||
* | ||
* @param Subject $subject | ||
* @param \Closure $proceed | ||
* @param string $name | ||
* @param string $value | ||
* @param bool $replace | ||
* | ||
* @return Subject|mixed | ||
*/ | ||
public function aroundSetHeader(Subject $subject, \Closure $proceed, $name, $value, $replace = false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not recommended to add new around plugins, as it has a greater performance penalty, specifically, in such a critical part of the system. |
||
{ | ||
//if varnish isn't enabled, don't do anything | ||
if (!$this->config->isEnabled() || $this->config->getType() != Config::VARNISH) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While the issue described in the Pull Request only relates to Varnish, this header is also used by Builtin Implementation. Would it make sense to make it more generic? |
||
return $proceed($name, $value, $replace); | ||
} | ||
|
||
$this->addHeaderToStaticMap(); | ||
|
||
if ($name == 'X-Magento-Tags') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are already plugins in the system which set |
||
$headerLength = 0; | ||
$value = (string)$value; | ||
$tags = explode(',', $value); | ||
|
||
$newTags = []; | ||
foreach ($tags as $tag) { | ||
if ($headerLength + strlen($tag) > $this->requestSize - count($tags) - 1) { | ||
$tagString = implode(',', $tags); | ||
$subject->getHeaders()->addHeaderLine($name, $tagString); | ||
$newTags = []; | ||
$headerLength = 0; | ||
} | ||
$headerLength += strlen($tag); | ||
$newTags[] = $tag; | ||
} | ||
|
||
return $subject; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Different plugins, for example solving similar issue for other header types may be theoretically broken in this way. Does it make sense to tune the actual Response implementation instead? |
||
} | ||
|
||
return $proceed($name, $value, $replace); | ||
} | ||
|
||
/** | ||
* Add X-Magento-Tags header to HeaderLoader static map | ||
*/ | ||
private function addHeaderToStaticMap() | ||
{ | ||
HeaderLoader::addStaticMap( | ||
[ | ||
'xmagentotags' => XMagentoTags::class, | ||
] | ||
); | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method seems to be copied from the Zend's default implementation of
\Zend\Http\Header\GenericMultiHeader::toStringMultipleHeaders
which could make it more challenging to support in the future