Jak propojit PHP aplikaci s Node.js pomocí gRPC

by on 4.8.2016

Ačkoliv je PHP velmi univerzální jazyk, některé úkoly se v něm řeší obtížně. Může to být například používání headless browseru pro scrapování webových stránek. Nebo paralelizace stahování nějakých zdrojů. Posláním tohoto článku ale není obhajovat použití Node.js pro řešení daného problému. Jeho cílem je ukázat tři cesty, jak se dají dvě aplikace na různých architekturách propojit.

Tl; dr

Použijte gRPC, pokud chcete mít komunikaci pod kontrolou.

Způsob č.1 – JSON API

Touto cestou se vydává většina webových služeb, které nabízí nějaké API. Princip je jednoduchý:

  • klient zná adresu API a má seznam endpointů
  • klient si připraví data ve svojí datové struktuře (pole, objekt) a přeloži ho do JSON formátu
  • server přijme HTTP požadavek, který má v těle JSON řetězec od klienta
  • server si JSON přeloží do své datové struktury a zpracuje ho

Problémem u tohoto způsobu komunikace je obtížná validace předávaných dat. Klient sestavuje požadavek defacto „na koleně“ a po odeslání požadavku doufá, že ho poslal ve správném formátu. Jediným zdrojem informací o schématu je dokumentace poskytovatele služby, která často nebývá aktuální.

Výhodou tohoto způsobu komunikace je to, že všechny použité technologie už jsou většinou dostupné a není tak nutné nic dalšího instalovat. Snad každý jazyk už dnes dovede datovou strukturu převést do JSON formátu a poslat ji přes HTTP protokol.

Způsob č. 2 – SOAP / XML-RPC

Druhou možností, jak si spolehlivěji předávat informace, je využití XML formátu a protokolu SOAP (nebo jeho předchůdce XML-RPC). XML formát je důvěrně známý a hlavně v oblasti enterprise API i hojně využívaný.

Použití této technologie už je mnohem složitější na implementaci. Hodí se, pokud jsou vyměňované informace velmi složité a komunikuje se přes hodně endpointů. Formát XML je velmi rozvláčný a je tím dobře čitelný pro lidi. Horší je to u strojového zpracování, kde už bývá zpracování pomalejší než u méně ukecaných formátů.

Pro většinu případů užití se zde jedná o příslovečné „kladivo na mravence“. Pokud chceme jenom interně propojit dvě aplikace, tak je SOAP dle mého soudu zbytečně složitý a komplikovaný. Nicméně i zde platí, že všechny potřebné technologie máme k dispozici v základu.

Způsob č.3 – gRPC

Co když ale nechceme pracovat s XML a implementovat SOAP server? Co když chceme jasně specifikovat formát komunikace a mít možnost ho definovat schématem společným pro obě aplikace? Co když nechceme řešit autentizaci, serializaci a deserializaci? Pak můžeme krásně využít gRPC, které je dostupné pro většinu programovacích jazyků (PHP, Java, C++, Node.js, Python, Go …) a ze kterým stojí takový gigant, jako je Google.

Jeho použití je sice složitější v tom, že si musíme nainstalovat (a mnohdy i zkompilovat) další nástroje a moduly, ale stojí to za to. Instalaci všeho potřebného zvládne i průměrně pokročilý vývojář a pak už následují jenom benefity.

grpc.io - diagram

Zdroj: http://www.grpc.io/docs/

Nechci tady úplně rozebírat teoretickou stránku věci, tu je možné si nastudovat z oficiálních stránek (odkaz na konci článku). Místo toho se zaměřím na praktické využití gRPC pro komunikaci mezi PHP aplikací a Node.js skriptem.

Protokol gRPC využíváme v rámci Marketing Mineru pro komunikaci mezi PHP workerem, který běží na ReactPHP a Node.js aplikací, která obsluhuje headless browser. Specifikace celého API, tzn. nabízených služeb a formátu výměňovaných dat, vypadá takto:

syntax = "proto2";

import "php.proto";

package headlessscraper;

service HeadlessScraper {
  rpc getPageSpeedData(PageSpeedRequest) returns (PageSpeedResponse) {}
}

