PA165/Cvičení Remoting
Obsah
JavaRMI
Nejdřív si předvedeme JavaRMI.
Na tabule.jar je JAR soubor s následující definicí:
package cz.muni.fi.pa165.rmi; import java.rmi.Remote; import java.rmi.RemoteException; public interface Tabule extends Remote { void zverejni(String text) throws RemoteException; }
Klient
Vytvořte si projekt, přidejte k němu JAR s rozhraním a vytvořte klienta:
package cz.muni.fi.pa165.rmi; import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; import java.rmi.RemoteException; import java.rmi.NotBoundException; public class Pisalek { public static void main(String[] args) throws RemoteException, NotBoundException { if(args.length!=2) { System.out.println("Usage: java "+Pisalek.class.getName()+" host text"); System.exit(1); } String host = args[0]; String text = args[1]; Registry remregistry = LocateRegistry.getRegistry(host); Tabule tabule = (Tabule) remregistry.lookup("tabule"); tabule.zverejni(text); } }
Klienta spusťte vůči serveru cvičícího na nymfe31.fi.muni.cz.
(Pro ty, kdo neumí zkompilovat a spustit javovou třídu, tak z příkazového řádku se to udělá takto:
mkdir rmicviceni cd rmicviceni wget http://acrab.ics.muni.cz/~makub/rmi/tabule.jar mkdir -p cz/muni/fi/pa165/rmi vi cz/muni/fi/pa165/rmi/Pisalek.java javac -cp tabule.jar:. cz/muni/fi/pa165/rmi/Pisalek.java java -cp tabule.jar:. cz.muni.fi.pa165.rmi.Pisalek nymfe31 'neco chytreho'
nebo v NetBeans si vytvoříte nový projekt pomocí File - New Project - Categories: Java - Projects: Java Application, v dialogu 'Create Main Class zadat cz.muni.fi.pa165.rmi.Pisalek, pak v Libraries zvolit Add JAR/Folder, přidat soubor tabule.jar, přepsat třídu Pisalek, na projektu pak zvolit Properties - Run - Arguments a do políčka napsat nymfe31 'neco chytreho' . Pak spustit projekt. Z příkazové řádky je to jednodušší, že. )
Server
Utvořte dvojice se sousedem a vyzkoušejte komunikaci s jeho serverem.
Na straně serveru je třeba spustit na pozadí program
rmiregistry &
mít implementaci serveru:
package cz.muni.fi.pa165.rmi; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.server.ServerNotActiveException; import java.rmi.server.UnicastRemoteObject; public class TabuleImpl implements Tabule { synchronized public void zverejni(String text) throws RemoteException { try { System.out.println(UnicastRemoteObject.getClientHost() + ": " + text); } catch (ServerNotActiveException e) { e.printStackTrace(); } } public static void main(String[] args) throws RemoteException { System.out.println("startuji Tabuli"); Tabule stub = (Tabule) UnicastRemoteObject.exportObject(new TabuleImpl(), 0); LocateRegistry.getRegistry().rebind("tabule", stub); } }
která musí být zkompilována
javac -cp tabule.jar:. cz/muni/fi/pa165/rmi/TabuleImpl.java
a spuštěna se správnými parametry:
java -Djava.rmi.server.codebase=http://acrab.ics.muni.cz/~makub/rmi/tabule.jar -cp tabule.jar:. cz.muni.fi.pa165.rmi.TabuleImpl
Pozor, v B130 v listopadu 2012 je nesmyslně zkonfigurovaný /etc/hosts, váže název stroje na lokální adresu, takže nelze kontaktovat jiné stroje přes RMI. Proto je potřeba použít následující:
MYADDR=`ifconfig | awk -F':' '/inet addr/&&!/127.0.0.1/{split($2,_," ");print _[1]}'` echo $MYADDR java -Djava.rmi.server.codebase=http://acrab.ics.muni.cz/~makub/rmi/tabule.jar \ -Djava.rmi.server.hostname=$MYADDR -cp tabule.jar:. cz.muni.fi.pa165.rmi.TabuleImpl
Metoda v rozhraní Tabule nepřijímá žádné třídy, které by JVM na straně serveru neznal. Pokud by tomu tak bylo, i klient by musel vystavit takové třídy někde na webu a musel by být spuštěn s parametrem -Djava.rmi.server.codebase
, aby si server mohl třídy stáhnout.
JSON-RPC
Ukážeme si vzdálené volání procedur (RPC) pomocí protokolu JSON-RPC 2.0. K implementaci použijeme knihovnu jsonrpc4j.
Rozhraní služby
Stáhněte si Mavenový projekt tabule-json-rpc.zip, rozzipujte, a nainstalujte, bud pomocí mvn install nebo otevřením v NetBeans a zvolením Build.
Projekt obsahuje definici rozhraní a vyjímky:
Tabule.java
package cz.muni.fi.pa165.jsonrpc; public interface Tabule { void zverejni(String text) throws Vyjimka; }
Vyjimka.java
package cz.muni.fi.pa165.jsonrpc; public class Vyjimka extends Exception { public Vyjimka(String message) { super(message); } }
JSON-RPC server
Vytvořte si nový projekt typu Maven s názvem tabule-json-rpc-server a do pom.xml připište závislost na projektu tabule-json-rpc a knihovnách pro JSON-RPC:
<name>tabule-json-rpc-server</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <repositories> <repository> <id>jsonrpc4j-webdav-maven-repo</id> <name>jsonrpc4j maven repository</name> <url>http://jsonrpc4j.googlecode.com/svn/maven/repo/</url> <layout>default</layout> </repository> </repositories> <dependencies> <dependency> <groupId>cz.muni.fi.pa165</groupId> <artifactId>tabule-json-rpc</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>com.googlecode</groupId> <artifactId>jsonrpc4j</artifactId> <version>0.25</version> </dependency> <dependency> <groupId>javax.portlet</groupId> <artifactId>portlet-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat-servlet-api</artifactId> <version>7.0.27</version> </dependency> <dependency> <groupId>org.simpleframework</groupId> <artifactId>simple</artifactId> <version>4.1.21</version> </dependency> </dependencies>
Vytvořte následující třídy v balíku cz.muni.fi.pa165.jsonrpc:
TabuleImpl.java
package cz.muni.fi.pa165.jsonrpc; public class TabuleImpl implements Tabule { @Override public void zverejni(String text) throws Vyjimka { if (text == null || text.isEmpty()) throw new Vyjimka("text je prázdný"); System.out.println(SimpleServer.getClient()+": " + text); } }
SimpleServer.java
package cz.muni.fi.pa165.jsonrpc; import com.googlecode.jsonrpc4j.JsonRpcServer; import java.io.IOException; import java.net.InetSocketAddress; import java.util.logging.*; import org.simpleframework.http.Request; import org.simpleframework.http.Response; import org.simpleframework.http.core.Container; import org.simpleframework.transport.connect.Connection; import org.simpleframework.transport.connect.SocketConnection; public class SimpleServer implements Container { private JsonRpcServer jsonRpcServer; public SimpleServer(JsonRpcServer jsonRpcServer) { this.jsonRpcServer = jsonRpcServer; } private static ThreadLocal<String> client = new ThreadLocal<String>(); static String getClient() { return client.get(); } @Override public void handle(Request request, Response response) { try { if ("POST".equals(request.getMethod())) { response.set("Content-Type", "application/json"); client.set(request.getClientAddress().getHostName()); jsonRpcServer.handle(request.getInputStream(), response.getOutputStream()); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws IOException { //enable logging Logger log = LogManager.getLogManager().getLogger(""); log.setLevel(Level.ALL); for (Handler h : log.getHandlers()) h.setLevel(Level.FINEST); //start server Logger.getLogger(SimpleServer.class.getName()).info("starting server"); JsonRpcServer tabule = new JsonRpcServer(new TabuleImpl(), Tabule.class); Connection connection = new SocketConnection(new SimpleServer(tabule)); connection.connect(new InetSocketAddress(1420)); } }
Spusťte server.
JSON-RPC klient
Vytvořte nový projekt typu Maven jménem tabule-json-rpc-klient a do pom.xml připište:
<repositories> <repository> <id>jsonrpc4j-webdav-maven-repo</id> <name>jsonrpc4j maven repository</name> <url>http://jsonrpc4j.googlecode.com/svn/maven/repo/</url> <layout>default</layout> </repository> </repositories> <dependencies> <dependency> <groupId>cz.muni.fi.pa165</groupId> <artifactId>tabule-json-rpc</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>com.googlecode</groupId> <artifactId>jsonrpc4j</artifactId> <version>0.25</version> </dependency> </dependencies>
Vytvořte třídu
SimpleClient.java
package cz.muni.fi.pa165.jsonrpc; import com.googlecode.jsonrpc4j.JsonRpcHttpClient; import com.googlecode.jsonrpc4j.ProxyUtil; import java.net.MalformedURLException; import java.net.URL; public class SimpleClient { public static void main(String[] args) throws MalformedURLException, Vyjimka { JsonRpcHttpClient client = new JsonRpcHttpClient(new URL("http://localhost:1420/")); Tabule tabule = ProxyUtil.createClientProxy(SimpleClient.class.getClassLoader(), Tabule.class, client); tabule.zverejni("ahoj"); //tabule.zverejni(null); } }
Spusťte klienta. Pak zkuste odkomentovat řádek s tabule.zverejni(null); a spusťte ho znovu.
Samostatný úkol - změna rozhraní
V původním projektu tabule-json-rpc přidejte do rozhraní Tabule nějakou další metodu, dejte Build.
V projektu tabule-json-rpc-server implementujte metodu ve třídě TabuleImpl a znovu spusťte server.
V projektu tabule-json-rpc-klient zavolejte novou metodu.
Pozorujte, jak vypadají JSON-RPC zprávy v logu serveru.
SOAP/WSDL Web Services
Ukážeme si web services pomocí Apache CXF.
SOAP server
Vytvořte si nový projekt typu Maven zvaný třeba soap-cxf-server. Do pom.xml připište:
<properties> <cxf.version>2.2.4</cxf.version> </properties> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <!-- Jetty is needed if you're are not using the CXFServlet --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> </dependencies> <build> <plugins> <!-- nastaveni verze zdrojaku --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>6</source> <target>6</target> </configuration> </plugin> </plugins> </build>
Vytvořte package cz.muni.fi.pa165.soap a v něm tyto třídy:
package cz.muni.fi.pa165.soap; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.ws.WebFault; @WebFault(name = "Vyjimka") @XmlAccessorType(XmlAccessType.FIELD) public class Vyjimka extends RuntimeException { String duvod; public Vyjimka(String message,String duvod) { super(message); this.duvod = duvod; } }
package cz.muni.fi.pa165.soap; import javax.jws.WebService; import javax.jws.WebParam; @WebService public interface Tabule { void zverejni(@WebParam(name = "text")String text) throws Vyjimka; }
package cz.muni.fi.pa165.soap; import javax.annotation.Resource; import javax.jws.WebService; import javax.servlet.http.HttpServletRequest; import javax.xml.ws.WebServiceContext; import javax.xml.ws.handler.MessageContext; @WebService(endpointInterface = "cz.muni.fi.pa165.soap.Tabule", serviceName = "Tabule") public class TabuleImpl implements Tabule { @Resource WebServiceContext wsCtx; public void zverejni(String text) throws Vyjimka { if (text == null || text.isEmpty()) throw new Vyjimka("empty text", "retezec prazdny"); System.out.println(getClient() + ":" + text); } private String getClient() { MessageContext msgCtx = wsCtx.getMessageContext(); HttpServletRequest req = (HttpServletRequest) msgCtx.get(MessageContext.SERVLET_REQUEST); return req.getRemoteHost(); } }
package cz.muni.fi.pa165.soap; import javax.xml.ws.Endpoint; public class Server { public static void main(String[] args) throws InterruptedException { String address = "http://localhost:9000/tabule"; Endpoint.publish(address, new TabuleImpl()); System.out.println("Server ready..."); } }
Spusťte třídu cz.muni.fi.pa165.soap.Server a navštivte URL http://localhost:9000/tabule?wsdl.
SOAP klient
Aniž uzavřete předchozí projekt, vytvořte nový projekt zvaný třeba soap-cxf-klient. Děláme to tak proto, abychom viděli, že jediným pojítkem mezi serverem a klientem je WSDL popis služby.
Do pom.xml připište
<properties> <cxf.version>2.2.4</cxf.version> </properties> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-common-utilities</artifactId> <version>${cxf.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>${cxf.version}</version> <executions> <execution> <id>generate-sources</id> <configuration> <wsdlOptions> <wsdlOption> <wsdl>http://localhost:9000/tabule?wsdl</wsdl> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin> <!-- nastaveni verze zdrojaku --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>6</source> <target>6</target> </configuration> </plugin> <!-- zavislosti na JARech --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <configuration> <outputDirectory> ${project.build.directory} </outputDirectory> </configuration> </plugin> </plugins> </build>
Přidejte třídu:
package cz.muni.fi.pa165.soap; public class Klient { public static void main(String[] args) { Tabule tabule = new Tabule_Service().getTabuleImplPort(); try { tabule.zverejni("ahoj"); tabule.zverejni(""); } catch (Vyjimka_Exception e) { e.printStackTrace(); System.out.println(e.getFaultInfo().getDuvod()); } } }
Klikněte pravým tlačítkem a vyberte Maven - Update folders. Alternativně můžete z příkazového řádku spustit mvn generate-sources a mvn package.
Spusťte třídu Klient.
Pro kopii všech knihoven potřebných pro spuštění z příkazového řádku zadejte příkaz mvn dependency:copy-dependencies.
URL na službu se bere z WSDL popisu služby. Pokud ho potřebujete změnit, lze to udělat takto:
((BindingProvider) tabule).getRequestContext() .put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://nymfe31.fi.muni.cz:9000/tabule");