Java en PHP communiceren met XML-RPC

PDF version

Een groot nadeel van software is, dat we niet op iedere locatie over dezelfde data beschikken. Om dit probleem op te lossen, zal men een server op moeten zetten. Er zijn veel verschillende soorten servers en verschillende typen communicatie. Een klassiek voorbeeld is de directe socketverbinding. Hierbij kan de programmeur zijn eigen protocol ontwikkelen over het netwerk. Dit heeft echter het nadeel dat bij updates, vaak zowel de server als de client moeten worden geupdate om enge foutmeldingen te vermijden. Wat ik wil zeggen: socketverbindingen zijn hopeloos ouderwets en in 90% van de gevallen compleet overbodig.

XML

De socketverbinding is dus afgeschreven, maar wat dan? XML is de oplossing. XML staat voor eXtensible Markup Language. Voor alle duidelijkheid: XML is geen programmeertaal! Allereerst maar even een stukje XML:

<?xml version="1.0" ?>
<persons>
	<person>
		<firstname>Vincent</firstname>
		<surname>Bitter</surname>
	</person>
</persons>

Zoals je ziet, is XML erg eenvoudig en vooral overzichtelijk. XML heef veel overeenkomsten met HTML en nog meer met XHTML. XML wordt gebruikt voor de communicatie tussen twee applicaties of voor de opslag van data. Een bekend voorbeeld uit de praktijk is RSS. Door de strenge standaardregels, kan men met iedere willekeurige RSS-reader de feeds uitlezen. En dat is dan ook de grote kracht van XML. Het is eenvoudig leesbaar voor zowel computers als mens.

De case

Na een reorganisatie is schoenenfabrikant The Dutch Shoe opgedeeld in meerdere afdelingen, waaronder administratie, sales en helpdesk. Deze drie afdelingen zijn verspreid over verschillende locaties en hebben allemaal hun eigen applicaties. Om de samenwerking te bevorderen, is er behoefte aan een gezamenlijk klantenbestand, zodat er onder andere minder kans is op inconsistentie. Er is helaas geen gemeenschappelijke server en het budget is veel te klein om deze aan te schaffen (en onderhouden). Wel is er een webhostingpakket met PHP en MySQL-ondersteuning. In dit voorbeeld slaan we alleen de voor- en achternaam op van de klanten. We zullen eerst een PHP-applicatie bouwen welke de mogelijkheid biedt om personen toe te voegen en uit te lezen. We zullen ons nog niet zozeer gaan richten op performance. Hiervoor zal ik verderop wel enkele tips geven.

PHP

Om te beginnen, gaan we een paar standaard klassen downloaden. XML-RPC for PHP is het meest gebruikte pakket hiervoor en op moment van schrijven is versie 2.2.2 de laatste stabiele versie. Aangezien we dit pakket willen gebruiken voor een server is het aan te raden geen beta-versies te gebruiken. Kopieer alles uit de lib-folder naar je projectmap.

<?php
 
//Importeer de XML-RPC-klassen
require_once 'xmlrpc.inc';
require_once 'xmlrpcs.inc';
 
//Importeer de domeinklassen
require_once 'MySQL.php';
require_once 'Person.php';
require_once 'PersonList.php';
 
//Functie om een persoon toe te voegen
function addPerson ( $params ) {
	//Parameters uitlezen
	$firstname = $params->getParam(0)->scalarval();
	$surname = $params->getParam(1)->scalarval();
 
	//Person-object aanmaken met aangeleverde data
	$person = new Person ( $firstname , $surname );
 
	//Antwoord genereren (true of false)
	$result = $person->save ( );
	//Antwoord geven
	return new xmlrpcresp ( new xmlrpcval ( $result, 'boolean' ) );
}
?>

In dit voorbeeld ga ik er vanuit dat er een Person-class is welke ten minste de volgende functies heeft:
void : Person ( $firstname , $surname )
boolean : save ( );
Als het wegschrijven van de persoon is gelukt, volgt er een true, anders een false. Deze waarde wordt teruggestuurd naar de bron, zodat deze dit kan verwerken d.m.v. een foutmelding.

Wanneer we ook willen dat de server een lijst met namen terug kan geven, zullen we een extra functie moeten maken.

getParam(0)->scalarval();
	$surname = $params->getParam(1)->scalarval();
 
	//Person-object aanmaken met aangeleverde data
	$person = new Person ( $firstname , $surname );
 
	//Antwoord genereren (true of false)
	$result = $person->save ( );
	//Antwoord geven
	return new xmlrpcresp ( new xmlrpcval ( $result, 'boolean' ) );
}
 