message PageSpeedRequest {
  required string url = 1;
}

message PageSpeedResponse {
  required int32 pageLoadTime = 1;
  required int32 domLoadTime = 2;
  message PageSpeedResource {
    required string url = 1;
    required int32 responseCode = 2;
    required string responseType = 3;
    message PageSpeedHttpHeaders {
      required string contentType = 1;
      required int32 contentLength = 2;
    }
    required PageSpeedHttpHeaders header = 4;
  }
  repeated PageSpeedResource resources = 3;
  optional string error = 4;
}

Bloky uvozené klíčovým slovem „service“ specifikují endpointy, které server implementuje. Bloky začínající slovem „message“ definují formát vyměňovaných zpráv. Tento soubor je společný pro server i klienta a nemůže tak dojít k tomu, že klient pošle data ve špatném formátu nebo na neexistující endpoint (jako se to stává u JSON API).

V PHP je nutný ještě jeden mezikrok, který specifikaci převede do zástupných (angl. stub) tříd, které pak bude aplikace reálně používat. Tento krok se ukázal nakonec jako nejsložitější a kompilace potřebných binárek mi jedno odpoledne zabrala, ale když se budete držet oficiální dokumentace a nebudete improvizovat, jako jsem to udělal já, tak to určitě zvládnete rychleji. Nenechte se tím ale odradit – řešit to budete stejně jenom jednou.

protoc-gen-php -i /protos -o /Service/Grpc /protos/headless_scraper.proto

Tímto příkazem se pak specifikace převede do PHP tříd. Je vhodné si ho někam uložit (ideálně zapojit do build procesu), protože API se může změnit a pak bude nutné soubory přegenerovat. Z toho vyplývá i další důsledek – generovaný soubor rozhodně neměnit ručně, změny by při příští kompilaci zmizely.

Server – Node.js

V Node.js je situace mnohem jednodušší. Po instalaci gRPC knihovny, stačí jen specifikovat cestu k .proto souboru a implementovat všechny metody, které jsou v něm specifikované:

var headless_scraper =
  grpc.load(__dirname + '/protos/headless_scraper.proto').headlessscraper;

function getPageSpeedData(request, callback) {
  ...
}

var server = new grpc.Server();

server.addProtoService(headless_scraper.HeadlessScraper.service, {
  getPageSpeedData: getPageSpeedData
});

server.bind('0.0.0.0:50051', grpc.ServerCredentials.createInsecure());

server.start();

Na posledních dvou řádcích se rovnou startuje server na specifikovaném portu.

To by tedy byla strana serveru a nyní jak to bude vypadat na klientovi.

Klient – PHP

Po zkompilování zástupných tříd už je můžeme rovnou použít.

require_once 'Grpc/headless_scraper.php';

use Service\Grpc\HeadlessScraperClient;
use Service\Grpc\PageSpeedRequest;
use Service\Grpc\PageSpeedResponse;

$client = new HeadlessScraperClient($remoteHost, [
  'credentials' => \Grpc\ChannelCredentials::createInsecure(),
]);

$request = (new PageSpeedRequest())->setUrl($url);

$response = $client->getPageSpeedData(
  $request,
  [ ],
  [
    "timeout" => 15 * 1000 * 1000, // 15 seconds
  ]
)->wait();

$client->close();

return $response;

Myslím, že tady už není nutné nic vysvětlovat. V proměnné $remoteHost je IP adresa serveru a port. Kdyby server běže lokálně, bude v ní „127.0.0.1:50051“. Nic ale nebrání tomu, aby server běžel někde na síti a klient se připojoval vzdáleně.

Jak vidíte, využívání gRPC pro meziaplikační komunikaci je transparentní, standardizované a udržitelné. Formát API je definovaný na jednom místě pro obě komunikujcí strany ve strojově čitelném formátu, se kterým si ale poradí i člověk. Specifikace navíc umožňuje určit i typ dat, takže klient má jistotu v tom, co se mu ze serveru vrátí. Protokol za nás řeší i de/serializaci vyměňovaných zpráv a zároveň jejich validaci.

Zdroje

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *