HTTP-Clients
Standardmäßig cURL, jeder PSR-18-Client, wenn Sie lieber Ihren eigenen verwenden, und ein Fake für Tests.
Der Standard
Ohne httpClient: laufen Anfragen über OpenEmail\Http\CurlHttpClient, der nichts als die curl-Erweiterung braucht. Er hält ein cURL-Handle pro Client, sodass eine zweite Anfrage die Verbindung wiederverwendet, die die erste geöffnet hat, und die Seite Konfiguration listet seine Optionen für Proxys, Zertifizierungsstellen und zusätzliche cURL-Einstellungen.
Was auch immer die Bytes bewegt, der Client selbst erledigt alles andere: die Zugangsdaten, die Idempotency-Keys, die Wiederholungen und ihr Backoff, das Timeout für jeden Versuch und die Exception für jeden Fehlschlag. Ein HTTP-Client sendet nur eine Anfrage und gibt die Antwort zurück.
Jeder PSR-18-Client
OpenEmail\Http\Psr18HttpClient kapselt jeden Client, der PSR-18 implementiert, etwa Guzzle oder Symfony HttpClient, sodass Anfragen über den Client laufen, den Ihre Anwendung bereits konfiguriert, mit seiner Middleware, seinem Logging und seinen Proxy-Einstellungen.
use GuzzleHttp\Client;use OpenEmail\Http\Psr18HttpClient;use OpenEmail\OpenEmail; $client = new OpenEmail(httpClient: new Psr18HttpClient(new Client())); $client->me->ping();use OpenEmail\Http\Psr18HttpClient;use OpenEmail\OpenEmail;use Symfony\Component\HttpClient\HttpClient;use Symfony\Component\HttpClient\Psr18Client; $http = new Psr18Client(HttpClient::create(['timeout' => 30, 'max_redirects' => 0])); $client = new OpenEmail(httpClient: new Psr18HttpClient($http)); $client->me->ping();PSR-18 baut seine Anfragen mit PSR-17-Factories. Der Adapter verwendet den Client selbst, wenn er auch eine Factory ist, wie Symfonys Psr18Client, dann php-http/discovery, wenn es installiert ist, dann nyholm/psr7 oder die eigenen von Guzzle, und wirft eine InvalidArgumentException, die nennt, was zu installieren ist, wenn er keine findet. Übergeben Sie requestFactory: und streamFactory:, um sie selbst zu wählen. Symfonys Psr18Client braucht außerdem die PSR-18-Interfaces und eine PSR-17-Implementierung, installieren Sie ihn also mit composer require symfony/http-client psr/http-client nyholm/psr7.
use GuzzleHttp\Client;use Nyholm\Psr7\Factory\Psr17Factory;use OpenEmail\Http\Psr18HttpClient;use OpenEmail\OpenEmail; $factory = new Psr17Factory(); $client = new OpenEmail(httpClient: new Psr18HttpClient(new Client(), requestFactory: $factory, streamFactory: $factory)); $client->me->ping();| Thema | Was passiert |
|---|---|
| Timeouts | Guzzle erhält das timeout: des Clients bei jeder Anfrage. PSR-18 bietet keinen Weg, einem anderen Client ein Timeout zu übergeben, setzen Sie also eines auf dem Client, den Sie kapseln, wie es HttpClient::create(['timeout' => 30, 'max_redirects' => 0]) oben tut, sonst kann eine Anfrage, die nie antwortet, ewig warten. |
| Fehlerstatus | Ein PSR-18-Client gibt 4xx- und 5xx-Antworten zurück, statt zu werfen, und der Adapter weist Guzzle an, dasselbe zu tun, sodass jede Ablehnung trotzdem zur richtigen ApiException wird. |
| Keine Antwort | Eine ClientExceptionInterface aus dem gekapselten Client wird zu einer NetworkException, die ihre Meldung behält, aber nicht die Exception selbst, weil diese die Anfrage samt ihrem Authorization-Header enthält. isTimeout() ist true, wenn die Meldung sagt, dass die Anfrage in ein Timeout lief, und bei jedem Client außer Guzzle sagt die Meldung, dass es das eigene Timeout des gekapselten Clients war. |
| Weiterleitungen | Guzzle wird angewiesen, ihnen nicht zu folgen, und das Symfony-Beispiel setzt max_redirects auf 0, sodass eine Weiterleitung zu einer ApiException wird, wie beim standardmäßigen cURL-Client. |
Ihr eigener Client
httpClient: nimmt alles, was OpenEmail\Http\HttpClient implementiert, ein Interface mit einer Methode: send(HttpRequest $request): HttpResponse. Geben Sie die Antwort für jeden Status zurück, 4xx und 5xx eingeschlossen, und werfen Sie nur, wenn keine Antwort ankam.
| Klasse | Was es trägt |
|---|---|
| HttpRequest | method, url, headers als Array aus Name und Wert, body als String oder null und timeout in Sekunden, oder null für keins. header($name) liest einen Header in beliebiger Schreibweise. var_dump(), print_r() und json_encode() zeigen den Authorization-Header als [redacted], aber var_export() und Symfonys dump() geben headers unverändert aus. Protokollieren Sie eine Anfrage daher nie mit diesen beiden. |
| HttpResponse | Wird als new HttpResponse($status, $headers, $body) gebaut. Die Header-Namen werden kleingeschrieben, und header($name) liest einen in beliebiger Schreibweise. |
| HttpClientException | Werfen Sie sie aus send, wenn keine Antwort ankam. new HttpClientException($message, timeout: true) kennzeichnet ein Timeout. |
Testen ohne Netzwerk
Ein Fake-Client zeichnet auf, was hinausgegangen wäre, und antwortet mit dem, was der Test braucht. So verlässt nichts den Rechner, und der Test kann genau prüfen, was gesendet wurde.
use OpenEmail\Http\HttpClient;use OpenEmail\Http\HttpRequest;use OpenEmail\Http\HttpResponse;use OpenEmail\OpenEmail; $fake = new class implements HttpClient { public array $requests = []; public function send(HttpRequest $request): HttpResponse { $this->requests[] = $request; return new HttpResponse( 200, ['content-type' => 'application/json'], json_encode(['id' => 'msg_test', 'status' => 'sent', 'replayed' => false], JSON_THROW_ON_ERROR), ); }}; $testClient = new OpenEmail(apiKey: 'oe_test_fake', httpClient: $fake, maxRetries: 0, disableUpdateNotice: true); $sent = $testClient->emails->send([ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Hi', 'text' => 'Hello',]); $request = $fake->requests[0]; echo $sent['status'], ' ', $request->method, ' ', $request->url, ' ', $request->header('Idempotency-Key'), PHP_EOL;var_dump($request);- Geben Sie einen Status außerhalb von 2xx mit dem Fehlerumschlag der API als Body zurück, etwa
{"error": {"type": "validation_error", "code": "invalid_parameter", "message": "..."}}, um die passende Unterklasse vonApiExceptionzu erhalten. - Werfen Sie
new HttpClientException('timed out', timeout: true)aussend, um eineNetworkExceptionzu erhalten, derenisTimeout()true ist. Jede andereException, etwa eineRuntimeException, wird zu einerNetworkException, derenisTimeout()false ist. - Eine
LogicExceptionund jederError, etwa einTypeError, gelten als Fehler im Fake. Sie werden unverändert geworfen und nie wiederholt.
Erzeugen Sie einen Test-Client mit maxRetries: 0, wenn Sie Fehlschläge skripten. Sonst wird ein wiederholbarer Status oder ein Netzwerkfehler bei einem Aufruf, der gefahrlos wiederholt werden kann, dreimal versucht, mit echten Wartezeiten dazwischen.