//Functie om een persoon toe te voegen
function listPersons ( $params ) {
	$personList = new PersonList ( );
	$personList->loadAll ( );
	$result = array ( );
	foreach ( $personList->getList ( ) as $person ) {
		$fn = new xmlrpcval ( $person->getFirstname ( ) , 'string' );
		$sn = new xmlrpcval ( $person->getSurname ( ) , 'string' );
		$res = array ( 'firstname' => $fn , 'surname' => $sn );
		$result[] = new xmlrpcval ( $res  , 'struct' );
	}
	//$struct = array ( 'listPersons' => new xmlrpcval ( $result, 'array' ) );
	//Antwoord geven
	return new xmlrpcresp ( new xmlrpcval ( $result, 'struct' ) );
}
?>

De response van listPersons() zit erg ingewikkeld in elkaar, doordat deze bestaat uit heel veel arrays. Op zich is dit logisch, omdat het vertaald moet worden naar een XML-bericht. En XML is als het ware ëën grote array.

Nu we de functies hebben gemaakt, moeten we er nog even voor zorgen dat het PHP-bestand ook daadwerkelijk wordt aangezien als een XMLRPC-server. Hiervoor plakken we de volgende code onder het bovenstaande script:

 'addPerson',
		'signature' => $add_sig,
		'docstring' => $add_doc);
$list = array('function' => 'listPersons',
		'signature' => $list_sig,
		'docstring' => $list_doc);
 
//Functies registreren
new xmlrpc_server (
	array (
		'person.add' => $add,
		'person.list' => $list
	)
);
?>

Op http://gggeek.raprap.it/debugger/ kun je je XMLRPC-server testen

Java

Nu we een XMLRPC-server hebben gemaakt, kunnen we ons gaan richten op de Java-client. Allereerst zullen we weer een API moeten downloaden. In dit geval Apache XML-RPC (zip). Kopieer alle bestanden uit de lib-folder naar de map waar je java-files komen te staan.

Nu moet je alleen nog de vijf jar-bestanden toe te voegen aan het ClassPath. Deze kun je vaak in je programmeer-omgeving wel aanpassen. Mocht je alles runnen via de command prompt of .bat-bestanden, dan moet je gebruiken:
-classpath ./ws-commons-util-1.0.2.jar;./xmlrpc-client-3.1.2.jar;etc...
De nummers zul je wellicht even aan moeten passen

Als dat eenmaal is geconfigureerd, kunnen we echt aan de slag. We beginnen eenvoudig: de domeinklassen. Beginnen met de domeinklassen is altijd het beste, aangezien deze (bijna) niet afhankelijk zijn van de andere lagen (GUI’s en data).

public class Person {
	private String firstname;
	private String surname;
 
	public Person ( String firstname , String surname ) {
		this.firstname = firstname;
		this.surname = surname;
	}
 
	public String getFirstname ( ) {
		return this.firstname;
	}
 
	public String getSurname ( ) {
		return this.surname;
	}
 
	public String toString ( ) {
		return this.getFirstname() + " " + this.getSurname();
	}
}

Met de Person-class op zak, is de domeinlaag eigenlijk al klaar in dit geval. We zouden deze nog uit kunnen breiden met een PersonList, maar voor dit voorbeeld, slaan we dat even over. We kunnen nu de datalaag opbouwen. Deze is verantwoordelijk voor de communicatie met de XML-RPC-server.

import java.util.*;
import java.net.*;
//When you have an error in the following two imports,
//please check your classpath
import org.apache.xmlrpc.*;
import org.apache.xmlrpc.client.*;
 
public class XMLRPCClient {
 
    //The location of our server.
    private final static String server_url =
		"http://www.domein.nl/XMLRPC/server.php";
	//The server we are communicating with
	private XmlRpcClient server;
 
    public XMLRPCClient ( ) {
		//Set up the configuration
		XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl ( );
	   	try {
		   	config.setServerURL ( new URL ( XMLRPCClient.server_url ) );
	   	} catch ( MalformedURLException e ) {
	   		System.out.println ( "URL not valid!" );
	   	}
	   	//Set up the client
		server = new XmlRpcClient ( );
		server.setConfig ( config );
    }
 
    public boolean addPerson ( Person p ) {
    	//Set the parameters for our request
    	Vector params = new Vector ( );
    	params.add ( p.getFirstname ( ) );
    	params.add ( p.getSurname ( ) );
 
    	try {
    		//Send the request and get the response
    		boolean result = (Boolean) server.execute("person.add", params);
    		//Translate the response to a boolean and return
    		return result;
    	} catch ( XmlRpcException e ) {
    		//Handle errors
    		System.out.println ( "ERROR: " + e.getMessage ( ) );
    	}
    	//Something went wrong, so return a false
		return false;
    }
 
