-
-
Notifications
You must be signed in to change notification settings - Fork 49
/
HttplugClient.php
254 lines (213 loc) · 9.5 KB
/
HttplugClient.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpClient;
use GuzzleHttp\Promise\Promise as GuzzlePromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\Utils;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Client\HttpAsyncClient;
use Http\Discovery\Psr17Factory;
use Http\Discovery\Psr17FactoryDiscovery;
use Nyholm\Psr7\Factory\Psr17Factory as NyholmPsr17Factory;
use Nyholm\Psr7\Request;
use Nyholm\Psr7\Uri;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
use Symfony\Component\HttpClient\Response\HttplugPromise;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Symfony\Contracts\Service\ResetInterface;
if (!interface_exists(HttpAsyncClient::class)) {
throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/discovery php-http/async-client-implementation:*".');
}
if (!interface_exists(RequestFactoryInterface::class)) {
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require php-http/discovery psr/http-factory-implementation:*".');
}
/**
* An adapter to turn a Symfony HttpClientInterface into an Httplug client.
*
* In comparison to Psr18Client, this client supports asynchronous requests.
*
* Run "composer require php-http/discovery php-http/async-client-implementation:*"
* to get the required dependencies.
*
* @author Nicolas Grekas <[email protected]>
*/
final class HttplugClient implements ClientInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface
{
private HttpClientInterface $client;
private ResponseFactoryInterface $responseFactory;
private StreamFactoryInterface $streamFactory;
/**
* @var \SplObjectStorage<ResponseInterface, array{RequestInterface, Promise}>|null
*/
private ?\SplObjectStorage $promisePool;
private HttplugWaitLoop $waitLoop;
public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null)
{
$this->client = $client ?? HttpClient::create();
$streamFactory ??= $responseFactory instanceof StreamFactoryInterface ? $responseFactory : null;
$this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null;
if (null === $responseFactory || null === $streamFactory) {
if (class_exists(Psr17Factory::class)) {
$psr17Factory = new Psr17Factory();
} elseif (class_exists(NyholmPsr17Factory::class)) {
$psr17Factory = new NyholmPsr17Factory();
} else {
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require php-http/discovery psr/http-factory-implementation:*".');
}
$responseFactory ??= $psr17Factory;
$streamFactory ??= $psr17Factory;
}
$this->responseFactory = $responseFactory;
$this->streamFactory = $streamFactory;
$this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory);
}
public function withOptions(array $options): static
{
$clone = clone $this;
$clone->client = $clone->client->withOptions($options);
return $clone;
}
public function sendRequest(RequestInterface $request): Psr7ResponseInterface
{
try {
return HttplugWaitLoop::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $this->sendPsr7Request($request), true);
} catch (TransportExceptionInterface $e) {
throw new NetworkException($e->getMessage(), $request, $e);
}
}
public function sendAsyncRequest(RequestInterface $request): HttplugPromise
{
if (!$promisePool = $this->promisePool) {
throw new \LogicException(\sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__));
}
try {
$response = $this->sendPsr7Request($request, true);
} catch (NetworkException $e) {
return new HttplugPromise(new RejectedPromise($e));
}
$waitLoop = $this->waitLoop;
$promise = new GuzzlePromise(static function () use ($response, $waitLoop) {
$waitLoop->wait($response);
}, static function () use ($response, $promisePool) {
$response->cancel();
unset($promisePool[$response]);
});
$promisePool[$response] = [$request, $promise];
return new HttplugPromise($promise);
}
/**
* Resolves pending promises that complete before the timeouts are reached.
*
* When $maxDuration is null and $idleTimeout is reached, promises are rejected.
*
* @return int The number of remaining pending promises
*/
public function wait(?float $maxDuration = null, ?float $idleTimeout = null): int
{
return $this->waitLoop->wait(null, $maxDuration, $idleTimeout);
}
/**
* @param UriInterface|string $uri
*/
public function createRequest(string $method, $uri = ''): RequestInterface
{
if ($this->responseFactory instanceof RequestFactoryInterface) {
$request = $this->responseFactory->createRequest($method, $uri);
} elseif (class_exists(Psr17FactoryDiscovery::class)) {
$request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri);
} elseif (class_exists(Request::class)) {
$request = new Request($method, $uri);
} else {
throw new \LogicException(\sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__));
}
return $request;
}
public function createStream(string $content = ''): StreamInterface
{
return $this->streamFactory->createStream($content);
}
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
{
return $this->streamFactory->createStreamFromFile($filename, $mode);
}
public function createStreamFromResource($resource): StreamInterface
{
return $this->streamFactory->createStreamFromResource($resource);
}
public function createUri(string $uri = ''): UriInterface
{
if ($this->responseFactory instanceof UriFactoryInterface) {
return $this->responseFactory->createUri($uri);
}
if (class_exists(Psr17FactoryDiscovery::class)) {
return Psr17FactoryDiscovery::findUriFactory()->createUri($uri);
}
if (class_exists(Uri::class)) {
return new Uri($uri);
}
throw new \LogicException(\sprintf('You cannot use "%s()" as no PSR-17 factories have been found. Try running "composer require php-http/discovery psr/http-factory-implementation:*".', __METHOD__));
}
public function __sleep(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup(): void
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->wait();
}
public function reset(): void
{
if ($this->client instanceof ResetInterface) {
$this->client->reset();
}
}
private function sendPsr7Request(RequestInterface $request, ?bool $buffer = null): ResponseInterface
{
try {
$body = $request->getBody();
if ($body->isSeekable()) {
$body->seek(0);
}
$headers = $request->getHeaders();
if (!$request->hasHeader('content-length') && 0 <= $size = $body->getSize() ?? -1) {
$headers['Content-Length'] = [$size];
}
$options = [
'headers' => $headers,
'body' => static fn (int $size) => $body->read($size),
'buffer' => $buffer,
];
if ('1.0' === $request->getProtocolVersion()) {
$options['http_version'] = '1.0';
}
return $this->client->request($request->getMethod(), (string) $request->getUri(), $options);
} catch (\InvalidArgumentException $e) {
throw new RequestException($e->getMessage(), $request, $e);
} catch (TransportExceptionInterface $e) {
throw new NetworkException($e->getMessage(), $request, $e);
}
}
}