JSON-RPC v2 Klient a Server

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
ptacek.pavel
Člen | 27
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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)

ptacek.pavel
Člen | 27
+
0
-

Díky! Mám si zase s čím hrát :) A takhle se mi to líbí mnohem víc

Ot@s
Backer | 476
+
0
-

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
+
0
-

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?

Ot@s
Backer | 476
+
0
-

Uf, muj zmatek. Vše funguje jak má. Prosím ignorujte.

jiri.medved
Člen | 33
+
0
-

Jak prosím například volat metody serveru v pythonu ?

ptacek.pavel
Člen | 27
+
0
-

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

jiri.medved
Člen | 33
+
0
-

Pavle velice Ti děkuji. Vyzkouším to. Díky.

elendir
Člen | 31
+
0
-

Ahoj, díky moc za skvělý plugin. Snad nebudu moc OT když se zeptám jakou knihovnu používáš na straně Androidího klienta? Pro jsonrpc verze 2.0 nemohu najít nic rozumného.

Blackhex
Člen | 1
+
0
-

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)