1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of ReactGuzzleRing. |
5
|
|
|
* |
6
|
|
|
** (c) 2014 Cees-Jan Kiewiet |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
namespace WyriHaximus\React\Guzzle\HttpClient; |
12
|
|
|
|
13
|
|
|
use GuzzleHttp\Psr7\Response; |
14
|
|
|
use Psr\Http\Message\RequestInterface; |
15
|
|
|
use React\EventLoop\LoopInterface; |
16
|
|
|
use React\HttpClient\Client as ReactHttpClient; |
17
|
|
|
use React\HttpClient\Request as HttpRequest; |
18
|
|
|
use React\HttpClient\Response as HttpResponse; |
19
|
|
|
use React\Promise\Deferred; |
20
|
|
|
use React\Stream\Stream as ReactStream; |
21
|
|
|
use Exception; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Class Request |
25
|
|
|
* |
26
|
|
|
* @package WyriHaximus\React\Guzzle\HttpClient |
27
|
|
|
*/ |
28
|
|
|
class Request |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* @var ReactHttpClient |
32
|
|
|
*/ |
33
|
|
|
protected $httpClient; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var LoopInterface |
37
|
|
|
*/ |
38
|
|
|
protected $loop; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var HttpResponse |
42
|
|
|
*/ |
43
|
|
|
protected $httpResponse; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var string |
47
|
|
|
*/ |
48
|
|
|
protected $buffer = ''; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var Stream |
52
|
|
|
*/ |
53
|
|
|
protected $stream; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var \Exception |
57
|
|
|
*/ |
58
|
|
|
protected $error = ''; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @var \React\EventLoop\Timer\TimerInterface |
62
|
|
|
*/ |
63
|
|
|
protected $connectionTimer; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @var \React\EventLoop\Timer\TimerInterface |
67
|
|
|
*/ |
68
|
|
|
protected $requestTimer; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @var ProgressInterface |
72
|
|
|
*/ |
73
|
|
|
protected $progress; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* @var Deferred |
77
|
|
|
*/ |
78
|
|
|
protected $deferred; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* @var array |
82
|
|
|
*/ |
83
|
|
|
protected $options; |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @var array |
87
|
|
|
*/ |
88
|
|
|
protected $defaultOptions = [ |
89
|
|
|
'stream' => false, |
90
|
|
|
'connect_timeout' => 0, |
91
|
|
|
'timeout' => 0, |
92
|
|
|
'delay' => 0, |
93
|
|
|
]; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @var RequestInterface |
97
|
|
|
*/ |
98
|
|
|
protected $request; |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @var bool |
102
|
|
|
*/ |
103
|
|
|
protected $connectionTimedOut = false; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @param RequestInterface $request |
107
|
|
|
* @param array $options |
108
|
|
|
* @param ReactHttpClient $httpClient |
109
|
|
|
* @param LoopInterface $loop |
110
|
|
|
|
111
|
|
|
*/ |
112
|
3 |
|
protected function __construct( |
113
|
|
|
RequestInterface $request, |
114
|
|
|
array $options, |
115
|
|
|
ReactHttpClient $httpClient, |
116
|
|
|
LoopInterface $loop |
117
|
|
|
) { |
118
|
3 |
|
$this->request = $request; |
119
|
3 |
|
$this->applyOptions($options); |
120
|
3 |
|
$this->httpClient = $httpClient; |
121
|
3 |
|
$this->loop = $loop; |
122
|
3 |
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @param RequestInterface $request |
126
|
|
|
* @param array $options |
127
|
|
|
* @param ReactHttpClient $httpClient |
128
|
|
|
* @param LoopInterface $loop |
129
|
|
|
* @return \React\Promise\Promise |
130
|
|
|
*/ |
131
|
1 |
|
public static function send( |
132
|
|
|
RequestInterface $request, |
133
|
|
|
array $options, |
134
|
|
|
ReactHttpClient $httpClient, |
135
|
|
|
LoopInterface $loop, |
136
|
|
|
Request $requestObject = null |
137
|
|
|
) { |
138
|
1 |
|
if ($requestObject === null) { |
139
|
|
|
$requestObject = new static($request, $options, $httpClient, $loop); |
140
|
|
|
} |
141
|
1 |
|
return $requestObject->perform(); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @return \React\Promise\Promise |
146
|
|
|
*/ |
147
|
1 |
|
protected function perform() |
148
|
|
|
{ |
149
|
1 |
|
$this->deferred = new Deferred(); |
150
|
|
|
|
151
|
1 |
|
$this->loop->addTimer( |
152
|
1 |
|
(int)$this->options['delay'] / 1000, |
153
|
|
|
function () { |
154
|
1 |
|
$this->tickRequest(); |
155
|
1 |
|
} |
156
|
1 |
|
); |
157
|
|
|
|
158
|
1 |
|
return $this->deferred->promise(); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* |
163
|
|
|
*/ |
164
|
1 |
|
protected function tickRequest() |
165
|
|
|
{ |
166
|
|
|
$this->loop->futureTick(function () { |
167
|
1 |
|
$request = $this->setupRequest(); |
168
|
1 |
|
$this->setupListeners($request); |
169
|
|
|
|
170
|
1 |
|
$body = $this->request->getBody()->getContents(); |
171
|
|
|
|
172
|
1 |
|
$this->progress->onSending($body); |
173
|
|
|
|
174
|
1 |
|
$this->setConnectionTimeout($request); |
175
|
1 |
|
$request->end($body); |
176
|
1 |
|
$this->setRequestTimeout($request); |
177
|
1 |
|
}); |
178
|
1 |
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* @return HttpRequest mixed |
182
|
|
|
*/ |
183
|
1 |
|
protected function setupRequest() |
184
|
|
|
{ |
185
|
1 |
|
$headers = []; |
186
|
1 |
|
foreach ($this->request->getHeaders() as $key => $values) { |
187
|
1 |
|
$headers[$key] = implode(';', $values); |
188
|
1 |
|
} |
189
|
|
|
|
190
|
1 |
|
return $this->httpClient->request( |
191
|
1 |
|
$this->request->getMethod(), |
192
|
1 |
|
(string)$this->request->getUri(), |
193
|
1 |
|
$headers, |
194
|
1 |
|
$this->request->getProtocolVersion() |
195
|
1 |
|
); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @param HttpRequest $request |
200
|
|
|
*/ |
201
|
1 |
|
protected function setupListeners(HttpRequest $request) |
202
|
|
|
{ |
203
|
1 |
|
$request->on( |
204
|
1 |
|
'headers-written', |
205
|
|
|
function () { |
206
|
|
|
$this->onHeadersWritten(); |
207
|
|
|
} |
208
|
1 |
|
); |
209
|
1 |
|
$request->on( |
210
|
1 |
|
'drain', |
211
|
|
|
function () { |
212
|
|
|
$this->progress->onSent(); |
213
|
|
|
} |
214
|
1 |
|
); |
215
|
1 |
|
$request->on( |
216
|
1 |
|
'response', |
217
|
|
|
function (HttpResponse $response) use ($request) { |
218
|
|
|
$this->onResponse($response, $request); |
219
|
|
|
} |
220
|
1 |
|
); |
221
|
1 |
|
$request->on( |
222
|
1 |
|
'error', |
223
|
|
|
function ($error) { |
224
|
|
|
$this->onError($error); |
225
|
|
|
} |
226
|
1 |
|
); |
227
|
1 |
|
$request->on( |
228
|
1 |
|
'end', |
229
|
|
|
function () { |
230
|
|
|
$this->onEnd(); |
231
|
|
|
} |
232
|
1 |
|
); |
233
|
1 |
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @param HttpRequest $request |
237
|
|
|
*/ |
238
|
1 |
View Code Duplication |
public function setConnectionTimeout(HttpRequest $request) |
|
|
|
|
239
|
|
|
{ |
240
|
1 |
|
if ($this->options['connect_timeout'] > 0) { |
241
|
1 |
|
$this->connectionTimer = $this->loop->addTimer( |
242
|
1 |
|
$this->options['connect_timeout'], |
243
|
|
|
function () use ($request) { |
244
|
|
|
$request->closeError(new \Exception('Connection time out')); |
245
|
|
|
} |
246
|
1 |
|
); |
247
|
1 |
|
} |
248
|
1 |
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* @param HttpRequest $request |
252
|
|
|
*/ |
253
|
2 |
View Code Duplication |
public function setRequestTimeout(HttpRequest $request) |
|
|
|
|
254
|
|
|
{ |
255
|
2 |
|
if ($this->options['timeout'] > 0) { |
256
|
1 |
|
$this->requestTimer = $this->loop->addTimer( |
257
|
1 |
|
$this->options['timeout'], |
258
|
|
|
function () use ($request) { |
259
|
|
|
$request->closeError(new \Exception('Transaction time out')); |
260
|
|
|
} |
261
|
1 |
|
); |
262
|
1 |
|
} |
263
|
2 |
|
} |
264
|
|
|
|
265
|
|
|
protected function onHeadersWritten() |
266
|
|
|
{ |
267
|
|
|
if ($this->connectionTimer !== null && $this->loop->isTimerActive($this->connectionTimer)) { |
268
|
|
|
$this->loop->cancelTimer($this->connectionTimer); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/** |
273
|
|
|
* @param HttpResponse $response |
274
|
|
|
* @param HttpRequest $request |
275
|
|
|
*/ |
276
|
|
|
protected function onResponse(HttpResponse $response, HttpRequest $request) |
277
|
|
|
{ |
278
|
|
|
$this->httpResponse = $response; |
279
|
|
|
if (isset($this->options['sink'])) { |
280
|
|
|
$this->saveTo(); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
$this->handleResponse($request); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
protected function saveTo() |
287
|
|
|
{ |
288
|
|
|
$saveTo = $this->options['sink']; |
289
|
|
|
|
290
|
|
|
$writeStream = fopen($saveTo, 'w'); |
291
|
|
|
stream_set_blocking($writeStream, 0); |
292
|
|
|
$saveToStream = new ReactStream($writeStream, $this->loop); |
293
|
|
|
|
294
|
|
|
$saveToStream->on( |
295
|
|
|
'end', |
296
|
|
|
function () { |
297
|
|
|
$this->onEnd(); |
298
|
|
|
} |
299
|
|
|
); |
300
|
|
|
|
301
|
|
|
$this->httpResponse->pipe($saveToStream); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* @param string $data |
306
|
|
|
*/ |
307
|
|
|
protected function onData($data) |
308
|
|
|
{ |
309
|
|
|
$this->progress->onData($data); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* @param \Exception $error |
314
|
|
|
*/ |
315
|
|
View Code Duplication |
protected function onError(\Exception $error) |
|
|
|
|
316
|
|
|
{ |
317
|
|
|
if ($this->requestTimer !== null && $this->loop->isTimerActive($this->requestTimer)) { |
318
|
|
|
$this->loop->cancelTimer($this->requestTimer); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
if ($this->connectionTimer !== null && $this->loop->isTimerActive($this->connectionTimer)) { |
322
|
|
|
$this->loop->cancelTimer($this->connectionTimer); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
$this->error = $error; |
326
|
|
|
$this->deferred->reject($this->error); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* |
331
|
|
|
*/ |
332
|
|
View Code Duplication |
protected function onEnd() |
|
|
|
|
333
|
|
|
{ |
334
|
|
|
if ($this->requestTimer !== null && $this->loop->isTimerActive($this->requestTimer)) { |
335
|
|
|
$this->loop->cancelTimer($this->requestTimer); |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
if ($this->connectionTimer !== null && $this->loop->isTimerActive($this->connectionTimer)) { |
339
|
|
|
$this->loop->cancelTimer($this->connectionTimer); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
$this->loop->futureTick(function () { |
343
|
|
|
if ($this->httpResponse === null) { |
344
|
|
|
$this->deferred->reject($this->error); |
345
|
|
|
} |
346
|
|
|
}); |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* |
351
|
|
|
*/ |
352
|
|
|
protected function handleResponse($request) |
353
|
|
|
{ |
354
|
|
|
$this->progress->onResponse($this->httpResponse); |
355
|
|
|
|
356
|
|
|
$this->createStream($request); |
357
|
|
|
|
358
|
|
|
$response = new Response( |
359
|
|
|
$this->httpResponse->getCode(), |
360
|
|
|
$this->httpResponse->getHeaders(), |
361
|
|
|
$this->stream, |
362
|
|
|
$this->httpResponse->getVersion(), |
363
|
|
|
$this->httpResponse->getReasonPhrase() |
364
|
|
|
); |
365
|
|
|
|
366
|
|
|
if (!$this->options['stream']) { |
367
|
|
|
return $request->on('end', function () use ($response) { |
368
|
|
|
$this->resolveResponse($response); |
369
|
|
|
}); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
$this->resolveResponse($response); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
protected function resolveResponse($response) |
376
|
|
|
{ |
377
|
|
|
$this->loop->futureTick(function () use ($response) { |
378
|
|
|
$this->deferred->resolve($response); |
379
|
|
|
}); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
protected function createStream($request) |
383
|
|
|
{ |
384
|
|
|
$this->stream = new Stream([ |
385
|
|
|
'response' => $this->httpResponse, |
386
|
|
|
'request' => $request, |
387
|
|
|
'loop' => $this->loop, |
388
|
|
|
]); |
389
|
|
|
} |
390
|
|
|
|
391
|
3 |
|
private function applyOptions(array $options = []) |
392
|
|
|
{ |
393
|
3 |
|
$this->options = array_replace_recursive($this->defaultOptions, $options); |
394
|
|
|
|
395
|
|
|
// provides backwards compatibility for Guzzle 3-5. |
396
|
3 |
|
if (isset($this->options['client'])) { |
397
|
2 |
|
$this->options = array_merge($this->options, $this->options['client']); |
398
|
2 |
|
unset($this->options['client']); |
399
|
2 |
|
} |
400
|
|
|
|
401
|
|
|
// provides for backwards compatibility for Guzzle 3-5 |
402
|
3 |
|
if (isset($this->options['save_to'])) { |
403
|
|
|
$this->options['sink'] = $options['save_to']; |
404
|
|
|
unset($this->options['save_to']); |
405
|
|
|
} |
406
|
|
|
|
407
|
3 |
|
if (isset($this->options['progress']) && is_callable($this->options['progress'])) { |
408
|
|
|
$this->progress = new Progress($this->options['progress']); |
409
|
|
|
} else { |
410
|
3 |
|
$this->progress = new Progress(function () { |
411
|
3 |
|
}); |
412
|
|
|
} |
413
|
3 |
|
} |
414
|
|
|
} |
415
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.