PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.25
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.25
1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 All 169 releases
woocommerce-pos / vendor_prefixed / sentry / sentry / src / Agent / Transport / AgentClient.php

AgentClient.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.25, at vendor_prefixed/sentry/sentry/src/Agent/Transport/AgentClient.php

192 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace WCPOS\Vendor\Sentry\Agent\Transport;
5
6 use WCPOS\Vendor\Sentry\HttpClient\HttpClientInterface;
7 use WCPOS\Vendor\Sentry\HttpClient\Request;
8 use WCPOS\Vendor\Sentry\HttpClient\Response;
9 use WCPOS\Vendor\Sentry\Options;
10 class AgentClient implements HttpClientInterface
11 {
12 private const SOCKET_TIMEOUT_SECONDS = 0.01;
13 /**
14 * @var string
15 */
16 private $host;
17 /**
18 * @var int
19 */
20 private $port;
21 /**
22 * @var resource|null
23 */
24 private $socket;
25 /**
26 * @var HttpClientInterface|null
27 */
28 private $fallbackClient;
29 /**
30 * @var (callable(): HttpClientInterface)|null
31 */
32 private $fallbackClientFactory;
33 /**
34 * @var string|null
35 */
36 private $fallbackClientError;
37 /**
38 * @var string
39 */
40 private $lastSendError = '';
41 /**
42 * @phpstan-param (callable(): HttpClientInterface)|null $fallbackClientFactory
43 */
44 public function __construct(string $host = '127.0.0.1', int $port = 5148, ?callable $fallbackClientFactory = null)
45 {
46 $this->host = $host;
47 $this->port = $port;
48 $this->fallbackClientFactory = $fallbackClientFactory;
49 }
50 public function __destruct()
51 {
52 $this->disconnect();
53 }
54 /**
55 * @phpstan-assert-if-true resource $this->socket
56 */
57 private function connect() : bool
58 {
59 if ($this->socket !== null) {
60 return \true;
61 }
62 // 10ms connect timeout to avoid blocking the request if the agent is not running
63 $errorNo = 0;
64 $errorMsg = '';
65 $socket = @\fsockopen($this->host, $this->port, $errorNo, $errorMsg, self::SOCKET_TIMEOUT_SECONDS);
66 if ($socket === \false) {
67 $this->lastSendError = \sprintf('Failed to connect to the local Sentry agent at %s:%d. [%d] %s', $this->host, $this->port, $errorNo, $errorMsg);
68 return \false;
69 }
70 // Use non-blocking writes with stream_select() so a hung agent cannot block the caller indefinitely.
71 \stream_set_blocking($socket, \false);
72 $this->socket = $socket;
73 return \true;
74 }
75 private function disconnect() : void
76 {
77 if ($this->socket === null) {
78 return;
79 }
80 \fclose($this->socket);
81 $this->socket = null;
82 }
83 private function send(string $message) : bool
84 {
85 $this->lastSendError = '';
86 $payload = \pack('N', \strlen($message) + 4) . $message;
87 // Attempt to send the payload, retrying once on write failure to handle
88 // stale sockets (e.g. agent restarts in long-running workers).
89 for ($attempt = 0; $attempt < 2; ++$attempt) {
90 if (!$this->connect()) {
91 return \false;
92 }
93 if ($this->writePayload($payload)) {
94 return \true;
95 }
96 $this->disconnect();
97 }
98 $this->lastSendError = \sprintf('Failed to write envelope to the local Sentry agent at %s:%d.', $this->host, $this->port);
99 return \false;
100 }
101 private function writePayload(string $payload) : bool
102 {
103 if ($this->socket === null) {
104 return \false;
105 }
106 $socket = $this->socket;
107 $payloadLength = \strlen($payload);
108 $totalWrittenBytes = 0;
109 $writeDeadline = \microtime(\true) + self::SOCKET_TIMEOUT_SECONDS;
110 while ($totalWrittenBytes < $payloadLength) {
111 if (!$this->waitUntilSocketIsWritable($socket, $writeDeadline)) {
112 return \false;
113 }
114 $bytesWritten = @\fwrite($socket, (string) \substr($payload, $totalWrittenBytes));
115 if ($bytesWritten === \false) {
116 return \false;
117 }
118 $totalWrittenBytes += $bytesWritten;
119 }
120 return \true;
121 }
122 /**
123 * @param resource $socket
124 */
125 private function waitUntilSocketIsWritable($socket, float $deadline) : bool
126 {
127 $remainingSeconds = $deadline - \microtime(\true);
128 if ($remainingSeconds <= 0) {
129 return \false;
130 }
131 $readSockets = null;
132 $writeSockets = [$socket];
133 $exceptSockets = null;
134 $selectedSockets = @\stream_select($readSockets, $writeSockets, $exceptSockets, 0, (int) \ceil($remainingSeconds * 1000000));
135 return $selectedSockets !== \false && $selectedSockets > 0;
136 }
137 private function getFallbackClient() : ?HttpClientInterface
138 {
139 if ($this->fallbackClient !== null) {
140 return $this->fallbackClient;
141 }
142 if ($this->fallbackClientFactory === null) {
143 return null;
144 }
145 try {
146 $fallbackClient = ($this->fallbackClientFactory)();
147 } catch (\Throwable $exception) {
148 $this->fallbackClientFactory = null;
149 $this->fallbackClientError = \sprintf('Failed to initialize fallback HTTP client. Reason: "%s". Fallback delivery has been disabled.', $exception->getMessage());
150 return null;
151 }
152 if (!$fallbackClient instanceof HttpClientInterface) {
153 $this->fallbackClientFactory = null;
154 $this->fallbackClientError = 'The fallback client factory did not return an instance of HttpClientInterface. Fallback delivery has been disabled.';
155 return null;
156 }
157 $this->fallbackClient = $fallbackClient;
158 return $this->fallbackClient;
159 }
160 public function sendRequest(Request $request, Options $options) : Response
161 {
162 $body = $request->getStringBody();
163 if (empty($body)) {
164 return new Response(400, [], 'Request body is empty');
165 }
166 if ($this->send($body)) {
167 // Since we are sending async there is no feedback so we always return an empty response
168 return new Response(202, [], '');
169 }
170 $logContext = ['agent_host' => $this->host, 'agent_port' => $this->port];
171 if ($this->lastSendError !== '') {
172 $logContext['error'] = $this->lastSendError;
173 }
174 $options->getLoggerOrNullLogger()->debug('Failed to hand off envelope to local Sentry agent.', $logContext);
175 $fallbackClient = $this->getFallbackClient();
176 if ($fallbackClient !== null) {
177 $options->getLoggerOrNullLogger()->debug('Using fallback HTTP client because local Sentry agent handoff failed.', $logContext);
178 try {
179 return $fallbackClient->sendRequest($request, $options);
180 } catch (\Throwable $exception) {
181 $options->getLoggerOrNullLogger()->debug('Fallback HTTP client failed while sending envelope.', \array_merge($logContext, ['exception' => $exception]));
182 return new Response(502, [], \sprintf('Failed to send envelope using fallback HTTP client. Reason: "%s".', $exception->getMessage()));
183 }
184 }
185 if ($this->fallbackClientError !== null) {
186 $options->getLoggerOrNullLogger()->debug($this->fallbackClientError, $logContext);
187 $this->fallbackClientError = null;
188 }
189 return new Response(502, [], 'Failed to send envelope to the local Sentry agent and no fallback client is available.');
190 }
191 }
192