Skip to content

Commit

Permalink
Adds request and response stacking to the merge header listener (see #…
Browse files Browse the repository at this point in the history
  • Loading branch information
aschempp authored and leofeyer committed Sep 15, 2017
1 parent 3f933aa commit cae98ce
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 34 deletions.
62 changes: 33 additions & 29 deletions src/EventListener/MergeHttpHeadersListener.php
Expand Up @@ -11,13 +11,16 @@
namespace Contao\CoreBundle\EventListener;

use Contao\CoreBundle\Framework\ContaoFrameworkInterface;
use Contao\CoreBundle\HttpKernel\Header\HeaderStorageInterface;
use Contao\CoreBundle\HttpKernel\Header\NativeHeaderStorage;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;

/**
* Adds HTTP headers sent by Contao to the Symfony response.
*
* @author Yanick Witschi <https://github.com/toflar>
* @author Andreas Schempp <https://github.com/aschempp>
*/
class MergeHttpHeadersListener
{
Expand All @@ -27,9 +30,14 @@ class MergeHttpHeadersListener
private $framework;

/**
* @var array|null
* @var HeaderStorageInterface
*/
private $headers;
private $headerStorage;

/**
* @var array
*/
private $headers = [];

/**
* @var array
Expand All @@ -45,13 +53,13 @@ class MergeHttpHeadersListener
/**
* Constructor.
*
* @param ContaoFrameworkInterface $framework
* @param array|null $headers Meant for unit testing only!
* @param ContaoFrameworkInterface $framework
* @param HeaderStorageInterface|null $headerStorage
*/
public function __construct(ContaoFrameworkInterface $framework, array $headers = null)
public function __construct(ContaoFrameworkInterface $framework, HeaderStorageInterface $headerStorage = null)
{
$this->framework = $framework;
$this->headers = $headers;
$this->headerStorage = $headerStorage ?: new NativeHeaderStorage();
}

/**
Expand Down Expand Up @@ -111,45 +119,41 @@ public function onKernelResponse(FilterResponseEvent $event)
return;
}

$event->setResponse($this->mergeHttpHeaders($event->getResponse()));
// Fetch remaining headers and add them to the response
$this->fetchHttpHeaders();
$this->setResponseHeaders($event->getResponse());
}

/**
* Merges the HTTP headers.
* Fetches and stores HTTP headers from PHP.
*/
private function fetchHttpHeaders()
{
$this->headers = array_merge($this->headers, $this->headerStorage->all());
$this->headerStorage->clear();
}

/**
* Sets the response headers.
*
* @param Response $response
*
* @return Response
*/
private function mergeHttpHeaders(Response $response)
private function setResponseHeaders(Response $response)
{
foreach ($this->getHeaders() as $header) {
list($name, $content) = explode(':', $header, 2);
$allowOverrides = [];

if ('cli' !== PHP_SAPI && !headers_sent()) {
header_remove($name);
}
foreach ($this->headers as $header) {
list($name, $content) = explode(':', $header, 2);

$uniqueKey = $this->getUniqueKey($name);

if (in_array($uniqueKey, $this->multiHeaders, true)) {
$response->headers->set($uniqueKey, trim($content), false);
} elseif (!$response->headers->has($uniqueKey)) {
} elseif (isset($allowOverrides[$uniqueKey]) || !$response->headers->has($uniqueKey)) {
$allowOverrides[$uniqueKey] = true;
$response->headers->set($uniqueKey, trim($content));
}
}

return $response;
}

/**
* Returns the headers.
*
* @return array
*/
private function getHeaders()
{
return $this->headers ?: headers_list();
}

/**
Expand Down
38 changes: 38 additions & 0 deletions src/HttpKernel/Header/HeaderStorageInterface.php
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of Contao.
*
* Copyright (c) 2005-2017 Leo Feyer
*
* @license LGPL-3.0+
*/

namespace Contao\CoreBundle\HttpKernel\Header;

/**
* Interface for HTTP header storage.
*
* @author Andreas Schempp <https://github.com/aschempp>
*/
interface HeaderStorageInterface
{
/**
* Returns all headers.
*
* @return array
*/
public function all();

/**
* Adds a header to the storage.
*
* @param string $header
*/
public function add($header);

/**
* Clears the storage.
*/
public function clear();
}
58 changes: 58 additions & 0 deletions src/HttpKernel/Header/MemoryHeaderStorage.php
@@ -0,0 +1,58 @@
<?php

/*
* This file is part of Contao.
*
* Copyright (c) 2005-2017 Leo Feyer
*
* @license LGPL-3.0+
*/

namespace Contao\CoreBundle\HttpKernel\Header;

/**
* Handles HTTP headers in memory (for unit tests).
*
* @author Andreas Schempp <https://github.com/aschempp>
*/
class MemoryHeaderStorage implements HeaderStorageInterface
{
/**
* @var array
*/
private $headers = [];

/**
* Constructor.
*
* @param array $headers
*/
public function __construct(array $headers = [])
{
$this->headers = $headers;
}

/**
* {@inheritdoc}
*/
public function all()
{
return $this->headers;
}

/**
* {@inheritdoc}
*/
public function add($header)
{
$this->headers[] = $header;
}

/**
* {@inheritdoc}
*/
public function clear()
{
$this->headers = [];
}
}
45 changes: 45 additions & 0 deletions src/HttpKernel/Header/NativeHeaderStorage.php
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of Contao.
*
* Copyright (c) 2005-2017 Leo Feyer
*
* @license LGPL-3.0+
*/

namespace Contao\CoreBundle\HttpKernel\Header;

/**
* Handles HTTP headers in PHP's native methods.
*
* @author Andreas Schempp <https://github.com/aschempp>
*/
class NativeHeaderStorage implements HeaderStorageInterface
{
/**
* {@inheritdoc}
*/
public function all()
{
return headers_list();
}

/**
* {@inheritdoc}
*/
public function add($header)
{
header($header);
}

/**
* {@inheritdoc}
*/
public function clear()
{
if ('cli' !== PHP_SAPI && !headers_sent()) {
header_remove();
}
}
}

0 comments on commit cae98ce

Please sign in to comment.