    public ArrayList<Person> listPersons ( ) {
    	//Set up an empty arraylist
    	ArrayList<Person> persons = new ArrayList<Person> ( );
 
    	//Set the parameters for our request (none, so empty vector)
    	Vector params = new Vector ( );
 
    	try {
    		//Send the request and get the response
    		HashMap result = (HashMap) server.execute("person.list", params);
    		//Walk through the results
    		for ( int i=0; i < result.size ( ); i++ ) {
    			//Get row i from the received HashMap
    			HashMap hm = (HashMap) result.get ( ""+i );
    			//Get the firstname and surname
    			String fn = hm.get("firstname").toString ( );
    			String sn = hm.get("surname").toString ( );
 
    			//Create a new Person-object and add it to the arraylist
    			persons.add ( new Person ( fn , sn ) );
    		}
    	} catch ( XmlRpcException e ) {
    		//Handle errors
    		System.out.println ( "ERROR: " + e.getMessage ( ) );
    	}
 
    	//Return the ArrayList with persons
    	return persons;
    }
}

Ik heb in deze klasse drie methoden aangemaakt. Om te beginnen natuurlijk de constructor. Hierin worden alle instellingen geladen zodat we straks snel acties kunnen uitvoeren.
De tweede functie voegt een Person-object toe aan de database. Het object moet daarvoor wel eerst vertaald worden naar losse Strings. Hele objecten versturen is (voor zover ik weet) helaas niet mogelijk. De functie geeft een true of false terug, zodat we snel kunnen zien of de actie geslaagd is.
De laatste functie is natuurlijk om de lijst met namen op te halen uit de database. Deze worden netjes naar een ArrayList geparsed, zodat we er later met een for-loopje doorheen kunnen lopen.
In de code staat vrij veel commentaar. Hierin staat eigenlijk heel duidelijk uitgelegd wat er gebeurt. Ik zal dus verder geen uitleg geven over de interne werking.

Het belangrijkste werk zit er nu eigenlijk op. De case is opgelost! Natuurlijk willen we de code wel even testen en daarvoor kun je een hele eenvoudige Main-klasse schrijven, zoals:

public class Main {
	public static void main ( String[] args ) {
		XMLRPCClient xml = new XMLRPCClient ( );
		Person person = new Person ( "John" , "Doe" );
		if ( xml.addPerson ( person ) )
			System.out.println ( person + " has been added to the database!" );
		else
			System.out.println ( "An error accured while adding " + person + " to the Database" );
		for ( Person p : xml.listPersons ( ) ) {
			System.out.println ( p );
		}
	}
}

Optimaliseren

Ik moet eerlijk toegeven dat ik nog niet zo ontzettend veel ervaring heb met XML-RPC en kan daarom nog niet zoveel zeggen over de performance-optimalisatie. Wel kan ik natuurlijk wat tips geven.

1. Beperk het verkeer zo veel mogelijk
Tegenwoordig beschikken de meeste bedrijven/particulieren wel over een snelle internetverbinding. Maar dat mag geen reden zijn om helemaal niet na te denken over optimalisaties in het dataverkeer. Hoe minder data we heen en weer sturen, hoe sneller de applicatie zal laden. Daarnaast is het niet ondenkbaar dat systemen worden omgezet naar java-applicaties voor de GSM. Dan moeten we helemaal rekening houden met beperkte snelheden.
Er zijn verschillende oplossingen te bedenken:

  • Sla de gedownloade data op, op de harde schijf en update alleen de gewijzigde of nieuwe items. Hou in de database bij wanneer een item voor het laatst is gewijzigd en leg de laatste update-tijd vast op de lokale pc. Bij het binnenhalen van de data, kun je de laatste update-tijd meegeven en de bewerkte items binnenhalen.
  • Verspreid de contactpersonen over meerdere categorieën. Iemand van de sales-afdeling hoeft bijvoorbeeld geen namen van leveranciers binnen te halen.
  • Laadt alleen in wat je echt nodig hebt. Bij het openen van een factuur, hoef je niet alle klanten binnen te halen, maar alleen die met klantnummer x. Wil je zoeken in de contacten? Laat dat de server lekker uitzoeken…

2. Beveilig je servers
Het klinkt misschien logisch, maar er zijn nog zat mensen die proberen weg te komen met “Maar de url is toch niet bekend?”. Hou er rekening mee dat je bepaalde mensen wel eens wilt kunnen afsluiten. Een ontslagen medewerker die de software nog op zijn laptop heeft staan, kan ontzettend veel schade aanbrengen.

  • Geef iedere medewerker een eigen account. Dit geeft de mogelijkheid tot een beter rechtenbeheer, blokkering en de mogelijkheid om logboeken aan te leggen.
  • Laat de gebruiker sowieso inloggen wanneer het systeem niet-publieke informatie bevat, zoals deze relatie-database. Inloggen via XML-RPC is net zo makkelijk als een normaal loginsysteem. Bij het inloggen maak je een sessie aan en je geeft de SessionID terug aan de client. Deze kan het ID dan gebruiken gedurende zijn werk. Ook kun je natuurlijk bij iedere request de gebruikersnaam en wachtwoord meesturen. Dan hoef je niet met sessies te werken, maar het kost wel meer performance wanneer je meerdere requests doet. Wanneer je eenmaal per dag de gewijzigde/toegevoegde relaties binnen wilt halen, is dit natuurlijk wel een prima oplossing.
  • Controleer de gebruikersinvoer! Ook via XML-RPC kun je een SQL-injection geven. Controleer dus altijd of de input voldoet aan de verwachtingen. Is het een nummer, is het hoger dan 0, bevat het geen vreemde tekens…

