1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Http\HttplugBundle\Collector; |
4
|
|
|
|
5
|
|
|
use Http\Client\Common\FlexibleHttpClient; |
6
|
|
|
use Http\Client\Common\VersionBridgeClient; |
7
|
|
|
use Http\Client\Exception\HttpException; |
8
|
|
|
use Http\Client\HttpAsyncClient; |
9
|
|
|
use Http\Client\HttpClient; |
10
|
|
|
use Psr\Http\Client\ClientInterface; |
11
|
|
|
use Psr\Http\Message\RequestInterface; |
12
|
|
|
use Psr\Http\Message\ResponseInterface; |
13
|
|
|
use Symfony\Component\Stopwatch\Stopwatch; |
14
|
|
|
use Symfony\Component\Stopwatch\StopwatchEvent; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* The ProfileClient decorates any client that implement both HttpClient and HttpAsyncClient interfaces to gather target |
18
|
|
|
* url and response status code. |
19
|
|
|
* |
20
|
|
|
* @author Fabien Bourigault <[email protected]> |
21
|
|
|
* |
22
|
|
|
* @internal |
23
|
|
|
*/ |
24
|
|
|
class ProfileClient implements HttpClient, HttpAsyncClient |
25
|
|
|
{ |
26
|
|
|
use VersionBridgeClient; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var HttpClient|HttpAsyncClient |
30
|
|
|
*/ |
31
|
|
|
private $client; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @var Collector |
35
|
|
|
*/ |
36
|
|
|
private $collector; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var Formatter |
40
|
|
|
*/ |
41
|
|
|
private $formatter; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var Stopwatch |
45
|
|
|
*/ |
46
|
|
|
private $stopwatch; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
private $eventNames = []; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @param HttpClient|HttpAsyncClient $client The client to profile. Client must implement HttpClient or |
55
|
|
|
* HttpAsyncClient interface. |
56
|
|
|
* @param Collector $collector |
57
|
|
|
* @param Formatter $formatter |
58
|
|
|
* @param Stopwatch $stopwatch |
59
|
|
|
*/ |
60
|
21 |
|
public function __construct($client, Collector $collector, Formatter $formatter, Stopwatch $stopwatch) |
61
|
|
|
{ |
62
|
21 |
View Code Duplication |
if (!(($client instanceof ClientInterface || $client instanceof HttpClient) && $client instanceof HttpAsyncClient)) { |
|
|
|
|
63
|
1 |
|
$client = new FlexibleHttpClient($client); |
64
|
|
|
} |
65
|
|
|
|
66
|
21 |
|
$this->client = $client; |
67
|
21 |
|
$this->collector = $collector; |
68
|
21 |
|
$this->formatter = $formatter; |
69
|
21 |
|
$this->stopwatch = $stopwatch; |
70
|
21 |
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* {@inheritdoc} |
74
|
|
|
*/ |
75
|
3 |
|
public function sendAsyncRequest(RequestInterface $request) |
76
|
|
|
{ |
77
|
3 |
|
$activateStack = true; |
78
|
3 |
|
$stack = $this->collector->getActiveStack(); |
79
|
3 |
|
if (null === $stack) { |
80
|
|
|
//When using a discovered client not wrapped in a PluginClient, we don't have a stack from StackPlugin. So |
81
|
|
|
//we create our own stack and activate it! |
82
|
|
|
$stack = new Stack('Default', $this->formatter->formatRequest($request)); |
83
|
|
|
$this->collector->addStack($stack); |
84
|
|
|
$this->collector->activateStack($stack); |
85
|
|
|
$activateStack = false; |
86
|
|
|
} |
87
|
|
|
|
88
|
3 |
|
$this->collectRequestInformations($request, $stack); |
89
|
3 |
|
$event = $this->stopwatch->start($this->getStopwatchEventName($request)); |
90
|
|
|
|
91
|
3 |
|
$onFulfilled = function (ResponseInterface $response) use ($event, $stack) { |
92
|
2 |
|
$this->collectResponseInformations($response, $event, $stack); |
93
|
2 |
|
$event->stop(); |
94
|
|
|
|
95
|
2 |
|
return $response; |
96
|
3 |
|
}; |
97
|
|
|
|
98
|
3 |
|
$onRejected = function (\Exception $exception) use ($event, $stack) { |
99
|
1 |
|
$this->collectExceptionInformations($exception, $event, $stack); |
100
|
1 |
|
$event->stop(); |
101
|
|
|
|
102
|
1 |
|
throw $exception; |
103
|
3 |
|
}; |
104
|
|
|
|
105
|
3 |
|
$this->collector->deactivateStack($stack); |
106
|
|
|
|
107
|
|
|
try { |
108
|
3 |
|
return $this->client->sendAsyncRequest($request)->then($onFulfilled, $onRejected); |
|
|
|
|
109
|
|
|
} catch (\Exception $e) { |
110
|
|
|
$event->stop(); |
111
|
|
|
|
112
|
|
|
throw $e; |
113
|
|
|
} finally { |
114
|
3 |
|
if ($activateStack) { |
115
|
|
|
//We only activate the stack when created by the StackPlugin. |
116
|
3 |
|
$this->collector->activateStack($stack); |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
5 |
|
protected function doSendRequest(RequestInterface $request) |
122
|
|
|
{ |
123
|
5 |
|
$stack = $this->collector->getActiveStack(); |
124
|
5 |
|
if (null === $stack) { |
125
|
|
|
//When using a discovered client not wrapped in a PluginClient, we don't have a stack from StackPlugin. So |
126
|
|
|
//we create our own stack but don't activate it. |
127
|
|
|
$stack = new Stack('Default', $this->formatter->formatRequest($request)); |
128
|
|
|
$this->collector->addStack($stack); |
129
|
|
|
} |
130
|
|
|
|
131
|
5 |
|
$this->collectRequestInformations($request, $stack); |
132
|
5 |
|
$event = $this->stopwatch->start($this->getStopwatchEventName($request)); |
133
|
|
|
|
134
|
|
|
try { |
135
|
5 |
|
$response = $this->client->sendRequest($request); |
|
|
|
|
136
|
4 |
|
$this->collectResponseInformations($response, $event, $stack); |
137
|
|
|
|
138
|
4 |
|
return $response; |
139
|
1 |
|
} catch (\Exception $e) { |
140
|
|
|
$this->collectExceptionInformations($e, $event, $stack); |
141
|
|
|
|
142
|
|
|
throw $e; |
143
|
1 |
|
} catch (\Throwable $e) { |
144
|
1 |
|
$this->collectExceptionInformations($e, $event, $stack); |
145
|
|
|
|
146
|
1 |
|
throw $e; |
147
|
|
|
} finally { |
148
|
5 |
|
$event->stop(); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @param RequestInterface $request |
154
|
|
|
* @param Stack $stack |
155
|
|
|
*/ |
156
|
8 |
|
private function collectRequestInformations(RequestInterface $request, Stack $stack) |
157
|
|
|
{ |
158
|
8 |
|
$stack->setRequestTarget($request->getRequestTarget()); |
159
|
8 |
|
$stack->setRequestMethod($request->getMethod()); |
160
|
8 |
|
$stack->setRequestScheme($request->getUri()->getScheme()); |
161
|
8 |
|
$stack->setRequestHost($request->getUri()->getHost()); |
162
|
8 |
|
$stack->setClientRequest($this->formatter->formatRequest($request)); |
163
|
8 |
|
$stack->setCurlCommand($this->formatter->formatAsCurlCommand($request)); |
164
|
8 |
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @param ResponseInterface $response |
168
|
|
|
* @param StopwatchEvent $event |
169
|
|
|
* @param Stack $stack |
170
|
|
|
*/ |
171
|
6 |
|
private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack) |
172
|
|
|
{ |
173
|
6 |
|
$stack->setDuration($event->getDuration()); |
174
|
6 |
|
$stack->setResponseCode($response->getStatusCode()); |
175
|
6 |
|
$stack->setClientResponse($this->formatter->formatResponse($response)); |
176
|
6 |
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @param \Throwable $exception |
180
|
|
|
* @param StopwatchEvent $event |
181
|
|
|
* @param Stack $stack |
182
|
|
|
*/ |
183
|
2 |
|
private function collectExceptionInformations(\Throwable $exception, StopwatchEvent $event, Stack $stack) |
184
|
|
|
{ |
185
|
2 |
|
if ($exception instanceof HttpException) { |
186
|
|
|
$this->collectResponseInformations($exception->getResponse(), $event, $stack); |
187
|
|
|
} |
188
|
|
|
|
189
|
2 |
|
$stack->setDuration($event->getDuration()); |
190
|
2 |
|
$stack->setClientException($this->formatter->formatException($exception)); |
191
|
2 |
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Generates the event name. |
195
|
|
|
* |
196
|
|
|
* @param RequestInterface $request |
197
|
|
|
* |
198
|
|
|
* @return string |
199
|
|
|
*/ |
200
|
8 |
|
private function getStopwatchEventName(RequestInterface $request) |
201
|
|
|
{ |
202
|
8 |
|
$name = sprintf('%s %s', $request->getMethod(), $request->getUri()); |
203
|
|
|
|
204
|
8 |
|
if (isset($this->eventNames[$name])) { |
205
|
|
|
$name .= sprintf(' [#%d]', ++$this->eventNames[$name]); |
206
|
|
|
} else { |
207
|
8 |
|
$this->eventNames[$name] = 1; |
208
|
|
|
} |
209
|
|
|
|
210
|
8 |
|
return $name; |
211
|
|
|
} |
212
|
|
|
} |
213
|
|
|
|
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.