Commit 8b83aa08 authored by effulgentsia's avatar effulgentsia

Issue #2493911 by dawehner, larowlan, damiankloip, hussainweb, jibran,...

Issue #2493911 by dawehner, larowlan, damiankloip, hussainweb, jibran, cilefen, benjy, iMiksu, mtdowling: Update guzzle, goutte and mink-goutte-driver to the latest release
parent cc34748c
......@@ -22,7 +22,7 @@
"twig/twig": "1.18.*",
"doctrine/common": "~2.4.2",
"doctrine/annotations": "1.2.*",
"guzzlehttp/guzzle": "~5.0",
"guzzlehttp/guzzle": "dev-master#1879fbe853b0c64d109e369c7aeff09849e62d1e",
"symfony-cmf/routing": "1.3.*",
"easyrdf/easyrdf": "0.9.*",
"phpunit/phpunit": "4.6.*",
......@@ -31,8 +31,8 @@
"stack/builder": "1.0.*",
"egulias/email-validator": "1.2.*",
"behat/mink": "~1.6",
"behat/mink-goutte-driver": "~1.1",
"fabpot/goutte": "^2.0.3",
"behat/mink-goutte-driver": "dev-master#cc5ce119b5a8e06662f634b35967aff0b0c7dfdd",
"fabpot/goutte": "~3.1",
"masterminds/html5": "~2.1",
"symfony/psr-http-message-bridge": "v0.2",
"zendframework/zend-diactoros": "1.1.0"
......
This diff is collapsed.
......@@ -2240,6 +2240,10 @@ function hook_validation_constraint_alter(array &$definitions) {
* - context_provider: Indicates a block context provider, used for example
* by block conditions. It has to implement
* \Drupal\Core\Plugin\Context\ContextProviderInterface.
* - http_client_middleware: Indicates that the service provides a guzzle
* middleware, see
* https://guzzle.readthedocs.org/en/latest/handlers-and-middleware.html for
* more information.
*
* Creating a tag for a service does not do anything on its own, but tags
* can be discovered or queried in a compiler pass when the container is built,
......
......@@ -379,10 +379,21 @@ services:
path.current:
class: Drupal\Core\Path\CurrentPathStack
arguments: ['@request_stack']
http_handler_stack:
class: GuzzleHttp\HandlerStack
public: false
factory: GuzzleHttp\HandlerStack::create
configurator: ['@http_handler_stack_configurator', configure]
http_handler_stack_configurator:
class: Drupal\Core\Http\HandlerStackConfigurator
public: false
arguments: ['@service_container']
http_client:
class: Drupal\Core\Http\Client
tags:
- { name: service_collector, tag: http_client_subscriber, call: attach }
class: GuzzleHttp\Client
factory: http_client_factory:fromOptions
http_client_factory:
class: Drupal\Core\Http\ClientFactory
arguments: ['@http_handler_stack']
theme.negotiator:
class: Drupal\Core\Theme\ThemeNegotiator
arguments: ['@access_check.theme']
......
......@@ -395,7 +395,7 @@ public static function state() {
/**
* Returns the default http client.
*
* @return \GuzzleHttp\ClientInterface
* @return \GuzzleHttp\Client
* A guzzle http client instance.
*/
public static function httpClient() {
......
......@@ -10,6 +10,7 @@
use Drupal\Core\Cache\Context\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass;
use Drupal\Core\DependencyInjection\Compiler\ContextProvidersPass;
use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterLazyRouteEnhancers;
......@@ -75,6 +76,7 @@ public function register(ContainerBuilder $container) {
// Collect tagged handler services as method calls on consumer services.
$container->addCompilerPass(new TaggedHandlersPass());
$container->addCompilerPass(new RegisterStreamWrappersPass());
$container->addCompilerPass(new GuzzleMiddlewarePass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
......@@ -132,10 +134,10 @@ protected function registerTest(ContainerBuilder $container) {
if (!drupal_valid_test_ua()) {
return;
}
// Add the HTTP request subscriber to Guzzle.
// Add the HTTP request middleware to Guzzle.
$container
->register('test.http_client.request_subscriber', 'Drupal\Core\Test\EventSubscriber\HttpRequestSubscriber')
->addTag('http_client_subscriber');
->register('test.http_client.middleware', 'Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware')
->addTag('http_client_middleware');
}
}
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\GuzzleMiddlewarePass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class GuzzleMiddlewarePass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$middleware_ids = array_keys($container->findTaggedServiceIds('http_client_middleware'));
$container->getDefinition('http_handler_stack_configurator')
->addArgument($middleware_ids);
}
}
......@@ -2,69 +2,66 @@
/**
* @file
* Contains \Drupal\Core\Http\Client.
* Contains \Drupal\Core\Http\ClientFactory.
*/
namespace Drupal\Core\Http;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Site\Settings;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
/**
* Drupal default HTTP client class.
* Helper class to construct a HTTP client with Drupal specific config.
*/
class Client extends GuzzleClient {
class ClientFactory {
/**
* {@inheritdoc}
* The handler stack.
*
* @var \GuzzleHttp\HandlerStack
*/
public function __construct(array $config = []) {
$default_config = array(
// Security consideration: we must not use the certificate authority
// file shipped with Guzzle because it can easily get outdated if a
// certificate authority is hacked. Instead, we rely on the certificate
// authority file provided by the operating system which is more likely
// going to be updated in a timely fashion. This overrides the default
// path to the pem file bundled with Guzzle.
'verify' => TRUE,
'timeout' => 30,
'headers' => array(
'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . static::getDefaultUserAgent(),
),
);
// The entire config array is merged/configurable to allow Guzzle client
// options outside of 'defaults' to be changed, such as 'adapter', or
// 'message_factory'.
$config = NestedArray::mergeDeep(array('defaults' => $default_config), $config, Settings::get('http_client_config', array()));
parent::__construct($config);
}
protected $stack;
/**
* Attaches an event subscriber.
* Constructs a new ClientFactory instance.
*
* @param \GuzzleHttp\Event\SubscriberInterface $subscriber
* The subscriber to attach.
*
* @see \GuzzleHttp\Event\Emitter::attach()
* @param \GuzzleHttp\HandlerStack $stack
* The handler stack.
*/
public function attach(SubscriberInterface $subscriber) {
$this->getEmitter()->attach($subscriber);
public function __construct(HandlerStack $stack) {
$this->stack = $stack;
}
/**
* Detaches an event subscriber.
* Constructs a new client object from some configuration.
*
* @param \GuzzleHttp\Event\SubscriberInterface $subscriber
* The subscriber to detach.
* @param array $config
* The config for the client.
*
* @see \GuzzleHttp\Event\Emitter::detach()
* @return \GuzzleHttp\Client
* The HTTP client.
*/
public function detach(SubscriberInterface $subscriber) {
$this->getEmitter()->detach($subscriber);
public function fromOptions(array $config = []) {
$default_config = [
// Security consideration: we must not use the certificate authority
// file shipped with Guzzle because it can easily get outdated if a
// certificate authority is hacked. Instead, we rely on the certificate
// authority file provided by the operating system which is more likely
// going to be updated in a timely fashion. This overrides the default
// path to the pem file bundled with Guzzle.
'verify' => TRUE,
'timeout' => 30,
'headers' => [
'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(),
],
'handler' => $this->stack,
];
$config = NestedArray::mergeDeep($default_config, Settings::get('http_client_config', []), $config);
return new Client($config);
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Http\HandlerStackConfigurator.
*/
namespace Drupal\Core\Http;
use GuzzleHttp\HandlerStack;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a class for configuring middlewares on the http handler stack.
*
* The http_client service requires a handler stack to perform http requests.
* This is provided by the http_handler_stack service. Modules wishing to add
* additional middlewares to the handler stack can create services and tag them
* as http_client_middleware. Each service must contain an __invoke method that
* returns a closure which will serve as the middleware.
*
* @see https://guzzle.readthedocs.org/en/latest/handlers-and-middleware.html
*
* @see \Drupal\Core\Http\Client
* @see \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware
*/
class HandlerStackConfigurator {
/**
* Array of middlewares to add to the handler stack.
*
* @var callable[]
*/
protected $middlewares = NULL;
/**
* A list of used middleware service IDs.
*
* @var string[]
*/
protected $middlewareIds = [];
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Contructs a new HandlerStackConfigurator object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The service container.
* @param string[] $middleware_ids
* The middleware IDs.
*/
public function __construct(ContainerInterface $container, array $middleware_ids) {
$this->middlewareIds = $middleware_ids;
$this->container = $container;
}
/**
* Ensures that the middlewares are initialized.
*/
protected function initializeMiddlewares() {
if (!isset($this->middlewares)) {
$this->middlewares = [];
foreach ($this->middlewareIds as $middleware_id) {
$middleware = $this->container->get($middleware_id);
if (is_callable($middleware)) {
$this->middlewares[$middleware_id] = $middleware();
}
else {
throw new \InvalidArgumentException('Middlewares need to implement __invoke, see https://guzzle.readthedocs.org/en/latest/handlers-and-middleware.html for more information about middlewares.');
}
}
}
}
/**
* Configures the stack using services tagged as http_client_middleware.
*
* @param \GuzzleHttp\HandlerStack $handler_stack
* The handler stack
*/
public function configure(HandlerStack $handler_stack) {
$this->initializeMiddlewares();
foreach ($this->middlewares as $middleware_id => $middleware) {
$handler_stack->push($middleware, $middleware_id);
}
}
}
......@@ -2,41 +2,38 @@
/**
* @file
* Contains \Drupal\Core\Test\EventSubscriber\HttpRequestSubscriber.
* Contains \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware.
*/
namespace Drupal\Core\Test\EventSubscriber;
namespace Drupal\Core\Test\HttpClientMiddleware;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\SubscriberInterface;
use Psr\Http\Message\RequestInterface;
/**
* Overrides the User-Agent HTTP header for outbound HTTP requests.
*/
class HttpRequestSubscriber implements SubscriberInterface {
class TestHttpClientMiddleware {
/**
* {@inheritdoc}
*
* HTTP middleware that replaces the user agent for simpletest requests.
*/
public function getEvents() {
return array(
'before' => array('onBeforeSendRequest'),
);
}
/**
* Event callback for the 'before' event
*/
public function onBeforeSendRequest(BeforeEvent $event) {
public function __invoke() {
// If the database prefix is being used by SimpleTest to run the tests in a copied
// database then set the user-agent header to the database prefix so that any
// calls to other Drupal pages will run the SimpleTest prefixed database. The
// user-agent is used to ensure that multiple testing sessions running at the
// same time won't interfere with each other as they would if the database
// prefix were stored statically in a file or database variable.
if ($test_prefix = drupal_valid_test_ua()) {
$event->getRequest()->setHeader('User-Agent', drupal_generate_test_ua($test_prefix));
}
return function ($handler) {
return function (RequestInterface $request, array $options) use ($handler) {
if ($test_prefix = drupal_valid_test_ua()) {
$request = $request->withHeader('User-Agent', drupal_generate_test_ua($test_prefix));
}
return $handler($request, $options);
};
};
}
}
......@@ -10,9 +10,13 @@
use Drupal\aggregator\Plugin\FetcherInterface;
use Drupal\aggregator\FeedInterface;
use Drupal\Component\Datetime\DateTimePlus;
use Drupal\Core\Http\ClientFactory;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -32,9 +36,9 @@ class DefaultFetcher implements FetcherInterface, ContainerFactoryPluginInterfac
/**
* The HTTP client to fetch the feed data with.
*
* @var \GuzzleHttp\ClientInterface
* @var \Drupal\Core\Http\ClientFactory
*/
protected $httpClient;
protected $httpClientFactory;
/**
* A logger instance.
......@@ -46,13 +50,13 @@ class DefaultFetcher implements FetcherInterface, ContainerFactoryPluginInterfac
/**
* Constructs a DefaultFetcher object.
*
* @param \GuzzleHttp\ClientInterface $http_client
* @param \Drupal\Core\Http\ClientFactory $http_client_factory
* A Guzzle client object.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
*/
public function __construct(ClientInterface $http_client, LoggerInterface $logger) {
$this->httpClient = $http_client;
public function __construct(ClientFactory $http_client_factory, LoggerInterface $logger) {
$this->httpClientFactory = $http_client_factory;
$this->logger = $logger;
}
......@@ -61,7 +65,7 @@ public function __construct(ClientInterface $http_client, LoggerInterface $logge
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$container->get('http_client'),
$container->get('http_client_factory'),
$container->get('logger.factory')->get('aggregator')
);
}
......@@ -70,19 +74,26 @@ public static function create(ContainerInterface $container, array $configuratio
* {@inheritdoc}
*/
public function fetch(FeedInterface $feed) {
$request = $this->httpClient->createRequest('GET', $feed->getUrl());
$request = new Request('GET', $feed->getUrl());
$feed->source_string = FALSE;
// Generate conditional GET headers.
if ($feed->getEtag()) {
$request->addHeader('If-None-Match', $feed->getEtag());
$request = $request->withAddedHeader('If-None-Match', $feed->getEtag());
}
if ($feed->getLastModified()) {
$request->addHeader('If-Modified-Since', gmdate(DateTimePlus::RFC7231, $feed->getLastModified()));
$request = $request->withAddedHeader('If-Modified-Since', gmdate(DateTimePlus::RFC7231, $feed->getLastModified()));
}
try {
$response = $this->httpClient->send($request);
/** @var \Psr\Http\Message\UriInterface $actual_uri */
$actual_uri = NULL;
$response = $this->httpClientFactory->fromOptions(['allow_redirects' => [
'on_redirect' => function(RequestInterface $request, ResponseInterface $response, UriInterface $uri) use (&$actual_uri) {
$actual_uri = (string) $uri;
}
]])->send($request);
// In case of a 304 Not Modified, there is no new content, so return
// FALSE.
......@@ -91,13 +102,17 @@ public function fetch(FeedInterface $feed) {
}
$feed->source_string = (string) $response->getBody();
$feed->setEtag($response->getHeader('ETag'));
$feed->setLastModified(strtotime($response->getHeader('Last-Modified')));
if ($response->hasHeader('ETag')) {
$feed->setEtag($response->getHeaderLine('ETag'));
}
if ($response->hasHeader('Last-Modified')) {
$feed->setLastModified(strtotime($response->getHeaderLine('Last-Modified')));
}
$feed->http_headers = $response->getHeaders();
// Update the feed URL in case of a 301 redirect.
if ($response->getEffectiveUrl() != $feed->getUrl()) {
$feed->setUrl($response->getEffectiveUrl());
if ($actual_uri && $actual_uri !== $feed->getUrl()) {
$feed->setUrl($actual_uri);
}
return TRUE;
}
......
......@@ -6,6 +6,9 @@
*/
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
/**
* Load the common translation API.
......@@ -234,15 +237,20 @@ function locale_translation_batch_fetch_finished($success, $results) {
function locale_translation_http_check($uri) {
$logger = \Drupal::logger('locale');
try {
$response = \Drupal::httpClient()->head($uri);
$actual_uri = NULL;
$response = \Drupal::service('http_client_factory')->fromOptions(['allow_redirects' => [
'on_redirect' => function(RequestInterface $request, ResponseInterface $response, UriInterface $request_uri) use (&$actual_uri) {
$actual_uri = (string) $request_uri;
}
]])->head($uri);
$result = array();
// Return the effective URL if it differs from the requested.
if ($response->getEffectiveUrl() != $uri) {
$result['location'] = $response->getEffectiveUrl();
if ($actual_uri && $actual_uri !== $uri) {
$result['location'] = $actual_uri;
}
$result['last_modified'] = $response->hasHeader('Last-Modified') ? strtotime($response->getHeader('Last-Modified')) : 0;
$result['last_modified'] = $response->hasHeader('Last-Modified') ? strtotime($response->getHeaderLine('Last-Modified')) : 0;
return $result;
}
catch (RequestException $e) {
......
......@@ -157,7 +157,7 @@ function stubTest() {
// request. This allows the stub test to make requests. The event does not
// fire here and drupal_generate_test_ua() can not generate a key for a
// test in a test since the prefix has changed.
// @see \Drupal\Core\Test\EventSubscriber\HttpRequestSubscriber::onBeforeSendRequest()
// @see \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware::onBeforeSendRequest()
// @see drupal_generate_test_ua();
$key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($this->databasePrefix, 10) . '/.htkey';
$private_key = Crypt::randomBytesBase64(55);
......
......@@ -40,7 +40,7 @@ class StatisticsAdminTest extends WebTestBase {