Remote Classes in PHP

Door StM op dinsdag 28 december 2010 14:00 - Reacties (11)
CategorieŽn: PHP, Research, Views: 4.275

Voor verschillende projecten de afgelopen jaren heb ik backend systemen gemaakt. De traditionele manieren om functionaliteit beschikbaar te stellen aan externe systemen zijn RPC en SOAP. Ook heb ik een aantal keren gewoon een simpel linebased protocol ontwikkeld om de functionaliteit te ontsluiten. Helaas zijn dit vaak systemen die relatief veel werk zijn om te implementeren en dat moet simpeler kunnen!

Ik ben al lange tijd fan van PHP. Hoewel ik absoluut geen moeite heb met Java of C++, PHP de nodige nukken heeft en de ontwikkeling aardig stil staat blijft het voor mij de webtaal bij uitstek. Ook schrijf ik low-volume backends zonder zeer hoge vereiste betrouwbaarheid ook zonder problemen in PHP. Nu zou een C++ daemon een hele mooie oplossing zijn om een hoog volume aan requests af te handelen, maar bij mijn laatste project, het bouwen van een radio streamplatform hebben we het over misschien 100 requests per uur, wat meestal niet verder gaat als draait de stream nog, wat speelt er en stuur dit als metadata. Requests die hooguit enkele milliseconde kosten, ook in PHP.

In C++ zou je een veelvoud van de tijd nodig hebben om hetzelfde te ontwikkelen, loop je een veel groter risico op buffer overflows en andere exploitable en crashing bugs en zal redelijk wat moeite moeten doen dmv een custom protocol of een RPC achtig systeem om dit beschikbaar te maken...

PHP heeft helaas weleens de neiging om bij pure PHP server implementaties na een week of 2 vage problemen te geven. Spontaan is de socket gesloten en meerdere connecties tegelijk afhandelen is ook verre van simpel. Nu kan je dmv tickers en forking wel wat trucjes uithalen maar waarom zo lastig doen? Door middel van ucspi-tcp kan je een kant en klare tcpserver geschreven in C gebruiken. Voor iedere connectie voert hij een bepaald commando uit (bv laat PHP dat script runnen) en door middel van pipes kan je op de socket readen en writen. Tevens kan je zonder problemen meerdere connecties tegelijk afhandelen. Echter hij start wel voor iedere verbinding een nieuw PHP process dus heel ver zal dit niet doorschalen. Maar een verbinding of 50 zou geen probleem moeten zijn voor een moderne server. Op dit moment draait de backend van het streamplatform al 2 maanden non stop zonder gefaald te hebben.

Zou het echter niet veel simpeler kunnen om functionaliteit extern beschikbaar te maken? Daarom heb ik in een paar avondjes als research projectje Remote Classes for PHP (RCP) geschreven.

Wat zijn de requirements voor RCP?
- Volledig geschreven in PHP
- ucspi-tcp voor de server implementatie
- SIMPEL maar toch krachtig
- Veilig

Als model heb ik Java RMI genomen echter vond ik het gebruik van stubs etc veel te omslachtig. Gelukkig bied PHP veel meer mogelijkheden voor dynamische functionaliteit zoals de magic methods. Hiermee kan je method en property calls die niet bestaan op een object afvangen.

Bij het verzoek om een nieuwe object stuurt de client dit door naar de server. Die instantieŽrt het object met de opgegeven parameters en stuurt een unieke object hash naar de client. Die associeert dit met een wrapper class die de aanroepen op het object doorlust naar de server.

Uiteraard is de veiligheid ook van belang. Om te voorkomen dat iedere class extern aanroepbaar is, moet iedere class die extern aanroepbaar moet zijn een interface implementeren. De data uitwisseling tussen de client en server is geŽncrypt met Triple DES. Dit is niet een superveilige methode maar zolang je de NSA niet achter je aan hebt moet het voldoende zijn.

De client en server communiceren met elkaar door middel van een heel simpel protocol. Op een hoog niveau stuur je een commando en een array met extra informatie. Het protocol is opgebouwd uit 2 delen. De onderste laag is bestaat uit de lengte gewoon in ASCII, een spatie gevolgd door de daadwerkelijke data. De data is gecodeerd met Triple DES. De data is opgebouwd het commando id, een spatie, het sequentie id, een spatie en dan de array met parameters geserializeerd.

Hoog tijd om eens naar de code te kijken!


PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class RCP_RemoteClass {
    private $_hash = null;
    private $_rcpClient = null;
    
    public function __construct($sHash) {
        $this->_hash = $sHash;
    }
    
    public function getRCPHash() {
        return $this->_hash;
    }
    
    public function setRCPClient(RCP_RemoteClassClient $oClient) {
        $this->_rcpClient = $oClient;
    }
    
    public function __call($sMethodName, $aArguments) {
        return $this->_rcpClient->callMethod($this, $sMethodName, $aArguments);
    }
    
    public function __get($sPropName) {
        return $this->_rcpClient->getProperty($this, $sPropName);
    }
    
    public function __set($sPropName, $mValue) {
        $this->_rcpClient->setProperty($this, $sPropName, $mValue);
    }
    
    public function __isset($sPropName) {
        return $this->_rcpClient->issetProperty($this, $sPropName);
    }
    
    public function __unset($sPropName) {
        $this->_rcpClient->unsetProperty($this, $sPropName);
    }
}



Met __call() worden de method calls opgevangen naar niet bestaande methods. In principe zijn dit dus de methods van het remote object. __get() en __set() zijn voor de properties. __isset() en __unset() mappen respectiefelijk naar de isset en unset language constructs.


PHP:
1
2
3
4
5
6
7
8
9
10
class RemoteMathClass implements RCP_IRemoteCallable {
public function add($a, $b) {
        return $a + $b;
    }
}

$oServer = new RCP_RemoteClassTcpServer($sListenServerHost, $iListenServerPort, $sEncryptionKey);
while ($oServer->processRequests()) {
    usleep(100);
}



Dit is een zeer simpele server die 2 getallen bij elkaar op kan tellen.


PHP:
1
2
3
$oRemotePHPServer = new RCP_RemoteClassClient($sRemoteServerHost, $iRemoteServerPort, $sEncryptionKey);
$oRemoteMathClass = $oRemotePHPServer->getObject('RemoteMathClass');
var_dump($oRemoteMathClass->add(3, 6));



En dit is de client.

Helaas kent dit systeem ook enkele beperkingen. Zo is het aanroepen van static methods niet mogelijk en zijn references nutteloos. Dit laatste is op zich een probleem want sinds PHP 5 worden objecten ook per reference doorgegeven. Dit betekend dat als je een object meegeeft als parameter en hier bewerkingen op uitvoert, dit niet op de client doorgevoerd wordt.

Dit was puur een proefproject en ik denk dat ik in mn doel geslaagd ben. Dit betekend echter ook dat de code niet van hoogwaardige kwaliteit is. Documentatie ontbreekt en delen zijn snel-snel gemaakt. Er zitten echter geen bekende bugs in en het voorbeeld beslaat de volledige functionaliteit. De code deel ik op Github onder de BSD licentie en ik daag iedereen uit die dit wel een interessant systeem vind er mee te spelen. Voor mij was het eigenlijk was dit eigenlijk pas de voorbereiding op een volgend project, wat ik ook daadwerkelijk in gebruik wil nemen. Wie weet is dat wel weer reden voor een nieuwe blogpost ;)

Volgende: D & FlashPolleD 12-'11 D & FlashPolleD
Volgende: new Thread(blog).start(); 12-'10 new Thread(blog).start();

Reacties


Door Tweakers user Ventieldopje, dinsdag 28 december 2010 14:13

In plaats van het wiel uit te vinden lijkt me dat je beter dit klikkerdeklik had kunnen gebruiken :P

[Reactie gewijzigd op dinsdag 28 december 2010 14:17]


Door Tweakers user StM, dinsdag 28 december 2010 14:30

Had gekund ja, maar wat is daar voor fun aan? Zo heel vrolijk wordt ik ook niet van hoe PHPRPC is opgebouwd en ik ben absoluut geen RPC fan. Het gaat over HTTP dus is stateless en objecten kan je dus niet gebruiken zoals ik doe alsof ze ook daadwerkelijk lokaal zijn. PHPRPC is beperkt tot het gebruik van losse functie's, al dan niet op een object.

RPC is lekker simpel als je het op een webhostingpakketje in wilt zetten maar imo useless voor zaken als aansturingsdaemon's etc.

Door Tweakers user thioz, woensdag 29 december 2010 09:44

Mooi voorbeeld en een verademing om eens een PHP voorbeeld te zien wat degelijk gecode is (lees: met hungarian notation).

Ik moet alleen nog een echte usecase vinden waar dit echt van pas kan komen. Bepaalde delen, zoals de TCPserver implementatie, zou ik wel zo kunnen gebruiken in projecten als simpele socketserver icm Flash of dergelijken

Door Tweakers user Cyphax, woensdag 29 december 2010 10:33

