gRPC
Rapira obsługuje unarne wywołania RPC w PHP. Jeden nasłuch akceptuje wywołania gRPC, gRPC-Web i Connect przez HTTP/1.1 lub nieszyfrowany HTTP/2, na gniazdach TCP lub Unix. Wywołanie unarne ma jeden komunikat żądania i jeden komunikat odpowiedzi.
Pula gRPC używa trybu Dispatcher. Rapira obsługuje transport i przekazuje do PHP binarne komunikaty protobuf. Każdy worker PHP obsługuje jedno wywołanie naraz. Metody strumieniowe nie są obsługiwane. Nasłuch nie obsługuje TLS.
Uruchomienie usługi echo
Zainstaluj Rapirę. Zainstaluj buf, aby zbudować zestaw deskryptorów, oraz grpcurl, aby wykonać polecenia klienta. Ten przykład zwraca bajty żądania jako odpowiedź. Nie wymaga wygenerowanych klas PHP ani rozszerzenia gRPC dla PHP.
Utwórz następującą strukturę katalogów:
app/
├── proto/
│ └── echo.proto
├── grpc.php
└── rapira.tomlDefinicja usługi
Zapisz ten schemat jako proto/echo.proto:
syntax = "proto3";
package example.v1;
option php_namespace = "Example\\V1";
option php_metadata_namespace = "Example\\Metadata";
service Echo {
rpc Echo(EchoMessage) returns (EchoMessage);
}
message EchoMessage {
string text = 1;
}Trasa to /example.v1.Echo/Echo. Kontekst PHP udostępnia metodę jako example.v1.Echo/Echo, bez początkowego ukośnika.
Budowanie zestawu deskryptorów
Rapira odczytuje schemat z zestawu deskryptorów: binarnego google.protobuf.FileDescriptorSet, który zawiera wszystkie importowane pliki. Zbuduj go w app/:
buf build proto --as-file-descriptor-set -o api.binpbprotoc również może go zbudować. Dodaj --include_imports, ponieważ bez tej opcji protoc nie dołącza importowanych plików:
protoc --include_imports --descriptor_set_out=api.binpb -I proto proto/echo.protoRapira nie wymaga programu protoc podczas działania.
Dyspozytor PHP
Zapisz ten skrypt jako grpc.php:
<?php
use Rapira\Exception\ClosedException;
use Rapira\Exception\WorkDiscardedException;
$dispatcher = Rapira\get_dispatcher();
try {
while (true) {
$call = $dispatcher->receive();
try {
$metadata = $call->getResponseMetadata();
$metadata->addHeader('x-worker', (string) getmypid());
$metadata->addTrailer('x-result', 'echoed');
$call->respond($call->getMessage());
} catch (WorkDiscardedException) {
continue;
}
}
} catch (ClosedException) {
return;
}Żądanie i odpowiedź mają ten sam typ komunikatu, więc handler może zwrócić bajty bezpośrednio. ClosedException kończy pętlę podczas zatrzymywania. WorkDiscardedException oznacza, że host już anulował wywołanie.
Konfiguracja nasłuchu i puli
Zapisz tę konfigurację jako rapira.toml:
[grpc]
listen = "127.0.0.1:50051"
descriptor_set = "api.binpb"
reflection = true
[grpc.pool]
entrypoint = "grpc.php"
mode = "dispatcher"
processes = 2descriptor_set używa katalogu pliku konfiguracyjnego jako podstawy. Konfiguracja obsługująca tylko gRPC nie wymaga sekcji [http].
Uruchom serwer z app/:
rapira serve rapira.tomlWywołanie usługi
Wyświetl listę usług w drugim terminalu:
grpcurl -plaintext 127.0.0.1:50051 listWywołaj metodę echo:
grpcurl -plaintext -d '{"text":"hello"}' 127.0.0.1:50051 example.v1.Echo/EchoOdpowiedź:
{
"text": "hello"
}grpcurl pobiera schemat przez refleksję. Dodaj -v przed adresem, aby zobaczyć nagłówki i nagłówki końcowe odpowiedzi.
Klient Connect może wysłać JSON. Rapira zamienia żądanie JSON na binarny protobuf, zanim PHP je otrzyma, i zamienia binarną odpowiedź z powrotem na JSON:
curl -H 'Content-Type: application/json' -d '{"text":"hello"}' http://127.0.0.1:50051/example.v1.Echo/EchoUżycie wygenerowanych komunikatów PHP
Wygeneruj klasy PHP, gdy handler musi odczytywać lub zmieniać pola komunikatu. Zainstaluj protoc i Composer na potrzeby tego etapu budowania.
Uruchom te polecenia w app/:
composer require google/protobuf
mkdir -p generated
protoc --proto_path=proto --php_out=generated proto/echo.protoDodaj te mapowania przestrzeni nazw do obiektu autoload.psr-4 w composer.json:
{
"autoload": {
"psr-4": {
"Example\\V1\\": "generated/Example/V1/",
"Example\\Metadata\\": "generated/Example/Metadata/"
}
}
}Zaktualizuj autoloader:
composer dump-autoloadWczytaj go przed pętlą dyspozytora w grpc.php:
require __DIR__ . '/vendor/autoload.php';Zastąp linię z respond() w zagnieżdżonym bloku try następującym kodem:
$message = new Example\V1\EchoMessage();
$message->mergeFromString($call->getMessage());
$message->setText(strtoupper($message->getText()));
$call->respond($message->serializeToString());Uruchom ponownie przykładowy serwer. To samo wywołanie klienta zwraca teraz {"text":"HELLO"}. W handlerze aplikacji przechwytuj wyjątki parsowania protobuf i wysyłaj StatusCode::InvalidArgument.
Przewodnik po wygenerowanym kodzie PHP opisuje metody dostępu do pól i serializację. To API serwera nie wymaga rozszerzenia gRPC dla PHP.
Kontrakt dyspozytora
Rapira\get_dispatcher() zwraca Rapira\Grpc\GrpcDispatcher w workerze gRPC. Zainicjalizuj autoloader i współdzielone usługi aplikacji przed pętlą.
| API | Działanie |
|---|---|
name() | Zwraca "grpc". |
getInfo(): GrpcDispatcherInfo | Zwraca migawkę liczników tego workera. pendingCount() liczy oczekujące wywołania. activeCount() wynosi 0 lub 1. |
receive(int $timeout = -1) | Zwraca następny UnaryCall. Limit oczekiwania jest podany w mikrosekundach. -1 oznacza oczekiwanie bez limitu. Przekroczenie limitu rzuca Rapira\Exception\TimeoutException. |
tryReceive() | Zwraca UnaryCall lub null, gdy żadne wywołanie nie czeka. Nie czeka. |
getServices() | Zwraca listę obsługiwanych usług oraz ich metod, typów wejściowych, typów wyjściowych i rodzajów metod. Metody strumieniowe są na liście. Dostępne przed pierwszym wywołaniem. |
$call->getContext() | Zwraca metodę, metadane, adres klienta, protokół, czas odebrania i termin zakończenia. |
$call->getMessage() | Zwraca komunikat żądania jako binarny protobuf w ciągu znaków PHP. |
$call->respond(string $message) | Kończy wywołanie jedną zserializowaną odpowiedzią protobuf. |
$call->fail(Status $status) | Kończy wywołanie statusem błędu gRPC. |
$call->isCancelled() | Informuje o anulowaniu przez klienta, zamknięciu połączenia lub przekroczeniu terminu zakończenia. |
$call->isFinalized() | Informuje, czy wywołanie zostało zakończone. |
Zakończ bieżące wywołanie przed odebraniem następnego. Gdy wywołanie jest otwarte, receive() rzuca \Error. Powtórne zakończenie rzuca Rapira\Exception\AlreadyFinalizedError. Odpowiedź po anulowaniu rzuca WorkDiscardedException.
Wywołanie, którego PHP nie zakończy, zostaje utracone. Klient otrzymuje wtedy INTERNAL z komunikatem internal error. Nieprzechwycony wyjątek także powoduje utratę wywołania, a klient nie widzi jego komunikatu.
Użyj $call->getContext()->method do wyboru handlera, gdy schemat definiuje kilka metod. $call->getContext()->protocol ma wartość Grpc, GrpcWeb lub Connect. Usuń stan aplikacji związany z wywołaniem przed następną iteracją. Dyspozytor nie wypełnia zmiennych superglobalnych HTTP.
Zwracanie błędów i szczegółów
Użyj fail() dla oczekiwanego błędu aplikacji. Wykonaj ten kod w handlerze bieżącego wywołania:
$call->fail(new Rapira\Grpc\Status(
Rapira\Grpc\StatusCode::InvalidArgument,
'text is required',
));StatusCode zawiera 16 kodów błędów gRPC. Nie ma przypadku Ok. Udane respond() wysyła status OK. fail() to jedyny sposób wysłania statusu błędu.
Opcjonalny trzeci argument Status to lista obiektów Rapira\Grpc\ErrorDetail. Każdy obiekt przechowuje URL typu protobuf i bajty zserializowanego komunikatu. Dla gRPC i gRPC-Web Rapira wysyła szczegóły w grpc-status-details-bin. Dla Connect wysyła je w treści błędu JSON.
Rapira nie przechwytuje Rapira\Grpc\Exception\GrpcException. Przechwyć go i przekaż jego właściwość $status do fail().
Klient otrzymuje UNAVAILABLE, gdy Rapira odrzuci wywołanie, zanim PHP je otrzyma. Dzieje się tak, gdy kolejka workerów pozostaje pełna przez 30 sekund, gdy pula się zatrzymuje lub gdy start PHP w workerze się nie powiódł. Interceptor także może odrzucić wywołanie, zanim PHP je otrzyma. Interceptor ustawia status, na przykład UNAUTHENTICATED. Zobacz Interceptory.
Metadane
Odczytuj metadane żądania z $call->getContext()->metadata. values($name) zwraca wszystkie wartości dla nazwy w kolejności nadejścia, bez rozróżniania wielkości liter w nazwie. Tablica entries, dostępna tylko do odczytu, przechowuje nazwy małymi literami.
Rapira usuwa z metadanych żądania nazwy transportowe, na przykład grpc-timeout, content-type i te. Odrzuca wartość tekstową, która nie jest drukowalnym ASCII. Dla nazwy zakończonej na -bin Rapira dzieli wartość według , i dekoduje każdy fragment z base64. PHP otrzymuje surowe bajty. Fragment, którego nie da się zdekodować, zostaje odrzucony.
Dodawaj metadane odpowiedzi przed respond() lub fail():
$requestId = $call->getContext()->metadata->values('x-request-id')[0] ?? '';
$metadata = $call->getResponseMetadata();
$metadata->addHeader('x-request-id', $requestId);
$metadata->addBinaryHeader('x-token-bin', "\x00\xff");
$metadata->addTrailer('x-result', 'completed');addHeader() i addTrailer() przyjmują wartości złożone z drukowalnych znaków ASCII. Pusta wartość jest dozwolona. Podczas wysyłania Rapira usuwa spacje z początku i końca wartości tekstowej. Dla wartości binarnych użyj addBinaryHeader() lub addBinaryTrailer(). Nazwa wartości binarnej musi kończyć się na -bin.
Rapira zamienia nazwy metadanych odpowiedzi na małe litery. Następnie nazwa może zawierać tylko 0-9, a-z, _, - i ., zgodnie ze specyfikacją protokołu gRPC. Nazwa transportowa, nieprawidłowa nazwa lub nieprawidłowa wartość rzuca \ValueError. Metody tekstowe odrzucają nazwy zakończone na -bin. Powtórzona nazwa dodaje kolejną wartość.
headers() i trailers() zwracają migawki. Dla Connect każdy nagłówek końcowy jest nagłówkiem z prefiksem trailer-. Przekaż metadane żądania opcją grpcurl -H 'x-request-id: demo-1'.
Interceptory
Interceptor sprawdza wywołanie, zanim PHP je otrzyma. Wymień interceptory w grpc.interceptors w kolejności łańcucha. Każdy interceptor ma własną tabelę. Rapira ma jeden interceptor: auth.
Uwierzytelnianie
auth przyjmuje wywołanie tylko wtedy, gdy wywołanie zawiera skonfigurowany token bearer. Zapisz tokeny w pliku, po jednym tokenie w wierszu:
# rapira gRPC tokens
2f1c9a7e4b0d4c8f9e3a
ci.deploy-tokenRapira pomija puste wiersze i wiersze zaczynające się od #. Każdy token składa się ze znaków A-Z, a-z, 0-9, -, ., _, ~, + i /, z opcjonalnymi znakami = na końcu. Następnie włącz interceptor:
[grpc]
descriptor_set = "api.binpb"
interceptors = ["auth"]
[grpc.auth]
tokens_file = "grpc-tokens"Ścieżka pliku tokenów używa katalogu pliku konfiguracyjnego jako podstawy. Zasady dotyczące kluczy opisuje Konfiguracja.
Klient wysyła dokładnie jedną wartość metadanych authorization jako Bearer <token>:
grpcurl -plaintext -H 'authorization: Bearer ci.deploy-token' 127.0.0.1:50051 listNasłuch nie szyfruje ruchu, więc token przechodzi przez sieć w postaci nieszyfrowanej. Umieść proxy TLS przed nasłuchem, gdy klienci łączą się przez sieć, której nie ufasz. Zobacz RFC 6750 §5.3.
Wywołanie bez prawidłowego tokena otrzymuje UNAUTHENTICATED. Unarne wywołanie Connect otrzymuje status HTTP 401 z nagłówkiem WWW-Authenticate: Bearer. Rapira nie czyta treści żądania, a PHP nie otrzymuje wywołania.
grpc.health.v1.Healthnie wymaga tokena, ponieważ sondy gRPC w Kubernetes nie mogą wysyłać metadanych.- Refleksja wymaga tokena.
- Wywołanie nieznanej metody bez tokena otrzymuje
UNAUTHENTICATED, a nieUNIMPLEMENTED. - PHP nadal otrzymuje wartość
authorizationwywołania, które przeszło kontrolę.
Proces nadrzędny wczytuje plik tokenów przy starcie. Plik, którego Rapira nie może odczytać, plik bez tokenów i nieprawidłowy token zatrzymują start. Uruchom ponownie Rapirę, aby wczytać zmieniony plik tokenów. Przeładowanie zachowuje stare tokeny.
Terminy zakończenia i anulowanie
Klient ustawia limit czasu przez grpc-timeout (gRPC i gRPC-Web) lub connect-timeout-ms (Connect). grpc.default_timeout_secs ustawia limit czasu wywołania, dla którego klient nie podał limitu. grpc.max_timeout_secs skraca dłuższy limit klienta do swojej wartości. Oba klucze są domyślnie nieustawione, więc wywołanie bez limitu klienta nie ma terminu zakończenia. Takie wywołanie odczytuje też komunikat żądania bez limitu czasu. Ustaw oba klucze, gdy klienci nie są zaufani.
$call->getContext()->deadline to termin zakończenia jako uniksowy znacznik czasu w sekundach lub null. receivedAt to czas, w którym Rapira odczytała cały komunikat żądania.
Na przykład ustaw klientowi termin zakończenia po dwóch sekundach:
grpcurl -plaintext -max-time 2 -d '{"text":"hello"}' 127.0.0.1:50051 example.v1.Echo/EchoPo przekroczeniu terminu klient otrzymuje DEADLINE_EXCEEDED, a isCancelled() zwraca true. Rapira nie może zatrzymać kodu PHP, więc PHP kontynuuje wywołanie. Sprawdzaj isCancelled() podczas długich operacji. Przechwytuj WorkDiscardedException przy wywoływaniu metod odpowiedzi, ponieważ anulowanie może nastąpić po sprawdzeniu.
Limit receive() określa, jak długo PHP czeka na nową pracę. Jest niezależny od terminu zakończenia wywołania. grpc.pool.request_terminate_timeout_secs nadzoruje czas działania procesu. Zastępuje workera, gdy wywołanie trwa dłużej niż limit.
Usługi i refleksja
Proces nadrzędny wczytuje zestaw deskryptorów przed forkowaniem workerów. Nieprawidłowy zestaw, brak importowanych plików lub nieznana usługa wskazana w konfiguracji uniemożliwiają uruchomienie. Uruchom ponownie Rapirę po zmianie zestawu deskryptorów. Przeładowanie zachowuje wczytany zestaw.
Domyślnie pula obsługuje usługi z plików, których nie importuje żaden inny plik zestawu. Plik importowany przez inny plik jest zależnością, na przykład google/longrunning/operations.proto. Jego usługi nie są obsługiwane. Ustaw grpc.services, aby wskazać obsługiwane usługi, na przykład ["billing.v1.InvoiceService"]. Użyj tego klucza, gdy kilka instancji Rapira współdzieli jeden zestaw, lub aby obsłużyć usługę z importowanego pliku.
Metoda strumieniowa, metoda usługi, której pula nie obsługuje, oraz nieznana metoda zwracają UNIMPLEMENTED. Przy starcie Rapira zapisuje ostrzeżenie dla każdej metody strumieniowej obsługiwanej usługi.
Refleksja jest domyślnie wyłączona. Przy reflection = true Rapira obsługuje grpc.reflection.v1 i grpc.reflection.v1alpha. ListServices zwraca obsługiwane usługi. Każdy plik i symbol zestawu deskryptorów jest dostępny, więc każdy klient może odczytać cały zestaw.
Przy reflection = false przekaż schemat klientowi:
grpcurl -plaintext -import-path proto -proto echo.proto -d '{"text":"hello"}' 127.0.0.1:50051 example.v1.Echo/EchoKontrole stanu
Rapira obsługuje protokół kontroli stanu gRPC (grpc.health.v1.Health) w każdym workerze. Check i Watch zgłaszają SERVING dla pustej nazwy "" i dla każdej obsługiwanej usługi. Podczas zatrzymywania zgłaszają NOT_SERVING.
Usługa health nie sprawdza PHP. Worker, w którym start PHP się nie powiódł, zgłasza SERVING, a jego wywołania otrzymują UNAVAILABLE. grpc.services nie może wskazywać usług health ani refleksji, ponieważ Rapira obsługuje je sama.
Refleksja nie wymienia usługi health. Żądanie Connect JSON nie wymaga schematu:
curl -H 'Content-Type: application/json' -d '{}' http://127.0.0.1:50051/grpc.health.v1.Health/CheckProtokoły i limity
- Użyj binarnego gRPC-Web (
application/grpc-web+proto). Tryb tekstowy gRPC-Web nie jest obsługiwany i zwracaUNIMPLEMENTED. - Dla klientów przeglądarkowych z innego origin skonfiguruj CORS na proxy. Rapira nie obsługuje żądań wstępnych CORS.
- Żądanie Connect JSON, którego nie da się zdekodować, zwraca
INVALID_ARGUMENT, a PHP nie otrzymuje wywołania. Dekoder JSON ignoruje nieznane pola. Nie ignoruje nazwy wartości enum, której zestaw deskryptorów nie deklaruje. - Metoda z
option idempotency_level = NO_SIDE_EFFECTS;akceptuje też żądanie Connect GET. - Komunikaty mogą używać kompresji gzip. Żądanie z innym kodowaniem komunikatu zwraca
UNIMPLEMENTED. - Żądania do usług PHP mają limit treści 4 MiB i osobny limit komunikatu po dekompresji 4 MiB. Większe żądanie zwraca
RESOURCE_EXHAUSTED. Żaden klucz TOML nie zmienia tych limitów. - Odpowiedź PHP, której nie można przekształcić do Connect JSON, zwraca
INTERNALi zapisuje ostrzeżenie. Odpowiedzi w binarnym protobuf nie używają tego przekształcenia. - Dekoder JSON nie ogranicza pamięci, której używają elementy żądania. Żądanie Connect JSON o rozmiarze 4 MiB z wieloma małymi elementami może spowodować, że worker użyje kilkuset MiB dla pól powtarzanych lub pól map. Dla pól
google.protobuf.StructlubListValueworker może użyć więcej niż 1 GiB. Limit 4 MiB obowiązuje po dekompresji. Gdy klienci nie są zaufani, umieść przed nasłuchem proxy, które ogranicza rozmiar żądania po dekompresji. $call->getContext()->tlsma zawsze wartośćnull. Umieść proxy TLS przed nasłuchem, gdy klienci potrzebują TLS. Dla natywnego gRPC proxy musi używać HTTP/2 do połączenia z Rapirą.- Rapira wysyła keepalive PING HTTP/2 do połączenia, które pozostaje bezczynne przez
grpc.keepalive_interval_secs. Zamyka połączenie, gdy PING nie otrzyma odpowiedzi w ciągugrpc.keepalive_timeout_secs. Oba klucze mają domyślnie wartość 10 sekund. HTTP/1.1 nie ma PING, więc te klucze nie dotyczą klientów gRPC-Web ani Connect na HTTP/1.1.
Jedno połączenie używa jednego workera
Jeden proces workera obsługuje każde połączenie. Klient gRPC zwykle wysyła wszystkie wywołania kanału jednym połączeniem HTTP/2. Taki klient otrzymuje przepustowość jednego workera, niezależnie od rozmiaru puli. Aby użyć więcej workerów, otwórz kilka połączeń lub użyj load balancera L7, który rozdziela wywołania.
Wspólne uruchomienie HTTP i gRPC
Jedna konfiguracja może zawierać [http] i [grpc]. Każda wtyczka ma własny nasłuch, skrypt wejściowy PHP i pulę workerów. Proces nadrzędny nadzoruje obie pule. Pula gRPC obsługuje te same ustawienia liczby workerów i wymiany workerów co pula HTTP, z mode = "dispatcher".
Wszystkie ustawienia gRPC opisuje Konfiguracja, a nadzór nad pulami opisuje Model procesów.
Windows
Wersja dla Windows obsługuje ten sam nasłuch gRPC i to samo API PHP. Obowiązują te różnice:
grpc.listenakceptuje tylko adres TCP.grpc.interceptorsi tabela[grpc.auth]nie są dostępne.- Pula gRPC to statyczna pula wątków interpretera PHP w jednym procesie.
grpc.pool.processesustawia liczbę wątków. getmypid()zwraca ten sam identyfikator procesu w każdym interpreterze.- Błąd startu PHP w dowolnej puli zatrzymuje serwer z kodem wyjścia 70.
Jak działają limity rozmiaru danych wejściowych?
Limit treści żądania obejmuje pięciobajtową kopertę unarnego żądania z ramkami. Usługi health i refleksji ograniczają każdy komunikat żądania do 16 KiB. Te limity wejściowe nie ustalają limitu rozmiaru odpowiedzi.
Jakie limity dotyczą zawartości Any w odpowiedziach JSON?
Konwersja JSON dekoduje zawartość każdego google.protobuf.Any z budżetem 32 MiB dla zdekodowanych elementów. Przekroczenie tego budżetu zwraca INTERNAL i zapisuje ostrzeżenie. Klienci binarnego protobuf nie używają tej konwersji.