Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
205
src/Framework/AsyncExamples/Http/AsyncHttpClient.php
Normal file
205
src/Framework/AsyncExamples/Http/AsyncHttpClient.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\AsyncExamples\Http;
|
||||
|
||||
use App\Framework\Async\AsyncPool;
|
||||
use App\Framework\Async\FiberManager;
|
||||
use Fiber;
|
||||
|
||||
/**
|
||||
* Asynchroner HTTP-Client mit Fiber-Unterstützung
|
||||
*/
|
||||
final class AsyncHttpClient
|
||||
{
|
||||
private array $defaultOptions = [
|
||||
'timeout' => 30,
|
||||
'connect_timeout' => 10,
|
||||
'follow_redirects' => true,
|
||||
'max_redirects' => 5,
|
||||
'user_agent' => 'AsyncHttpClient/1.0',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly FiberManager $fiberManager = new FiberManager(),
|
||||
array $defaultOptions = []
|
||||
) {
|
||||
$this->defaultOptions = array_merge($this->defaultOptions, $defaultOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet einen GET-Request
|
||||
*/
|
||||
public function get(string $url, array $headers = [], array $options = []): HttpResponse
|
||||
{
|
||||
return $this->request('GET', $url, null, $headers, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet einen POST-Request
|
||||
*/
|
||||
public function post(string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
|
||||
{
|
||||
return $this->request('POST', $url, $data, $headers, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet einen PUT-Request
|
||||
*/
|
||||
public function put(string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
|
||||
{
|
||||
return $this->request('PUT', $url, $data, $headers, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet einen DELETE-Request
|
||||
*/
|
||||
public function delete(string $url, array $headers = [], array $options = []): HttpResponse
|
||||
{
|
||||
return $this->request('DELETE', $url, null, $headers, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet mehrere Requests parallel
|
||||
*
|
||||
* @param array<string, array> $requests ['key' => ['method' => 'GET', 'url' => '...', ...]]
|
||||
* @return array<string, HttpResponse>
|
||||
*/
|
||||
public function requestMultiple(array $requests): array
|
||||
{
|
||||
$operations = [];
|
||||
foreach ($requests as $key => $request) {
|
||||
$operations[$key] = fn () => $this->request(
|
||||
$request['method'] ?? 'GET',
|
||||
$request['url'],
|
||||
$request['data'] ?? null,
|
||||
$request['headers'] ?? [],
|
||||
$request['options'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
return $this->fiberManager->batch($operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sendet Requests mit begrenzter Parallelität
|
||||
*/
|
||||
public function requestBatch(array $requests, int $maxConcurrency = 10): array
|
||||
{
|
||||
$pool = new AsyncPool($maxConcurrency, $this->fiberManager);
|
||||
|
||||
foreach ($requests as $key => $request) {
|
||||
$pool->add(
|
||||
fn () => $this->request(
|
||||
$request['method'] ?? 'GET',
|
||||
$request['url'],
|
||||
$request['data'] ?? null,
|
||||
$request['headers'] ?? [],
|
||||
$request['options'] ?? []
|
||||
),
|
||||
$key
|
||||
);
|
||||
}
|
||||
|
||||
return $pool->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hauptmethode für HTTP-Requests
|
||||
*/
|
||||
private function request(string $method, string $url, mixed $data = null, array $headers = [], array $options = []): HttpResponse
|
||||
{
|
||||
$options = array_merge($this->defaultOptions, $options);
|
||||
|
||||
$context = $this->createContext($method, $data, $headers, $options);
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$content = @file_get_contents($url, false, $context);
|
||||
|
||||
if ($content === false) {
|
||||
$error = error_get_last();
|
||||
|
||||
throw new HttpException("HTTP request failed: " . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
|
||||
$responseHeaders = $this->parseHeaders($http_response_header ?? []);
|
||||
$statusCode = $this->extractStatusCode($http_response_header ?? []);
|
||||
|
||||
return new HttpResponse(
|
||||
statusCode: $statusCode,
|
||||
headers: $responseHeaders,
|
||||
body: $content,
|
||||
requestTime: microtime(true) - $startTime
|
||||
);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
throw new HttpException("HTTP request failed: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
private function createContext(string $method, mixed $data, array $headers, array $options)
|
||||
{
|
||||
$contextOptions = [
|
||||
'http' => [
|
||||
'method' => $method,
|
||||
'timeout' => $options['timeout'],
|
||||
'user_agent' => $options['user_agent'],
|
||||
'follow_location' => $options['follow_redirects'],
|
||||
'max_redirects' => $options['max_redirects'],
|
||||
'ignore_errors' => true,
|
||||
],
|
||||
];
|
||||
|
||||
if ($data !== null) {
|
||||
if (is_array($data) || is_object($data)) {
|
||||
$contextOptions['http']['content'] = json_encode($data);
|
||||
$headers['Content-Type'] = 'application/json';
|
||||
} else {
|
||||
$contextOptions['http']['content'] = (string)$data;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($headers)) {
|
||||
$headerStrings = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
$headerStrings[] = "$key: $value";
|
||||
}
|
||||
$contextOptions['http']['header'] = implode("\r\n", $headerStrings);
|
||||
}
|
||||
|
||||
return stream_context_create($contextOptions);
|
||||
}
|
||||
|
||||
private function parseHeaders(array $httpResponseHeader): array
|
||||
{
|
||||
$headers = [];
|
||||
foreach ($httpResponseHeader as $header) {
|
||||
if (strpos($header, ':') !== false) {
|
||||
[$key, $value] = explode(':', $header, 2);
|
||||
$headers[trim($key)] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
private function extractStatusCode(array $httpResponseHeader): int
|
||||
{
|
||||
if (empty($httpResponseHeader)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$statusLine = $httpResponseHeader[0];
|
||||
if (preg_match('/HTTP\/\d+\.\d+\s+(\d+)/', $statusLine, $matches)) {
|
||||
return (int)$matches[1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
29
src/Framework/AsyncExamples/Http/HttpException.php
Normal file
29
src/Framework/AsyncExamples/Http/HttpException.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\AsyncExamples\Http;
|
||||
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
|
||||
/**
|
||||
* Exception für HTTP-Fehler
|
||||
*/
|
||||
class HttpException extends FrameworkException
|
||||
{
|
||||
public function __construct(
|
||||
string $message = '',
|
||||
int $code = 0,
|
||||
?\Throwable $previous = null,
|
||||
public readonly ?HttpResponse $response = null
|
||||
) {
|
||||
$context = ExceptionContext::forOperation('http.request', 'http')
|
||||
->withData([
|
||||
'response_code' => $response?->statusCode ?? null,
|
||||
'response_headers' => $response?->headers ?? null,
|
||||
]);
|
||||
|
||||
parent::__construct($message, $context, $code, $previous);
|
||||
}
|
||||
}
|
||||
64
src/Framework/AsyncExamples/Http/HttpResponse.php
Normal file
64
src/Framework/AsyncExamples/Http/HttpResponse.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\AsyncExamples\Http;
|
||||
|
||||
/**
|
||||
* HTTP Response Objekt
|
||||
*/
|
||||
final readonly class HttpResponse
|
||||
{
|
||||
public function __construct(
|
||||
public int $statusCode,
|
||||
public array $headers,
|
||||
public string $body,
|
||||
public float $requestTime
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft ob Response erfolgreich war
|
||||
*/
|
||||
public function isSuccess(): bool
|
||||
{
|
||||
return $this->statusCode >= 200 && $this->statusCode < 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dekodiert JSON Response
|
||||
*/
|
||||
public function json(): array
|
||||
{
|
||||
return json_decode($this->body, true) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt spezifischen Header zurück
|
||||
*/
|
||||
public function getHeader(string $name): ?string
|
||||
{
|
||||
return $this->headers[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Content-Type zurück
|
||||
*/
|
||||
public function getContentType(): ?string
|
||||
{
|
||||
return $this->getHeader('Content-Type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert zu Array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'status_code' => $this->statusCode,
|
||||
'headers' => $this->headers,
|
||||
'body' => $this->body,
|
||||
'request_time' => $this->requestTime,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user