3. Bepaal waar de meeste resources liggen
Het is belangrijk om te weten waar de meeste kracht zit in het netwerk. Wanneer je een goedkoop klein hosting-pakket hebt, is het niet verstandig om daar hele grote berekeningen uit te laten voeren. In dat geval kun je beter ruwe data terugsturen en de Java-applicatie het lekker op laten lossen.
Wordt de data verzonden naar een GSM, dan kun je er maar beter voor zorgen dat alles op de server wordt uitgerekend en gefilterd.

4. Hou het functioneel
Het is ontzettend verleidelijk om nu voor de eerste de beste applicatie een XML-RPC-verbinding op te zetten, maar in veel gevallen is dit regelrechte onzin en geldverspilling. XML-RPC brengt in dit soort gevallen ook onnodige risico’s met zich mee. Een defecte server kan betekenen dat niemand meer kan werken.
Zorg er dus voor dat je enkel met XML-RPC aan de slag gaat als datacommunicatie echt belangrijk is en probeer de applicaties dan zo veel mogelijk op te splitsen. Het is bijvoorbeeld veel beter om een losse applicatie te maken voor een postcodecheck, dan dit te verwerken in een relatiebeheer-applicatie. Door de elementen te verspreiden, kun je ze veel gemakkelijk hergebruiken.

Conclusie

XML-RPC is een geweldig communicatiemiddel en maakt het mogelijk om de server- en clientsoftware los van elkaar te ontwikkelen. Vrijwel iedere programmeertaal is op deze manier aan elkaar te verbinden en dat biedt veel mogelijkheden.

Op moment van schrijven, zit ik midden in project waarbij XML-RPC een absolute must was. We zijn namelijk bezig een computerspel te programmeren waarbij de spelers ook online kunnen spelen. De verbinding tussen de spelers wordt opgezet met socketverbindingen, maar we wilden ook een verbinding met een webserver die bij houdt welke ‘rooms’ er actief zijn en hoeveel spelers er in een spel zitten. De spelers kunnen nu op Join Game klikken en krijgen dan een overzicht met actieve spellen waar ze deel aan kunnen nemen.
Het gebruiken van een shared hosting brengt als grote voordeel met zich mee, dat er weinig onderhoud gepleegd hoeft te worden. Je hebt in ieder geval geen systeembeheerder meer nodig die elke week beveiligingsupdates zit te installeren.

Wat mij betreft is XML-RPC het perfecte communicatiemiddel.

121 Comments

jeremiahJanuary 26th, 2015 at 17:53

.

tnx for info!!…

louisJanuary 26th, 2015 at 18:29

.

ñïñ….

lesterJanuary 28th, 2015 at 07:38

.

ñýíêñ çà èíôó!…

CecilJanuary 28th, 2015 at 20:44

.

tnx….

OscarJanuary 31st, 2015 at 17:59

.

ñïñ çà èíôó!…

BrettFebruary 1st, 2015 at 03:48

.

ñýíêñ çà èíôó!…

MattFebruary 1st, 2015 at 07:03

.

good!…

ChrisFebruary 1st, 2015 at 07:37

.

ñïñ çà èíôó….

RonnieFebruary 3rd, 2015 at 04:21

.

ñýíêñ çà èíôó….

ElmerFebruary 3rd, 2015 at 06:22

.

thank you….

RickFebruary 7th, 2015 at 16:47

.

tnx….

stevenFebruary 7th, 2015 at 17:19

.

good info….

EduardoFebruary 8th, 2015 at 00:12

.

ñïñ çà èíôó!…

KeithFebruary 8th, 2015 at 12:52

.

hello!…

gilbertFebruary 9th, 2015 at 01:15

.

ñýíêñ çà èíôó!!…

JoeyFebruary 9th, 2015 at 23:14

.

ñïàñèáî!…

royFebruary 10th, 2015 at 01:25

.

thank you!!…

martinFebruary 11th, 2015 at 01:39

.

tnx!!…

CraigFebruary 11th, 2015 at 02:19

.

good info….

VincentFebruary 13th, 2015 at 13:38

.

ñïñ çà èíôó….

alfonsoFebruary 14th, 2015 at 03:41

.

tnx for info!!…

Leave a comment

Your comment