thioz schreef op woensdag 29 december 2010 @ 09:44:
Mooi voorbeeld en een verademing om eens een PHP voorbeeld te zien wat degelijk gecode is (lees: met hungarian notation).
Jammer dat die vorm van hongaarse notatie allesbehalve "degelijk" is. :P
Zo is de hongaarse notatie dus nooit bedoeld. Je hebt er zo ook helemaal geen reet aan.

Door Tweakers user Luwe, woensdag 29 december 2010 18:48

Ik ben ook tegen Hungarian notation, maar het gebruik ervan is puur persoonlijk. De compiler van PHP checked zelf wat voor type het is en bovendien is vaak uit de context van je script wel duidelijk wat voor waarde een variabele heeft.

Degelijke coding wil zeggen: gebruik maken van geaccepteerde conventies zoals die van Zend. Hungarian Notation valt daar helemaal buiten.

Door Tweakers user Luwe, woensdag 29 december 2010 18:49

-- Foutje

[Reactie gewijzigd op woensdag 29 december 2010 18:50]


Door Tweakers user thioz, donderdag 30 december 2010 10:08

@Cyphax
Hoezo is Hungarian notation nooit zo bedoeld ? Verder is mijn definitie van degelijk coden niet echt helemaal afhankelijk van codingstyle ... deze moet wel uniform zijn natuurlijk maar 'degelijk' heeft meer te maken met hoe de code is opgebouwd en hoe OO concepten gebruikt worden.

@Luwe

De compiler checked inderdaad wel wat voor type iets is, alleen is PHP nog steeds geen strict language en kun je nog steeds een string en een integer bij elkaar optellen. Dit is inderdaad een persoonlijke keuze.

Daarnaast is (volgens wikipedia)
'Hungarian notation is an identifier naming convention in computer programming,' ... dit is dus wel degelijk een naming convention al beslaat die alleen het benamen van variables.

Door Tweakers user creator1988, donderdag 30 december 2010 15:02

thioz schreef op donderdag 30 december 2010 @ 10:08:
@Cyphax
Hoezo is Hungarian notation nooit zo bedoeld ? Verder is mijn definitie van degelijk coden niet echt helemaal afhankelijk van codingstyle ... deze moet wel uniform zijn natuurlijk maar 'degelijk' heeft meer te maken met hoe de code is opgebouwd en hoe OO concepten gebruikt worden.
Leesvoer!

Door Tweakers user StM, donderdag 30 december 2010 15:30

Ik ben het slechts deels eens met Joel.

- Hij gaat uit van een static language. Dan kan je IDE je idd helpen met het type. Dat is sowieso iets dat in PHP NIET kan.
- Per definitie is je data in je app unsafe. Naar mijn mening moet je data unsafe storen en pas in je view garanderen dat het safe is.
- Er is geen algemeen geaccepteerde code conventie. Het belangrijkste is dat iedereen binnen 1 project er hetzelfde voor denkt en gebruikt.

@thioz

Ik zou die TCPServer niet gebruiken. PHP wil nog weleens falen op de langere termijn en dat ding heb ik in nog geen half uur ofzo gemaakt en amper getest. Je kan beter ucspi-tcp pakken en dmv pipes. Dan heb je concurent connections en zover ik me kan herinneren heb ik nog nooit een crash gehad in de 3 jaar ofzo dat ik dat systeem nu gebruik. Misschien moet ik hier sowieso eens een artikel over maken?

Door Tweakers user thioz, donderdag 30 december 2010 16:53

@StM

Bij nadere inspectie zag ik inderdaad al dat meerdere connecties niet ondersteund worden in die TCP class. Sowiezo is PHP in mijn ogen niet echt de taal om zoiets in te maken.

Thanks in ieder geval voor je voorbeeld mbt remote objects, dit heeft me wel een paar leuke ideeen gegeven. Ook met betrekking to het statefull opzetten van een vergelijkbare toepassing via HTTP.

Door Tweakers user analog_, donderdag 30 december 2010 20:04

Hierin wordt een coding style gesuggereerd waarmee je bepaalde problemen gemakkelijker (visueel) kan inspecteren. Het lijkt mij nooit verstandig om je code aan te passen vanwege UI-wise redenen net zoals spaties ipv. tabs gewoon idioot is, omdat je editor toevallig kut doet. Waar ik meer inzie is geavanceerde code analyse die code (diep) analyseert en daarmee helpt dan guidelines te volgen. Visuele hulpmiddelen horen niet thuis in code maar in je IDE.

[Reactie gewijzigd op donderdag 30 december 2010 20:05]


Reageren is niet meer mogelijk