forked from wallabag/wallabag
Migrate from Guzzle to Symfony HttpClient
This commit is contained in:
@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\Helper;
|
||||
|
||||
use GuzzleHttp\Client as GuzzleClient;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Event\SubscriberInterface;
|
||||
use Http\Adapter\Guzzle5\Client as GuzzleAdapter;
|
||||
use Http\Client\HttpClient;
|
||||
use Http\HttplugBundle\ClientFactory\ClientFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Builds and configures the HTTP client.
|
||||
*/
|
||||
class HttpClientFactory implements ClientFactory
|
||||
{
|
||||
/** @var SubscriberInterface[] */
|
||||
private $subscribers = [];
|
||||
|
||||
/** @var CookieJar */
|
||||
private $cookieJar;
|
||||
|
||||
private $restrictedAccess;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* HttpClientFactory constructor.
|
||||
*
|
||||
* @param string $restrictedAccess This param is a kind of boolean. Values: 0 or 1
|
||||
*/
|
||||
public function __construct(CookieJar $cookieJar, $restrictedAccess, LoggerInterface $logger)
|
||||
{
|
||||
$this->cookieJar = $cookieJar;
|
||||
$this->restrictedAccess = $restrictedAccess;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a subscriber to the HTTP client.
|
||||
*/
|
||||
public function addSubscriber(SubscriberInterface $subscriber)
|
||||
{
|
||||
$this->subscribers[] = $subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input an array of configuration to be able to create a HttpClient.
|
||||
*
|
||||
* @return HttpClient
|
||||
*/
|
||||
public function createClient(array $config = [])
|
||||
{
|
||||
$this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
|
||||
|
||||
if (0 === (int) $this->restrictedAccess) {
|
||||
return new GuzzleAdapter(new GuzzleClient($config));
|
||||
}
|
||||
|
||||
// we clear the cookie to avoid websites who use cookies for analytics
|
||||
$this->cookieJar->clear();
|
||||
if (!isset($config['defaults']['cookies'])) {
|
||||
// need to set the (shared) cookie jar
|
||||
$config['defaults']['cookies'] = $this->cookieJar;
|
||||
}
|
||||
|
||||
$guzzle = new GuzzleClient($config);
|
||||
foreach ($this->subscribers as $subscriber) {
|
||||
$guzzle->getEmitter()->attach($subscriber);
|
||||
}
|
||||
|
||||
return new GuzzleAdapter($guzzle);
|
||||
}
|
||||
}
|
||||
@ -2,24 +2,18 @@
|
||||
|
||||
namespace Wallabag\HttpClient;
|
||||
|
||||
use GuzzleHttp\Event\BeforeEvent;
|
||||
use GuzzleHttp\Event\CompleteEvent;
|
||||
use GuzzleHttp\Event\SubscriberInterface;
|
||||
use GuzzleHttp\Message\RequestInterface;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Wallabag\SiteConfig\LoginFormAuthenticator;
|
||||
use Wallabag\SiteConfig\SiteConfig;
|
||||
use Wallabag\SiteConfig\SiteConfigBuilder;
|
||||
|
||||
class Authenticator implements SubscriberInterface, LoggerAwareInterface
|
||||
class Authenticator implements LoggerAwareInterface
|
||||
{
|
||||
// avoid loop when login failed which can just be a bad login/password
|
||||
// after 2 attempts, we skip the login
|
||||
public const MAX_RETRIES = 2;
|
||||
private int $retries = 0;
|
||||
|
||||
/** @var SiteConfigBuilder */
|
||||
private $configBuilder;
|
||||
|
||||
@ -29,9 +23,6 @@ class Authenticator implements SubscriberInterface, LoggerAwareInterface
|
||||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* AuthenticatorSubscriber constructor.
|
||||
*/
|
||||
public function __construct(SiteConfigBuilder $configBuilder, LoginFormAuthenticator $authenticator)
|
||||
{
|
||||
$this->configBuilder = $configBuilder;
|
||||
@ -44,78 +35,61 @@ class Authenticator implements SubscriberInterface, LoggerAwareInterface
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function getEvents(): array
|
||||
public function loginIfRequired(string $url): bool
|
||||
{
|
||||
return [
|
||||
'before' => ['loginIfRequired'],
|
||||
'complete' => ['loginIfRequested'],
|
||||
];
|
||||
}
|
||||
|
||||
public function loginIfRequired(BeforeEvent $event)
|
||||
{
|
||||
$config = $this->buildSiteConfig($event->getRequest());
|
||||
$config = $this->buildSiteConfig(new Uri($url));
|
||||
if (false === $config || !$config->requiresLogin()) {
|
||||
$this->logger->debug('loginIfRequired> will not require login');
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = $event->getClient();
|
||||
|
||||
if (!$this->authenticator->isLoggedIn($config, $client)) {
|
||||
$this->logger->debug('loginIfRequired> user is not logged in, attach authenticator');
|
||||
|
||||
$emitter = $client->getEmitter();
|
||||
$emitter->detach($this);
|
||||
$this->authenticator->login($config, $client);
|
||||
$emitter->attach($this);
|
||||
if ($this->authenticator->isLoggedIn($config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->logger->debug('loginIfRequired> user is not logged in, attach authenticator');
|
||||
|
||||
$this->authenticator->login($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function loginIfRequested(CompleteEvent $event)
|
||||
public function loginIfRequested(ResponseInterface $response): bool
|
||||
{
|
||||
$config = $this->buildSiteConfig($event->getRequest());
|
||||
$config = $this->buildSiteConfig(new Uri($response->getInfo('url')));
|
||||
if (false === $config || !$config->requiresLogin()) {
|
||||
$this->logger->debug('loginIfRequested> will not require login');
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = $event->getResponse()->getBody();
|
||||
$body = $response->getContent();
|
||||
|
||||
if (
|
||||
null === $body
|
||||
|| '' === $body->getContents()
|
||||
) {
|
||||
if ('' === $body) {
|
||||
$this->logger->debug('loginIfRequested> empty body, ignoring');
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$isLoginRequired = $this->authenticator->isLoginRequired($config, $body);
|
||||
|
||||
$this->logger->debug('loginIfRequested> retry #' . $this->retries . ' with login ' . ($isLoginRequired ? '' : 'not ') . 'required');
|
||||
$this->logger->debug('loginIfRequested> retry with login ' . ($isLoginRequired ? '' : 'not ') . 'required');
|
||||
|
||||
if ($isLoginRequired && $this->retries < self::MAX_RETRIES) {
|
||||
$client = $event->getClient();
|
||||
|
||||
$emitter = $client->getEmitter();
|
||||
$emitter->detach($this);
|
||||
$this->authenticator->login($config, $client);
|
||||
$emitter->attach($this);
|
||||
|
||||
$event->retry();
|
||||
|
||||
++$this->retries;
|
||||
if (!$isLoginRequired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->authenticator->login($config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SiteConfig|false
|
||||
*/
|
||||
private function buildSiteConfig(RequestInterface $request)
|
||||
private function buildSiteConfig(UriInterface $uri)
|
||||
{
|
||||
return $this->configBuilder->buildForHost($request->getHost());
|
||||
return $this->configBuilder->buildForHost($uri->getHost());
|
||||
}
|
||||
}
|
||||
|
||||
84
src/HttpClient/WallabagClient.php
Normal file
84
src/HttpClient/WallabagClient.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Wallabag\HttpClient;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\BrowserKit\HttpBrowser;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
|
||||
class WallabagClient implements HttpClientInterface
|
||||
{
|
||||
private $restrictedAccess;
|
||||
private HttpClientInterface $httpClient;
|
||||
private HttpBrowser $browser;
|
||||
private Authenticator $authenticator;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct($restrictedAccess, HttpBrowser $browser, Authenticator $authenticator, LoggerInterface $logger)
|
||||
{
|
||||
$this->restrictedAccess = $restrictedAccess;
|
||||
$this->browser = $browser;
|
||||
$this->authenticator = $authenticator;
|
||||
$this->logger = $logger;
|
||||
|
||||
$this->httpClient = HttpClient::create([
|
||||
'timeout' => 10,
|
||||
]);
|
||||
}
|
||||
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
|
||||
|
||||
if (0 === (int) $this->restrictedAccess) {
|
||||
return $this->httpClient->request($method, $url, $options);
|
||||
}
|
||||
|
||||
$login = $this->authenticator->loginIfRequired($url);
|
||||
|
||||
if (!$login) {
|
||||
return $this->httpClient->request($method, $url, $options);
|
||||
}
|
||||
|
||||
if (null !== $cookieHeader = $this->getCookieHeader($url)) {
|
||||
$options['headers']['cookie'] = $cookieHeader;
|
||||
}
|
||||
|
||||
$response = $this->httpClient->request($method, $url, $options);
|
||||
|
||||
$login = $this->authenticator->loginIfRequested($response);
|
||||
|
||||
if (!$login) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (null !== $cookieHeader = $this->getCookieHeader($url)) {
|
||||
$options['headers']['cookie'] = $cookieHeader;
|
||||
}
|
||||
|
||||
return $this->httpClient->request($method, $url, $options);
|
||||
}
|
||||
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
return $this->httpClient->stream($responses, $timeout);
|
||||
}
|
||||
|
||||
private function getCookieHeader(string $url): ?string
|
||||
{
|
||||
$cookies = [];
|
||||
|
||||
foreach ($this->browser->getCookieJar()->allRawValues($url) as $name => $value) {
|
||||
$cookies[] = $name . '=' . $value;
|
||||
}
|
||||
|
||||
if ([] === $cookies) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return implode('; ', $cookies);
|
||||
}
|
||||
}
|
||||
@ -2,18 +2,19 @@
|
||||
|
||||
namespace Wallabag\SiteConfig;
|
||||
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use Symfony\Component\BrowserKit\HttpBrowser;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Wallabag\ExpressionLanguage\AuthenticatorProvider;
|
||||
|
||||
class LoginFormAuthenticator
|
||||
{
|
||||
private HttpBrowser $browser;
|
||||
private ExpressionLanguage $expressionLanguage;
|
||||
|
||||
public function __construct(AuthenticatorProvider $authenticatorProvider)
|
||||
public function __construct(HttpBrowser $browser, AuthenticatorProvider $authenticatorProvider)
|
||||
{
|
||||
$this->browser = $browser;
|
||||
$this->expressionLanguage = new ExpressionLanguage(null, [$authenticatorProvider]);
|
||||
}
|
||||
|
||||
@ -22,17 +23,14 @@ class LoginFormAuthenticator
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function login(SiteConfig $siteConfig, ClientInterface $guzzle)
|
||||
public function login(SiteConfig $siteConfig)
|
||||
{
|
||||
$postFields = [
|
||||
$siteConfig->getUsernameField() => $siteConfig->getUsername(),
|
||||
$siteConfig->getPasswordField() => $siteConfig->getPassword(),
|
||||
] + $this->getExtraFields($siteConfig);
|
||||
|
||||
$guzzle->post(
|
||||
$siteConfig->getLoginUri(),
|
||||
['body' => $postFields, 'allow_redirects' => true, 'verify' => false]
|
||||
);
|
||||
$this->browser->request('POST', $siteConfig->getLoginUri(), $postFields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -42,15 +40,12 @@ class LoginFormAuthenticator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLoggedIn(SiteConfig $siteConfig, ClientInterface $guzzle)
|
||||
public function isLoggedIn(SiteConfig $siteConfig)
|
||||
{
|
||||
if (($cookieJar = $guzzle->getDefaultOption('cookies')) instanceof CookieJar) {
|
||||
/** @var \GuzzleHttp\Cookie\SetCookie $cookie */
|
||||
foreach ($cookieJar as $cookie) {
|
||||
// check required cookies
|
||||
if ($cookie->getDomain() === $siteConfig->getHost()) {
|
||||
return true;
|
||||
}
|
||||
foreach ($this->browser->getCookieJar()->all() as $cookie) {
|
||||
// check required cookies
|
||||
if ($cookie->getDomain() === $siteConfig->getHost()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user