JSON-RPC v2 Klient a Server
- ptacek.pavel
- Člen | 27
Zdravím všechny,
do doplňků jsem přidal novou komponentu, a sice JSON-RPC klient / server
knihovnu s referenční implementací. Vím, že frosty již jednu vytvořil,
pro naši aplikaci jsme ale potřebovali knihovnu, která bude implementovat
protokol ve verzi 2.0 – ten totiž obsahuje novinku řetězení
requestů.
Co je JSON-RPC, proč je dvojka dobrá a jak používat danou class včetně polo-automatického test suite najdete zde: https://componette.org/search/?…
Veškeré komentáře jsou vítány!
- Filip Procházka
- Moderator | 4668
Nelíbí se mi nutnost vytvářet vždy instance tříd. Se soapem se to řeší tak, že se udělá více služeb/vstupů (více adres) kde máš jednotlivé třídy.
Když už tam máš takto šikovnou podporu volání vlastností, nešlo by do toho trochu více zapojit Nette?
class MySuperAppApi extends Nette\DI\Container
{
protected function createServiceAccess()
{
return new AccessHandler;
}
protected function createServiceUser()
{
return new UserHandler;
}
protected function createServiceMatika()
{
return new Matika;
}
protected function createServicePrvniMetoda()
{
return function ($i) {
return $i+1;
};
}
}
$server = new Lightbulb\Json\Rpc2\Server(new MySuperAppApi);
Co myslíš? :)
- ptacek.pavel
- Člen | 27
Právě narozdíl od soapu jsme nechtěli mít více endpointů, aby se nám api lépe dělalo. Druhou věcí je, že přímo v RFC json-rpc je metoda „foo.get“ a ta tečka tam přímo evokuje objekt..
Nicméně k tvé připomínce: souhlasím s nette\di\container
který lazy-loaduje objekty ve chvíli, kdy jsou potřeba. Navíc, tímhle
přístupem by se celá server instance dala hodit do configu a presenter by si
jenom šáhnul do contextu. Kromě toho se s containerem bude v server class
pracovat jednoduššeji, než ten bordel co tam je teď :-)
Prvotně jsem to neimplementoval, neboť jsem v tu chvíli ještě moc netušil o DI obecně :-) Best-practice, proxy-classes stejně zůstane nedotčená (což jak mě tak napadá doplním do dokumentace.)
Doupravím, pushnu a dám vědět.
- Filip Procházka
- Moderator | 4668
Právě narozdíl od soapu jsme nechtěli mít více endpointů, aby se nám api lépe dělalo. Druhou věcí je, že přímo v RFC json-rpc je metoda „foo.get“ a ta tečka tam přímo evokuje objekt..
Já to vím proto jsem navrhl řešení s containerem :)
… Navíc, tímhle přístupem by se celá server instance dala hodit do configu …
services:
apiEntries:
class: MySuperAppApi
api:
class: Lightbulb\Json\Rpc2\Server
arguments: [@apiEntries]
$this->context->api->handle($request); // blíže jsem nestudoval, hádám
:)
- ptacek.pavel
- Člen | 27
naprosto přesně – vypadá to tak mnohem lépe :-)) akorát
$request
je buď string (raw input json – něco jako
file_get_contents('php://input')
), nebo už naparsovaný
json_string nebo null – a pokud null, tak se to čapne samo
z php://input.
Nejsem si jistý, jestli RAW_POST_DATA
jsou v nette
přístupné.?
Každopádně se k dodělávání dostanu až během týdne
edit: presenter pak bude vypadat takhle:
class MyJsonPresenter extends Presenter {
public function renderDefault() {
$result = $this->context->api->handle();
if(!empty($result) || is_array($result)) {
$this->sendResponse(new JsonResponse($result));
}
else {
$this->sendResponse(new TextResponse(''));
}
}
}
Editoval ptacek.pavel (13. 9. 2011 16:48)
- Filip Procházka
- Moderator | 4668
Já na to nespěchám, jenom radím co mě napadlo :)
K RAW datům se bohužel přes Nette nedostaneš… Nemohl by handle už
rovnou vracet Response
?
// požadavek
$request = Nette\Utils\Json::decode(file_get_contents('php://input'));
// zpracování
$response = $this->context->api->handle($request);
$payload = $response->getPayload(); // JSON
// odpověď
$this->sendResponse($response);
Editoval HosipLan (13. 9. 2011 17:17)
- ptacek.pavel
- Člen | 27
já vím a rád to implementuju :-)
Nicméně JSON::decode
bych do presenteru nutně nerval, páč
dle RFC je nutné poslat zpátky validní json-error „invalid request.“ –
muselo by se to házet do try/catch a hlavně tohle je imho spíš záležitost
serveru než presenteru, který v podstatě funguje jenom jako proxy..
Návrat response přímo ze serveru by šel taky, nicméně v tu chvíli bychom se dostali do fáze, že by server nešel použít jako stand-alone – a nebo udělat factory, která vrací pro nette instance nette tříd, a zendisti by mohli použít něco jiného. Ještě to promyslím :-)
edit: takže tohle by bylo Konečné Řešení ™?
class MyJsonPresenter extends Presenter {
public function renderDefault() {
$response = $this->context->api->handle();
$this->sendResponse($response);
}
}
edit2: Hlavně ještě budu muset dovypiplat debugger error
presenter tak, aby při fatal error nevyhazoval Nette 500 html page, ale
json-response.
Editoval ptacek.pavel (13. 9. 2011 17:32)
- Filip Procházka
- Moderator | 4668
Když ti aplikace kompletně umře, tak s tím moc nenaděláš. Když ti ale vyhodí výjimku (což by měla) tak ji můžeš odchytit a zalogovat. V takovém případě můžeš vracet svůj vlastní response, který by nastavil hlavičky a vrátil správnou „chybovou“ odpověď :)
Btw, znáš tohle?
Debugger::tryError();
// nějaký kód, který vyhazuje notice/warning/fatal
if (Debugger::catchError($e)) {
// nastala chyba
}
//edit:
Nette nastavuje tyto handlery, třeba ti to pomůže :)
A jako řešení bez nette by se dal vytvořit nějaký soubor
responses.php
, který by jsi označil jako ignorovaný souborem
netterobots.txt
. A kdyby nebylo načteno nette
if (!defined('NETTE')) {
tak by jsi ho načetl a měl tam vlastní
response třídy :)
Editoval HosipLan (13. 9. 2011 18:30)
- Ot@s
- Backer | 476
Velmi elegantní a praktická pomůcka – díky za ni.
Řeším však zádrhel. Potřebuji komunikovat mezi klientem/serverem na
základě autorizace. Tu provádím na začátku oproti přihl. údajům. Server
pak v případě úspěšné autorizace vrací token, který se dál
používám v komunikaci mezi kl/srv. Problém je ten, že na serverové
části nefunguje (=neuloží se) ukládání do sesion (resp. uložení onoho
tokenu) via klasické $this->session-...
. Myslel jsem si, že je
problém v tom, že v HTTP komunikaci klienta chybí cookie PHPSESSID, tak
jsem ji do komunikace přidal, ale nepomohlo to. Dalo by se to obejít
ukládáním tokenu přímo do DB, ale stále mi to vrtá hlavou. Nevíte
v čem by mohl být probém?
- Filip Procházka
- Moderator | 4668
Session se ti uloží, ale klient nejspíš ignoruje sušenky, které jsou pro práci se session bezpodmínečné. Nějaký token je celkem zbytečný, podstatné jsou sušenky. Možná by se ten token mohl skládat z těch sušenek a pak je nastavit službě Session v Nette?
- ptacek.pavel
- Člen | 27
Záleží, jaká je implementace python clienta. „tečkování“ je něco,
co jsem si vymyslel pro jednodušší dělení (+ časem doděláme container),
nicméně pro implementaci nejzákladnějšího serveru stačí
používat $server->metoda = callback(...);
V pythonu nedělám, našel jsem nicméně tuhle knihovnu , která by mola být funkční. Potom by jsi měl dvě možnosti:
Varianta 1
- všechny metody nacpat přímo do $server->metoda a volat z pythonu:
server = jsonrpclib.Server('http://projekt.domena.tld/nette/endpoint')
return = server.metoda(par, par2, par3)
U této knihovny je nutné tzv. notifikace (kdy server nic nevrací, v podstatě se jedná o signál – tedy handleNeco!) volat samostatnou metodou:
server._notify.add(5,6)
Knihovna umí i batch requesty, které server podporuje.
Varianta 2
Chceš použít „tečkovou magii“ – tohle (co jsem koukal) pythoní
knihovny neumí, i když se o tom ve specifikaci „mluví“ (dobře –
naznačuje.) V takovém případě budeš potřebovat volat přímo __call__
metodu pythonu, která je (myslím!) namapovaná na metodu _request. Vypadalo by
to takhle:
<?php
$server = new Lightbulb\Json\Rpc2\Server;
$server->access = new AccessHandler;
$server->user = new UserHandler;
?>
A na straně python klienta pak:
server = jsonrpclib.Server('http://projekt.domena.tld/nette/endpoint')
args = [15, 16] # vůbec nevím jak se v pythonu pracuje s array()
return = server._request("access.login", args);
poznámka – v pythonu je metoda _request definována jako
def _request(self, methodname, params, rpcid=None):
– a vůbec
netuším, co tam dělá to self.
Metoda _request
následně pošle dotaz na server s tím, že
v rámci json-notace bude {method: "access.login"}
což php server
interpretuje jako $server->access->login(args)
– tzn. bude
volat class AccessHandler
- Blackhex
- Člen | 1
Mohl by mě někdo odkázat na jednoduchý příklad, jak pomocí JSON-RPC2 serveru vytvořeného v presenteru můžu vrátit výsledek DB dotazu na model aplikace přes factory? Mám na mysli něco, jako v následujícím kódu http://nopaste.info/ed03e1331c.html . Nicméně tento kód nefunguje. Vrací http://nopaste.info/5165c640f6.html . Když je obsah metody TestHandler::register() přímo v presenteru, tak to funguje. Vypadá to na nějakou chybu s přístupem do cache, když se předává context objekt mezi instancí presenteru a staticky volanou instancí handleru.
EDIT: V cache se daný soubor nenachází, je tam podobný, ale s jinou hash v názvu. Když vypnu DB cache, tak to funguje.
Editoval Blackhex (8. 4. 2012 13:11)