Batman::send()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Veslo project <https://github.com/symfony-doge/veslo>.
5
 *
6
 * (C) 2019 Pavel Petrov <[email protected]>.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @license https://opensource.org/licenses/GPL-3.0 GPL-3.0
12
 */
13
14
declare(strict_types=1);
15
16
namespace Veslo\AppBundle\Http\Client;
17
18
use GuzzleHttp\ClientInterface;
19
use GuzzleHttp\Exception\ConnectException;
20
use HttpRuntimeException;
21
use Psr\Http\Message\RequestInterface;
22
use Symfony\Component\Console\ConsoleEvents;
23
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
24
use Symfony\Component\OptionsResolver\OptionsResolver;
25
use Veslo\AppBundle\Event\Http\Client\ConnectFailedEvent;
26
use Veslo\AppBundle\Http\Proxy\Manager\Alfred;
27
use Veslo\AppBundle\Http\ProxyAwareClientInterface;
28
29
/**
30
 * Http client that uses dynamic proxies and fake fingerprints to ensure requests stability
31
 *
32
 * Configured for using in console environment (using ConsoleEvents)
33
 */
34
class Batman implements ClientInterface, ProxyAwareClientInterface
35
{
36
    /**
37
     * Base http client implementation
38
     *
39
     * @var ClientInterface
40
     */
41
    private $httpClient;
42
43
    /**
44
     * Provides proxies for requests
45
     *
46
     * @var Alfred
47
     */
48
    private $proxyManager;
49
50
    /**
51
     * Dispatches a connect failed event to listeners
52
     *
53
     * @var EventDispatcherInterface|null
54
     */
55
    private $eventDispatcher;
56
57
    /**
58
     * Options for stable http client
59
     *
60
     * @var array
61
     */
62
    private $options;
63
64
    /**
65
     * Batman constructor.
66
     *
67
     * @param ClientInterface $httpClient   Base http client implementation
68
     * @param Alfred          $proxyManager Provides proxies for requests
69
     * @param array           $options      Options for stable http client
70
     */
71
    public function __construct(ClientInterface $httpClient, Alfred $proxyManager, array $options)
72
    {
73
        $this->httpClient   = $httpClient;
74
        $this->proxyManager = $proxyManager;
75
76
        $optionsResolver = new OptionsResolver();
77
        $optionsResolver->setDefaults(
78
            [
79
                'proxy' => [
80
                    'enabled' => false,
81
                ],
82
            ]
83
        );
84
85
        $this->options = $optionsResolver->resolve($options);
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     *
91
     * @throws HttpRuntimeException
92
     */
93
    public function send(RequestInterface $request, array $options = [])
94
    {
95
        throw new HttpRuntimeException('Method is not supported.');
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     *
101
     * @throws HttpRuntimeException
102
     */
103
    public function sendAsync(RequestInterface $request, array $options = [])
104
    {
105
        throw new HttpRuntimeException('Method is not supported.');
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111
    public function request($method, $uri, array $options = [])
112
    {
113
        if ($this->isProxyEnabled()) {
114
            $options = array_replace_recursive($options, $this->getProxyOptions());
115
116
            // TODO: log appended stability options at debug level.
117
        }
118
119
        // TODO: fingerprint faking.
120
121
        try {
122
            return $this->httpClient->request($method, $uri, $options);
123
        } catch (ConnectException $e) {
124
            $eventDispatcher = $this->eventDispatcher;
125
126
            // we are "scheduled" to execute some health maintenance tasks on-the-fly during kernel's terminate stage.
127
            // as an alternative, it could be implemented as a custom middleware for the HTTP client.
128
            if ($eventDispatcher instanceof EventDispatcherInterface) {
129
                $eventDispatcher->addListener(
130
                    ConsoleEvents::TERMINATE,
131
                    function () use ($eventDispatcher, $e) {
132
                        $connectFailedEvent = new ConnectFailedEvent($this, $e);
133
                        $eventDispatcher->dispatch(ConnectFailedEvent::NAME, $connectFailedEvent);
134
                    }
135
                );
136
            }
137
138
            throw $e;
139
        }
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     *
145
     * @throws HttpRuntimeException
146
     */
147
    public function requestAsync($method, $uri, array $options = [])
148
    {
149
        throw new HttpRuntimeException('Method is not supported.');
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function getConfig($option = null)
156
    {
157
        return $this->httpClient->getConfig($option);
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function isProxyEnabled(): bool
164
    {
165
        return !! $this->options['proxy']['enabled'];
166
    }
167
168
    /**
169
     * Sets an event dispatcher for processing a connect failed event
170
     *
171
     * @param EventDispatcherInterface $eventDispatcher
172
     *
173
     * @return void
174
     */
175
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
176
    {
177
        $this->eventDispatcher = $eventDispatcher;
178
    }
179
180
    /**
181
     * Returns proxy-related options for http client
182
     *
183
     * @return array
184
     */
185
    private function getProxyOptions(): array
186
    {
187
        $proxy   = $this->proxyManager->getProxy();
188
        $options = ['proxy' => $proxy];
189
190
        return $options;
191
    }
192
}
193