1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
handler.php
См. документацию.
1<?php
2
9
11
17use Psr\Http\Message\RequestInterface;
18
19class Handler extends Http\Handler
20{
21 protected const BUF_BODY_LEN = 131072;
22 protected const BUF_READ_LEN = 32768;
23
24 public const PENDING = 0;
25 public const CONNECTED = 1;
26 public const HEADERS_SENT = 2;
27 public const BODY_SENT = 3;
28 public const HEADERS_RECEIVED = 4;
29 public const BODY_RECEIVED = 5;
30 public const CONNECT_SENT = 6;
31 public const CONNECT_RECEIVED = 7;
32
33 protected Stream $socket;
34 protected bool $useProxy = false;
35 protected int $state = self::PENDING;
36 protected string $requestBodyPart = '';
37
38 protected ?StopWatch $connectTimer = null;
39 protected ?StopWatch $handshakeTimer = null;
40 protected ?StopWatch $requestTimer = null;
41 protected ?StopWatch $totalTimer = null;
42
49 {
50 parent::__construct($request, $responseBuilder, $options);
51
52 if (!empty($options['proxyHost']))
53 {
54 $this->useProxy = true;
55 }
56
57 $this->socket = $this->createSocket($options);
58 }
59
66 public function process(?Http\Promise $promise = null)
67 {
69 $uri = $request->getUri();
70 $fetchBody = true;
71
72 try
73 {
74 switch ($this->state)
75 {
76 case self::PENDING:
77 $this->initTimers();
78
79 $logUri = new Web\Uri((string)$uri);
80 $logUri->convertToUnicode();
81
82 $this->log("***CONNECT to {address} for URI {uri}\n", Web\HttpDebug::CONNECT, ['address' => $this->socket->getAddress(), 'uri' => $logUri]);
83
84 // this is a new job - should connect asynchronously
85 try
86 {
87 $this->socket->connect();
88 }
89 catch (\RuntimeException $e)
90 {
91 throw new Http\NetworkException($request, $e->getMessage());
92 }
93
94 $this->connectTimer?->stop();
95
96 $this->state = self::CONNECTED;
97
98 break;
99
100 case self::CONNECTED:
101 case self::CONNECT_RECEIVED:
102 if ($this->state === self::CONNECTED && $this->useProxy && $uri->getScheme() === 'https')
103 {
104 // implement CONNECT method for https connections via proxy
105 $this->sendConnect();
106
107 $this->state = self::CONNECT_SENT;
108 }
109 else
110 {
111 // enable ssl before sending request headers
112 if ($uri->getScheme() === 'https')
113 {
114 if ($this->async)
115 {
116 $this->socket->setBlocking();
117 }
118
119 if ($this->socket->enableCrypto() === false)
120 {
121 throw new Http\NetworkException($request, 'Error establishing an SSL connection.');
122 }
123
124 $this->handshakeTimer?->stop();
125 }
126
127 // the socket is ready - can write headers
128 $this->sendHeaders();
129
130 // prepare the body for sending
131 $body = $request->getBody();
132 if ($body->isSeekable())
133 {
134 $body->rewind();
135 }
136
137 $this->state = self::HEADERS_SENT;
138 }
139
140 break;
141
142 case self::CONNECT_SENT:
143 if ($this->receiveHeaders())
144 {
145 $this->log("<<<CONNECT\n{headers}\n", Web\HttpDebug::REQUEST_HEADERS, ['headers' => $this->responseHeaders]);
146
147 // response to CONNECT from the proxy
148 $headers = Web\HttpHeaders::createFromString($this->responseHeaders);
149
150 if (($status = $headers->getStatus()) >= 200 && $status < 300)
151 {
152 $this->responseHeaders = '';
153
154 $this->state = self::CONNECT_RECEIVED;
155 }
156 else
157 {
158 throw new Http\NetworkException($request, 'Error receiving the CONNECT response from the proxy: ' . $headers->getStatus() . ' ' . $headers->getReasonPhrase());
159 }
160 }
161
162 break;
163
164 case self::HEADERS_SENT:
165 // it's time to send the request body asynchronously
166 if ($this->sendBody())
167 {
168 // sent all the body
169 $this->requestTimer?->stop();
170
171 $this->state = self::BODY_SENT;
172 }
173
174 break;
175
176 case self::BODY_SENT:
177 // request is sent now - switching to reading
178 if ($this->receiveHeaders())
179 {
180 // all headers received
181 $this->log("\n<<<RESPONSE\n{headers}\n", Web\HttpDebug::RESPONSE_HEADERS, ['headers' => $this->responseHeaders]);
182
183 // build the response for the next stage
184 $this->response = $this->responseBuilder->createFromString($this->responseHeaders);
185
186 $fetchBody = $this->waitResponse;
187
188 if ($this->shouldFetchBody !== null)
189 {
190 $fetchBody = call_user_func($this->shouldFetchBody, $this->response, $request);
191 }
192
193 if ($fetchBody)
194 {
195 $this->state = self::HEADERS_RECEIVED;
196 }
197 else
198 {
199 // we don't want a body, just fulfil a promise with response headers
200 $this->state = self::BODY_RECEIVED;
201 }
202 }
203
204 break;
205
206 case self::HEADERS_RECEIVED:
207 // receiving a response body
208 if ($this->receiveBody())
209 {
210 // have read all the body
211 $this->state = self::BODY_RECEIVED;
212 }
213
214 break;
215 }
216
217 if ($this->state == self::BODY_RECEIVED)
218 {
219 $this->socket->close();
220
221 if ($fetchBody)
222 {
223 if ($this->debugLevel & Web\HttpDebug::RESPONSE_BODY)
224 {
225 $this->log($this->response->getBody(), Web\HttpDebug::RESPONSE_BODY);
226 }
227
228 // need to ajust the response headers (PSR-18)
229 $this->response->adjustHeaders();
230 }
231
232 $this->totalTimer?->stop();
233
234 $this->logDiagnostics();
235
236 // we have a result!
237 $promise?->fulfill($this->response);
238 }
239 }
240 catch (Http\ClientException $exception)
241 {
242 $this->socket->close();
243
244 $promise?->reject($exception);
245
246 $this->getLogger()?->error($exception->getMessage() . "\n");
247
248 if ($promise === null)
249 {
250 throw $exception;
251 }
252 }
253 }
254
255 protected function write(string $data, string $error)
256 {
257 try
258 {
259 $result = $this->socket->write($data);
260 }
261 catch (\RuntimeException $e)
262 {
263 throw new Http\NetworkException($this->request, $error . ' ' . $e->getMessage());
264 }
265
266 return $result;
267 }
268
269 protected function sendConnect(): void
270 {
272 $uri = $request->getUri();
273 $host = $uri->getHost();
274
275 $requestHeaders = 'CONNECT ' . $host . ':' . $uri->getPort() . ' HTTP/1.1' . "\r\n"
276 . 'Host: ' . $host . "\r\n"
277 ;
278
279 if ($request->hasHeader('Proxy-Authorization'))
280 {
281 $requestHeaders .= 'Proxy-Authorization' . ': ' . $request->getHeaderLine('Proxy-Authorization') . "\r\n";
282 $this->request = $request->withoutHeader('Proxy-Authorization');
283 }
284
285 $requestHeaders .= "\r\n";
286
287 $this->log(">>>CONNECT\n{headers}", Web\HttpDebug::REQUEST_HEADERS, ['headers' => $requestHeaders]);
288
289 if ($this->async)
290 {
291 // blocking is critical for headers
292 $this->socket->setBlocking();
293 }
294
295 $this->write($requestHeaders, 'Error sending CONNECT to proxy.');
296
297 if ($this->async)
298 {
299 $this->socket->setBlocking(false);
300 }
301 }
302
303 protected function sendHeaders(): void
304 {
306 $uri = $request->getUri();
307
308 // Full URI for HTTP proxies
309 $target = ($this->useProxy && $uri->getScheme() === 'http' ? (string)$uri : $request->getRequestTarget());
310
311 $requestHeaders = $request->getMethod() . ' ' . $target . ' HTTP/' . $request->getProtocolVersion() . "\r\n";
312
313 foreach ($request->getHeaders() as $name => $values)
314 {
315 foreach ($values as $value)
316 {
317 $requestHeaders .= $name . ': ' . $value . "\r\n";
318 }
319 }
320
321 $requestHeaders .= "\r\n";
322
323 $this->log(">>>REQUEST\n{headers}", Web\HttpDebug::REQUEST_HEADERS, ['headers' => $requestHeaders]);
324
325 if ($this->async)
326 {
327 // blocking is critical for headers
328 $this->socket->setBlocking();
329 }
330
331 $this->write($requestHeaders, 'Error sending the message headers.');
332
333 if ($this->async)
334 {
335 $this->socket->setBlocking(false);
336 }
337 }
338
339 protected function sendBody(): bool
340 {
342 $body = $request->getBody();
343
344 if (!$body->eof() || $this->requestBodyPart !== '')
345 {
346 if (!$body->eof() && strlen($this->requestBodyPart) < self::BUF_BODY_LEN)
347 {
348 $part = $body->read(self::BUF_BODY_LEN);
349 $this->requestBodyPart .= $part;
350 $this->log($part, Web\HttpDebug::REQUEST_BODY);
351 }
352
353 $result = $this->write($this->requestBodyPart, 'Error sending the message body.');
354
355 $this->requestBodyPart = substr($this->requestBodyPart, $result);
356 }
357
358 return ($body->eof() && $this->requestBodyPart === '');
359 }
360
361 protected function receiveHeaders(): bool
362 {
363 while (!$this->socket->eof())
364 {
365 try
366 {
367 $line = $this->socket->gets();
368 }
369 catch (\RuntimeException $e)
370 {
371 throw new Http\NetworkException($this->request, $e->getMessage());
372 }
373
374 if ($line === false)
375 {
376 // no data in the socket or error(?)
377 return false;
378 }
379
380 if ($line === "\r\n")
381 {
382 // got all headers
383 return true;
384 }
385
386 $this->responseHeaders .= $line;
387 }
388
389 if ($this->responseHeaders === '')
390 {
391 throw new Http\NetworkException($this->request, 'Empty response from the server.');
392 }
393
394 return true;
395 }
396
397 protected function receiveBody(): bool
398 {
400 $headers = $this->response->getHeadersCollection();
401 $body = $this->response->getBody();
402
403 $length = $headers->get('Content-Length');
404
405 if ($length !== null && $length <= 0)
406 {
407 // nothing to read
408 return true;
409 }
410
411 while (!$this->socket->eof())
412 {
413 if ($length !== null)
414 {
415 $bufLength = min($length, self::BUF_READ_LEN);
416 }
417 else
418 {
419 $bufLength = self::BUF_READ_LEN;
420 }
421
422 try
423 {
424 $buf = $this->socket->read($bufLength);
425 }
426 catch (\RuntimeException)
427 {
428 throw new Http\NetworkException($request, 'Stream reading error.');
429 }
430
431 if ($buf === '')
432 {
433 // no data in the stream yet
434 return false;
435 }
436
437 try
438 {
439 $body->write($buf);
440 }
441 catch (\RuntimeException)
442 {
443 throw new Http\NetworkException($request, 'Error writing to response body stream.');
444 }
445
446 if ($this->bodyLengthMax > 0 && $body->getSize() > $this->bodyLengthMax)
447 {
448 throw new Http\NetworkException($request, 'Maximum content length has been reached. Breaking reading.');
449 }
450
451 if ($length !== null)
452 {
453 $length -= strlen($buf);
454 if ($length <= 0)
455 {
456 // have read all the body
457 return true;
458 }
459 }
460 }
461
462 return true;
463 }
464
465 protected function createSocket(array $options): Stream
466 {
467 $proxyHost = (string)($options['proxyHost'] ?? '');
468 $proxyPort = (int)($options['proxyPort'] ?? 80);
469 $contextOptions = $options['contextOptions'] ?? [];
470
471 $uri = $this->request->getUri();
472
473 if ($proxyHost != '')
474 {
475 $host = $proxyHost;
476 $port = $proxyPort;
477
478 // set original host to match a sertificate for proxy tunneling
479 $contextOptions['ssl']['peer_name'] = $uri->getHost();
480 }
481 else
482 {
483 $host = $uri->getHost();
484 $port = $uri->getPort();
485
486 if (isset($options['effectiveIp']) && $options['effectiveIp'] instanceof IpAddress)
487 {
488 // set original host to match a sertificate
489 $contextOptions['ssl']['peer_name'] = $host;
490
491 // resolved in HttpClient if private IPs were disabled
492 $host = $options['effectiveIp']->get();
493 }
494 }
495
496 $socket = new Stream(
497 'tcp://' . $host . ':' . $port,
498 [
499 'socketTimeout' => $options['socketTimeout'] ?? null,
500 'streamTimeout' => $options['streamTimeout'] ?? null,
501 'contextOptions' => $contextOptions,
502 'async' => $options['async'] ?? null,
503 ],
504 );
505
506 return $socket;
507 }
508
509 public function getState(): int
510 {
511 return $this->state;
512 }
513
519 public function getSocket(): Stream
520 {
521 return $this->socket;
522 }
523
524 protected function initTimers(): void
525 {
526 // $this->debugLevel can be set from Diag\Logger::create()
527 if ($this->getLogger())
528 {
529 if ($this->debugLevel & HttpDebug::DIAGNOSTICS)
530 {
531 $this->connectTimer = (new StopWatch())->start();
532 if ($this->request->getUri()->getScheme() === 'https')
533 {
534 $this->handshakeTimer = (new StopWatch())->start();
535 }
536 $this->requestTimer = (new StopWatch())->start();
537 $this->totalTimer = (new StopWatch())->start();
538 }
539 }
540 }
541
542 protected function getDiagnostics(): array
543 {
544 return [
545 'connect' => $this->connectTimer?->get() ?? 0.0,
546 'handshake' => $this->handshakeTimer?->get() ?? 0.0,
547 'request' => $this->requestTimer?->get() ?? 0.0,
548 'total' => $this->totalTimer?->get() ?? 0.0,
549 ];
550 }
551
552 public function execute(): Http\Response
553 {
554 while ($this->state != self::BODY_RECEIVED)
555 {
556 // request in a blocking way
557 $this->process();
558 }
559
560 return $this->response;
561 }
562}
Определения response.php:5
shouldFetchBody(callable $callback)
Определения handler.php:128
log(string $logMessage, int $level, array $context=[])
Определения handler.php:90
RequestInterface $request
Определения handler.php:26
Response $response
Определения handler.php:30
bool $waitResponse
Определения handler.php:22
logDiagnostics()
Определения handler.php:108
ResponseBuilderInterface $responseBuilder
Определения handler.php:27
__construct(RequestInterface $request, Http\ResponseBuilderInterface $responseBuilder, array $options=[])
Определения handler.php:48
StopWatch $totalTimer
Определения handler.php:41
process(?Http\Promise $promise=null)
Определения handler.php:66
const BODY_RECEIVED
Определения handler.php:29
const CONNECT_RECEIVED
Определения handler.php:31
const HEADERS_RECEIVED
Определения handler.php:28
write(string $data, string $error)
Определения handler.php:255
string $requestBodyPart
Определения handler.php:36
StopWatch $requestTimer
Определения handler.php:40
StopWatch $connectTimer
Определения handler.php:38
createSocket(array $options)
Определения handler.php:465
StopWatch $handshakeTimer
Определения handler.php:39
const CONNECT
Определения httpdebug.php:20
const RESPONSE_BODY
Определения httpdebug.php:18
const DIAGNOSTICS
Определения httpdebug.php:21
const REQUEST_BODY
Определения httpdebug.php:15
const RESPONSE_HEADERS
Определения httpdebug.php:17
const REQUEST_HEADERS
Определения httpdebug.php:14
static createFromString(string $response)
Определения httpheaders.php:391
Определения uri.php:17
$options
Определения commerceml2.php:49
$data['IS_AVAILABLE']
Определения .description.php:13
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
if(file_exists($_SERVER['DOCUMENT_ROOT'] . "/urlrewrite.php")) $uri
Определения urlrewrite.php:61
$status
Определения session.php:10
$name
Определения menu_edit.php:35
Определения cookie.php:3
$host
Определения mysql_to_pgsql.php:32
$error
Определения subscription_card_product.php:20