9 giugno 2009

Console remota embeddable in python

Altro post di informatica pura, che del resto è il mio mondo molto più di quanto non lo sia la politica o la sociologia ;)

A parte le cazzate, il tema odierno è una facility che usai un tempo tramite twisted matrix, una grossa libreria che permette di rendere asincrono praticamente tutto, anche lo svegliarsi rispetto all'alzarsi dal letto, purche lo si faccia in python.

La facility in questione era l'esposizione di una console di python via rete. Sin qui è già una bella magia ma non stratosferica, più interessante il fatto che fosse anche integrata in un programma esistente (ed in esecuzione), dando quindi accesso, per i più svariati scopi, al programma medesimo, come se lo stessimo eseguendo dall'interactive console (IDLE, per intenderci) .

In rete ci sono pochissimi riferimenti a come ottenere il medesimo risultato senza portarsi dietro il tonno che è Twisted Matrix, per il quale non provo oltretutto grande simpatia.

C'è qualcuno che fa cose simili, ma o non le fa in rete ma con widget, o usa trucchi distruttivi sull'ambiente del programma rendendo impossibile l'integrazione in un contesto già esistente, o lo fa in modo così triviale che lo sforzo per renderlo funzionante in un contesto più generico supera il beneficio di avere già una base di partenza.

Mi è costato una giornata di prove assortite, ma alla fine sono arrivato a scrivere un pezzo convincente di codice, che come si vedrà, alla fine, è anche piuttosto ridotto, ma contiene dei barbatrucchi piuttosto importanti.

Come al solito, prima il codice poi i commenti! Eccolo:



I pezzi importanti sono due, il resto è solo un po' di fumo al contorn, ma lo vedremo.

Il pezzo chiave del tutto è il metodo mainloop della classe ipconsole.
Qui viene istanziato un interprete a cui vengono passate le stringhe lette dalla rete, qui si fa la lettura dalla rete e le varie interazioni con l'interprete medesimo.
Tutto sembrerebbe a posto, se non fosse che non c'è modo di redirigere l'output dell'interprete in questione senza gabbarlo. è qui che casca l'asino in quasi tutte le implementazioni che si trovano in giro.
Il modo logicamente più semplice di fregare l'interprete, e anche più pulito, sarebbe stato quello di eseguirlo in un ambiente modificato in cui il riferimento a sys fosse modificato (verso un clone di tutto il modulo sys e non un suo riferimento che non serve a nulla. In tale contesto diventa facile fare quello che si vuole di sys.stdout, sys.stderr ed eventualmente sys.stdin del sys clonato.
Se si riuscisse a ottenere questo clone, passarlo come contesto all'interprete sarebbe semplice, ma...peccato che non ci sia verso in Python di clonare un modulo (oddio, forse qualche modulo stupido sì, ma sys no). Che fare allora?
Si tarocca direttamente sys.stdout!
La soluzione è un po' invasiva e fa si che non sia completamente generica, ma almeno nei casi comuni (quelli che non manipolano sys.stdout e sys.stderr runtime e non si mettono a giocare coi reload di sys) è trasparente. Il trucco consiste nel creare una classe ponte che nasconda le 2 istanze stdout e stderr, incapsulandole.
Questa classe è appunto la mia fakeout.
fakeout manipola il metodo write, quello che, per questioni di interfacce, tutti gli stream writers devono esporre e stdout, che è un outputfile, non può esimersi da mettere a disposizione e viene usato anche da statement come "print".
Nel nuovo write, se il thread corrente non si è registrato, si passa la chiamata allo streamwriter originale (stdout o chi per lui), che è stato incapsulato durante la creazione dell'istanza di fakeout, se invece il thread corrente si è registrato, usando il metodo setout4thread, si usa il writer passato durante la registrazione.
In questo modo thread diversi possono registrarsi, ognuno usando come stdout taroccato un proprio writer.

Questa genialata mi è stata suggerita da una discussione su una newsletter (che mi sono salvato e potete leggere qui, perché è giusto che il merito vada a chi ha le idee) ed è la base della soluzione a tutti i problemi!

Beh, tutti si fa per dire, perché in realtà un problema rimane: la formattazione dell'output.
Il programma funziona correttamente con il telnet di Linux come client, ha dei problemi di formattazione con la raw connection di putty, nel senso che quando va a capo non torna a inizio linea, e non funziona con telnet di putty, che si ostina a mandare spazzatura ad inizio linea nonostante io provi a impostarlo diversamente, risultato netto: non interpreta nulla.
Credo comunque che siano problemi di configurazione del client e se qualcuno ha una idea di come arginarli o, ancora meglio, risolverli...ben venga!

Ah....quasi dimenticavo...per usare il tutto basta copiare il pezzo di codice di sopra in un file qualunque, chiamiamolo tcpconsole.py. A questo punto vi basta fare import tcpconsole dal vostro codice e poi una chiamata alla funzione tcpconsole.createServer passandole come parametro ip e porta su cui ascoltare.

Nulla di più semplice....sempre se il firewall di vista ve lo lascia fare....

Nessun commento:

Posta un commento