REST
Willemers Informatik-Ecke
Java Server Pages SOAP

Das REST in RESTful-Service steht für Representational State Transfer. Ein RESTful-Service stellt eine Ressource zur Verfügung, die durch ihre URI eindeutig identifiziert wird.

REST stellt einen Verzeichnisbaum für die Ressourcen einer Anwendung zur Verfügung. Eine Klasse dieses Verzeichnisbaums wird durch eine eindeutige URL gekennzeichnet.

Auf jede dieser Klassen können HTTP-Methoden wie GET, PUT, POST und DELETE angewandt werden. Dabei haben diese Methoden folgende semantische Bedeutung:

REST-Server sind stateless, eine Anfrage kann also nicht auf einer vorhergehenden Anfrage aufbauen. Entsprechend gibt es keine Transaktionen wie bei Datenbanken.

Da ein REST-Server über HTTP liefert, werden die Daten typischerweise für die serialisierte Übertragung konvertiert. Typisch sind dabei XML und JSON.

Der Client legt durch seinen Accept- bzw. Content-Type-Parameter fest, in welchem Format er seine Daten transportiert sehen will. Ist der Server nicht in der Lage, das gewünschte Format zu liefern, sendet er eine HTTP-Fehlermeldung, die mit 5 beginnt.

JAX-RS: Jersey

JAX-RS ist die Java-API für RESTful Web Services. Mit JAX-RS 2.0 steht auch eine Client-API zur Verfügung. Die Implementierung erfolgt in der Open-Source-Bibliothek Jersey.

Ein RESTful Service wird in Java auf der Basis eines Application-Servers aufgesetzt, gehört also zum Thema Jakarta bzw. Java Enterprise Edition. Dieser Artikel beschreibt, wie ein solcher Service programmiert wird und wie man ihn mit Java-Clients nutzt.

Erster Testlauf: REST-Projekt mit Eclipse

Zunächst wird ein REST-Projekt erstellt. Voraussetzung ist eine Eclipse-Glassfish-Entwicklungsumgebung.

Download der Jersey-Bibliotheken

Auf der Jersey-Homepage gibt es eine Download-Seite. Dort befindet sich unter der Überschrift JAX-RS 2.1 / Jersey 2.26+ ein Link auf das aktuelle Jersey JAX-RS 2.1 RI bundle.

Der darunter liegende Link verweist auf auf eine http://-Seite. Allerdings muss hier eine https://-Adresse verwendet werden, sonst kann die Datei nicht heruntergeladen werden.

Ändern Sie die URL entsprechend oder verwenden Sie diesen Link für den Download von jaxrs-ri-2.30.1.zip

Integration der Jersey-Jars in das Projekt

In der ZIP-Datei jaxrs-ri-XXXX.zip befinden sich in den Unterverzeichnissen api, lib und ext mehrere Jar-Dateien.

Application und @ApplicationPath

Anstatt den Pfadnamen in der Datei web.xml festzulegen, können Sie auch die Klasse Application erweitern und dort in der Annotation @ApplicationPath den Pfad setzen.
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("app")
public class HalloApplication extends Application {
}

Service-Klasse

Der eigentliche Server wird durch eine eigene Klasse realisiert, deren Rahmen durch Eclipse erzeugbar ist. Es entsteht ein Rahmenprogramm Hallo.java.
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;

@Path("service")
public class Hallo {
    @SuppressWarnings("unused")
    @Context
    private UriInfo uriInfo;

    /**
     * Default constructor. 
     */
    public Hallo() {
        // TODO Auto-generated constructor stub
    }
    
    /**
     * Retrieves representation of an instance of Hallo
     * @return an instance of String
     */
    @GET
    @Produces("application/xml")
    public String getXml() {
        // TODO return proper representation object
        throw new UnsupportedOperationException();
    }

    /**
     * PUT method for updating or creating an instance of Hallo
     * @param content representation for the resource
     * @return an HTTP response with content of the updated or created resource.
     */
    @PUT
    @Consumes("application/xml")
    public void putXml(String content) {
    }
}
Diese Server-Klasse wird auf GET- und PUT-Anfragen mit XML als Datenformat reagieren, indem es die Methoden getXml und putXml aufruft.

Um zu testen, ob diese Klasse arbeitet und auf Anfragen reagiert, bauen wir noch eine zusätzliche Methode ein, die auf eine GET-HTML-Anfrage reagiert.

@GET
@Produces(MediaType.TEXT_HTML)
public String sagHtml() {
    return "<html><body><h1>Hallo!</h1></body></html>";
}
Wichtig: Das Beantworten von HTML-Anfragen ist nicht der Zweck eines REST-Servers, aber praktisch in der Testphase.

Test Datenaustausch mit einem String

Nun kennen wir die URL. Über den Browser kann getestet werden, ob der Server korrekt läuft. Im nächsten Schritt soll ein Client Daten abholen.

Dazu wird in der Server-Klasse die getXml-Methode geändert. Sie liefert nun einen wiedererkennbaren String.

@GET
@Produces("application/xml")
public String getXml() {
    // throw new UnsupportedOperationException();
    return "XML-String";
}
Den zurückgegebenen String soll nun ein REST-Client empfangen, der auf der Basis von HttpURLConnection geschrieben wurde.

Dazu wird ein zusätzliches Java-Projekt angelegt und die Klasse RestClientHttp angelegt.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class RestClientHttp {
    public static void get(String url) {
        try {
            HttpURLConnection verbind =
                    (HttpURLConnection) new URL(url).openConnection();
            verbind.setRequestProperty("Accept", "application/xml");
            verbind.setRequestMethod("GET");
            InputStream inStream = verbind.getInputStream();
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(inStream));
            String empfangeneZeile;
            StringBuffer antwort = new StringBuffer();

            while ((empfangeneZeile = in.readLine()) != null) {
                antwort.append(empfangeneZeile + "\n");
            }
            in.close();
            System.out.println(antwort.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        String url = "http://localhost:8080/TestRest/app/service";
        get(url);
    }
}
Nach dem Start dieses Clients sollte die Meldung XML-String auf der Console erscheinen.

Für einen Datenaustausch könnte man hier beliebige Daten zu XML konvertieren, auf der Client-Seite einlesen und wieder zu den ursprünglichen Daten zurück konvertieren.

Datenaustausch mit XML

Beim Anlegen des Web Services kann unter Representation class eine vorbelegte Klasse übernommen werden. Diese sollte allerdings in ein einem package abgelegt werden und idealerweise eine Bean sein.

Als Beispiel erzeugen wir ein Projekt RestXml und verwenden darin eine Wohnung. Damit sie in XML umgewandelt werden kann, muss sie mit @XmlRootElement gekennzeichnet werden.

package data; // Wichtig: Muss in einem package liegen!

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Wohnung {
    private String bez;
    private int qm;
    private double miete;
...
Natürlich benötigen wir auch in diesem Fall eine Application.
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("app")
public class XmlApp extends Application {
}
Nun wird die Service-Klasse erzeugt. Wir lassen uns von Eclipse helfen, indem wir das Projekt mit der rechten Maustaste anklicken und dann New|RESTful Web Service from Pattern wählen. Der Dialog wird folgendermaßen gefüllt: Das folgende Listing zeigt die erzeugte Klasse. Der Übersicht halber sind die Kommentare und import-Anweisungen entfernt worden: Der Path wurde bereits von Hand auf wohnung umgestellt.

@Path("wohnung")
public class ServiceWohnung {
    @SuppressWarnings("unused")
    @Context
    private UriInfo uriInfo;

    public ServiceWohnung() {
    }

    @GET
    @Produces("application/xml")
    public Wohnung getXml() {
        // TODO return proper representation object
        throw new UnsupportedOperationException();
    }

    @PUT
    @Consumes("application/xml")
    public void putXml(Wohnung content) {
    }
}
Die Methode getXml hat als Rückgabetyp die Klasse Wohnung. Hier erzeugen wir eine Wohnung, belegen sie mit Werten vor und geben sie per return zurück:
    @GET
    @Produces("application/xml")
    public Wohnung getXml() {
        Wohnung w = new Wohnung();
        w.setBez("Parterre");
        w.setMiete(500.0);
        w.setQm(80);
        return w;
    }
Zum Test benötigen wir einen Client, der die Anfrage ausführt und das Ergebnis anzeigt. Dazu verwenden wir den HttpURLConnection-Client von oben und ändern nur die URL im Hauptprogramm:
public static void main(String[] args) {
    String url = "http://localhost:8080/RestXml/app/wohnung";
    get(url);
}
Nach dem Start erhalten wir den automatisch erzeugten XML-String. Die Zeilenumbrüche und Einrückungen habe ich der Deutlichkeit halber von Hand nachgearbeitet.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<wohnung>
    <bez>Parterre</bez>
    <miete>500.0</miete>
    <qm>80</qm>
</wohnung>

Objektaustausch an Jersey-Client mit XML

Wenn Jersey die XML-Wandlung auf Server-Seite hinbekommt, können wir diese Qualität ja auch auf der Client-Seite nutzen.

Jersey-Bibliotheken in ein Java-Projekt einfügen

Die Wohnung anlegen

Damit beide vom gleichen Objekt reden, muss natürlich auch der Client eine Klasse Wohnung definieren. Damit es gleich ist, kommt es auch hier in ein Package data.

Ein Jersey-Client

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.client.ClientConfig;
import data.Wohnung;

public class RestClient {

    static final String PACKAGE = "RestXml";
    static final String URL = "app";
    static final String PATH = "wohnung";

    public static void main(String[] args) {
        ClientConfig clientconfig = new ClientConfig();
        Client client = ClientBuilder.newClient(clientconfig);
        WebTarget target = client.target(UriBuilder
                .fromUri("http://localhost:8080/"+PACKAGE).build());
        Wohnung wohnung = target.path(URL).path(PATH).request()
                .accept(MediaType.APPLICATION_XML).get(Wohnung.class);
        System.out.println(wohnung.getBez());
    }
}
Nach dem Start gibt das Programm das Wort Parterre aus. Miete und Zimmerzahl dürften auch stimmen.

Jersey-Client-API

Herzlichen Glückwunsch! Wir haben ein Objekt der Klasse Wohnung per XML übertragen, ohne den XML-Code überhaupt nur einmal anzusehen oder von Hand zu bearbeiten.

CRUD und die REST-API

Der bisherige Server beantwortet nur GET-Anfragen. Letztlich soll aber ein REST-Server eine Ressource mit den typischen Lese- und Schreibaktionen verwalten. Aus diesem Grund spricht man auch von einem CRUD (Create, Read, Update, Delete) oder RUDI (Read, Update, Delete, Insert).

Diese vier Operationen werden durch die HTTP-Methoden GET, POST, DELETE und PUT angesteuert.

Die Klasse Kunde

Letztlich dreht sich in der ganzen Wirtschaft alles um den Kunden, so auch hier. Und für diesen Kunden gibt es eine Klasse, die den Schlüssel nr sowie den Namen name und die Adresse adresse aufnimmt.
package kunde;

import java.io.Serializable;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Kunde implements Serializable {
    private static final long serialVersionUID = 1L;
    private String nr;
    private String name;
    private String adresse;

    public Kunde() {
    }

    public Kunde(String nr, String name, String adresse) {
        this.nr=nr; this.name=name; this.adresse=adresse;
    }

    public String toString() {
        String out = "";
        out += getNr() + ":" + getName() + ":" + getAdresse();
        return out;
    }
    public String getNr() {    return nr; }
    public void setNr(String nr) { this.nr = nr; }
    public String getName() { return name; }
    public String getAdresse() { return adresse; }
    public void setName(String name) {this.name=name; }
    public void setAdresse(String adresse) {this.adresse=adresse; }
}
Das eigentlich Wichtige ist die Annotation @XmlRootElement. Und natürlich befindet sich der Kunde in einem Package kunde.

Application

Auch hier erweitern wir die Klasse Application.
package kunde;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("app")
public class KundeApplication extends Application {
}

Die Schnittstellen-Klasse für CRUD

Wieder lässt sich die eigentliche Server-Klasse auch durch Eclipse erstellen. Die Basis ist dieses Mal die Klasse kunde.Kunde. Durch die Annotation @Path wird der Pfad des Servers bestimmt.

Die Klasse besitzt eine Instanz auf die DAO-Klasse, die sich um die Speicherung der Kunden kümmern soll. Wichtig ist, dass es sich um eine statische Variable handelt.

package kunde;

@Path("kunde")
public class KundenServer {
    static KundeDao kundeDao = new KundeDao();

    @Context
    private UriInfo uriInfo;
Die UriInfo wird automatisch erzeugt, und tatsächlich später von der POST-Methode benötigt.

GET-Methoden

Unsere bisherige GET-Methode lieferte ein Objekt zurück. So einfach ist die Welt aber bei Datenbeständen nicht. Bei einer Anfrage bekomme ich entweder ganz viele Kunden oder ich muss ein Kriterium angeben, welchen Kunden ich suche.

Hole eine Liste

Die GET-Methode getKundenXml liefert eine komplette Liste aller Kunden. Im Gegensatz zu unserem bisherigen GET liefert sie eine Liste.
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public List<Kunde> getKundenXml() {
        System.out.println("get application/xml angefragt");
        return kundeDao.getKunden();
    }
Es bedarf noch einiger import-Anweisungen, für die Eclipse allerdings recht gute Vorschläge liefert, wenn man [Strg]+[Shift]+[O] drückt.

Das DAO kümmert sich später darum, wo die Kunden genau herkommen. Dadurch kann der Service hier schlank bleiben.

Einen einzelnen Kunden holen

Um einen einzelnen Kunden zu holen, muss der Pfad verändert werden. In diesem Fall wird ein zusätzlicher Pfad id an die URL gehängt. Um einen einzelnen Kunden zu holen, muss an diesen Pfad die Kundennummer gehängt werden.

Die Methode getEinenKundenXml weist den Parameter hinter id dem String id zu und übergibt ihn an die Methode getKunde der DAO-Klasse.

    @GET
    @Produces(MediaType.APPLICATION_XML)
    @Path("{id}")
    public Kunde getEinenKundenXml(@PathParam("id") String id) {
        Kunde kunde = kundeDao.getKunde(id);
        if (kunde==null)
            throw new NotFoundException();
        return kunde;
    }

HTML für Testzwecke

Die GET-HTML-Methode ist eigentlich ein Fremdkörper. Sie erweist sich aber als ganz praktisch, wenn man schnell per Browser ein paar Daten sehen will.
    @GET
    @Produces(MediaType.TEXT_HTML)
    public String getKundenHtml() {
        String out = "";
        List<Kunde> kunden = kundeDao.getKunden();
        for (Kunde kunde : kunden) {
            out += kunde.toString() + "<br>";
        }
        return out;
    }

POST-Methoden

Mit der POST-Methode werden neue Kunden angelegt. Dieses Mal wird der Kunde als Parmaeter übergeben. Der Rückgabetyp ist Response, in der der Server seine Klagen loswerden kann, wenn etwas nicht geklappt hat.

War alles in Ordnung, möchte der Client sicher gern wissen, unter welcher ID der Kunde abgelegt wurde.

    @POST
    @Consumes(MediaType.APPLICATION_XML)
    public Response postKunde(Kunde kunde) {
        String pos = kundeDao.neuKunde(kunde);
        if (pos==null || pos.isEmpty()) {
            throw new WebApplicationException(
                    Response.status(400).entity("Not created.").build());
        }
        URI location = uriInfo.getAbsolutePathBuilder().path("" + pos).build();
        return Response.created(location).build();
    }
Auch beim POST können wir eine Methode einbauen, die auf HTML reagiert. Das macht es einfach, mit dem Browser ein Menü zu bauen und die Daten zu übermitteln. Aber auch hier gilt, dass das nicht eigentlich der Zweck eines REST-Services ist.

Wirklich interessant ist hier, wie die Parameter der Eingabefelder in Parametervariable mit der Annotation @FormParam übernommen werden.

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    // Für die Erzeugung über ein Browserformular
    public Response postKunde(
            @FormParam("nr") String nr,
            @FormParam("name") String name,
            @FormParam("adresse") String adresse) {

        if (name == null || nr == null || adresse == null) {
            throw new WebApplicationException(
                Response.status(400).entity("Could not be created.").build());
        }
        Kunde kunde = new Kunde(nr, name, adresse);
        String pos = kundeDao.neuKunde(kunde);
        URI location = uriInfo.getAbsolutePathBuilder().path("" + pos).build();
        return Response.created(location).build();
    }

DELETE-Methode

Das Löschen erfolgt über den Schlüssel, der ebenso wie bei der Abfrage eines einzelnen Kunden über die URL abgegeben wird. Insofern ist auch der Rest der Methode keine große Überraschung.
    @DELETE
    @Path("{id}")
    public Response deleteKunden(@PathParam("id") String id) {
        if (kundeDao.deleteKunde(id)) {
            return Response.accepted().build();
        }
        throw new NotFoundException();
    }
}

Die Datenverwaltung DAO

Die REST-Schnittstelle kümmert sich um die Aufspaltung der Anfragen und die Übernahme und Übergabe der Werte. Diese Daten müssen irgendwo abgelegt werden. Dazu bietet sich natürlich eine Datenbank an. Die Schicht zwischen Service-Klasse und Datenbanken wird als Data Access Objects (DAO) bezeichnet.

Für dieses Beispiel werden die Daten einfach in ArrayLists abgelegt.

package kunde;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class KundeDao {

    private List<Kunde> kunden = new ArrayList<Kunde>();
    
    public List<Kunde> getKunden() {
        return kunden;
    }

    public Kunde getKunde(String id) {
        for (Kunde k : kunden) {
            if (k.getNr().equals(id))
                return k;
        }
        return null;
    }

    public String neuKunde(Kunde kunde) {
        kunden.add(kunde);
        return ""+kunde.getNr();
    }

    public boolean deleteKunde(String id) {
        boolean found =false;
        Iterator<Kunde> k = kunden.iterator();
        while (k.hasNext()) {
            Kunde kunde = k.next();
            if (kunde.getNr().equals(id)) {
                k.remove();
                found = true;
            }
        }
        return found;
    }
}
Diese DAO ist natürlich nichts für den Realbetrieb, reicht aber schon aus, den REST-Service zu testen.

Der Jersey-Client für REST

Wenn Sie Jersey auch für Ihren Client nutzen, muss dieses zwar auch die Jersey-Libraries mit sich herumschleppen, aber der Quelltext schlanker. Hinzu kommt, dass Ihnen Jersey auch gleich die Umwandlung Ihrer Klassen in XML erledigt.

Im ersten Teil werden die Importe erledigt. Da hilft Eclipse gern mit [Strg]+[Shift]+[O]. Manchmal fragt Eclipse dabei nach, welche Bibliothek es sein soll. Da sind diejenigen richtig, die mit javax.ws.rs anfangen.

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.client.ClientConfig;

import data.Kunde;
import java.util.List;

public class JerseyClientKunde {
    static final String RESOURCE = "RestKunde";
    static final String URL = "app";
    static final String PATH = "kunde";

    public static void main(String[] args) {
        ClientConfig config = new ClientConfig();
        Client client = ClientBuilder.newClient(config);
        WebTarget service = client.target(
                UriBuilder.fromUri("http://localhost:8080/" + RESOURCE).build());
        Response response = null;
Ein Kunde wird angelegt und per POST versendet. Für das Datenformat wird XML angefordert. Als Rückgabetyp wird Response.class angefordert. Dadurch kann der Status des Servers ausgewertet werden.
        // Kunden anlegen per POST versenden
        Kunde kunde = new Kunde("007", "James Bond", "London");
        response = service.path(URL).path(PATH)
                .request(MediaType.APPLICATION_XML)
                .post(Entity.entity(kunde, MediaType.APPLICATION_XML),
                Response.class);
        System.out.println("Der POST-Befehl sollte 201 liefern, es ist: "
                +response.getStatus());
Mit dem nächsten POST simuliert der Client ein HTML-Formular und täuscht die imput-Felder nr, name und adresse vor.
        // Simuliere ein HTML-Formular und sende neuen Kunden
        Form form = new Form();
        form.param("nr", "4");
        form.param("name", "Batman");
        form.param("adresse", "Gotham");
        response = service.path(URL).path(PATH).request()
                .post(Entity.entity(form,
                MediaType.APPLICATION_FORM_URLENCODED),
                Response.class);
        System.out.println("Form sollte 201 liefern, es ist: "
                + response.getStatus());
Nun wird die Kundenliste geholt. Dazu dient der GET auf die Standard-URL. Der Client verwendet hier eine Liste der Java Collection Frame. Diese muss speziell als GenericType beim Parameter von get angegeben werden.
        // Zeige alle Kunden an
        List<Kunde> liste =  service.path(URL).path(PATH).request()
                .accept(MediaType.APPLICATION_XML)
                .get(new GenericType<List<Kunde>>() {});
        System.out.println("--- Liste aller Kunden ---");
        for (Kunde k : liste) {
            System.out.println(k.getNr()+": "+k.getName()+","+k.getAdresse());
        }
Nachdem wir die gesamte Liste gelesen haben, soll nun ein spezieller Kunde geholt werden, den wir über den Suchpfad identifizieren. Da bietet sich 007 an. Zunächst wollen wir nur wissen, ob es ihn wirklich gibt. Dazu fordern wir eine Response als Parameter von get.
        // Prüfe, ob es 007 noch gibt
        String suchstr = "/007";
        Response checkKunde = service.path(URL).path(PATH + suchstr).request()
                .accept(MediaType.APPLICATION_XML).get();
        System.out.println("Suche nach "+suchstr+ " Status: "+checkKunde.getStatus());
Nun soll James Bond angefordert werden. In dem Fall wird die Klasse Kunde als Parameter für get eingesetzt. Der Rückgabewert ist ein Kunde und enthält dann tatsächlich James Bond.
        // Hole den Kunden 007
        Kunde james = service.path(URL).path(PATH + suchstr).request()
                .accept(MediaType.APPLICATION_XML).get(Kunde.class);
        System.out.println(james.getName()+" wohnt in "+james.getAdresse());
James Bond lebt gefährlich. Viele wollen ihn ausschalten. Über den REST-Server senden wir ihm ein DELETE entgegen. Auch hier wird die Kennung per Pfad übergeben.
        // Den Kunde mit der id 007 ausschalten
        service.path(URL).path(PATH + suchstr).request().delete();
Zum Schluss lassen wir uns noch einmal alle Kunden anzeigen. In diesem Fall übergeben wir der get-Methode allerdings den Typ String. Das führt dazu, dass der XML-String nicht in den Typ Kunde umgewandelt wird, sondern als normaler String vorliegt, den man selbst parsen müsste.
        // Zeige noch einmal alle Kunden
        System.out.println(service.path(URL).path(PATH).request()
                .accept(MediaType.APPLICATION_XML).get(String.class));
    }
}
Das Ergebnis des Programms lautet:
Der POST-Befehl sollte 201 liefern, es ist: 201
Form sollte 201 liefern, es ist: 201
--- Liste aller Kunden ---
007: James Bond,London
4: Batman,Gotham
Suche nach /007 Status: 200
James Bond wohnt in London
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><kundes>
  <kunde><adresse>Gotham</adresse><name>Batman</name><nr>4</nr></kunde>
</kundes>
Den XML-String habe ich um der besseren Lesbarkeit willen umgebrochen.

Der HttpURL-Client für REST

Sie können den Server auch ohne Jersey als Client ansprechen. Der Grund kann darin liegen, dass es in der gewünschten Umgebung Jersey nicht gibt oder dass Sie vielleicht eine andere Programmiersprache verwenden.

Das ist grundsätzlich kein Problem, da REST über HTTP spricht und mit jedem Client korrespondiert, der HTTP versteht. Das ginge sogar zur Not mit einem reinen Socket-Client.

Ganz so weit wollen wir es nicht treiben. Wir verwenden die HttpURLConnection-Klasse. Allerdings bleibt Ihnen dann die Aufgabe, die XML-Strings zu wandeln. Für dieses Problem gibt es allerdings reichlich Lösungen und Bibliotheken.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;

public class RestClientHttp {
Die Methode get soll eine Liste aller Kunden auswerfen. Das tut sie auch. Das Ergebnis findet sich als String in der Variablen antwort. Da die Property Accept application/xml anfordert, besteht die Antwort aus der XML-Kodierung der Kunden.
    public static void get(String url) {
        try {
            HttpURLConnection verbind = (HttpURLConnection)
                    new URL(url).openConnection();
            verbind.setRequestProperty("Accept", "application/xml");
            verbind.setRequestMethod("GET");
            InputStream inStream = verbind.getInputStream();
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(inStream));
            String empfangeneZeile;
            StringBuffer antwort = new StringBuffer();

            while ((empfangeneZeile = in.readLine()) != null) {
                antwort.append(empfangeneZeile + "\n");
            }
            in.close();
            System.out.println(antwort.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Die Methode post sendet die Daten für einen Kunden in XML-Form. Im Gegensatz zur Methode get muss setDoOutPut auf true stehen, sonst wird nichts gesendet. Damit der gesandte String auch als XML interpretiert wird, muss Content-Type auf application/xml gesetzt sein, sonst gibt es als Rückgabestatus 400.

In diesem Fall wurde der XML-Code von Hand zusammengesetzt. Bei größeren Strukturen würde man eher eine Bibliothek zuhilfe nehmen.

    static boolean post(String url, String id, String name, String adresse) {
        try {
            HttpURLConnection verbind = (HttpURLConnection)
                    new URL(url).openConnection();
            verbind.setRequestProperty("Accept", "application/xml");
            verbind.setRequestProperty("Content-Type", "application/xml");
            verbind.setRequestMethod("POST");
            verbind.setDoOutput(true);
            verbind.setDoInput(true);
            String command = "<kunde>"
                    + "<nr>"+id+"</nr>"
                    + "<name>"+name+"</name>"
                    + "<adresse>"+adresse+"</adresse>"
                    + "</kunde>";
            OutputStream os = verbind.getOutputStream();
            OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
            osw.write(command);
            osw.flush();
            osw.close();
            System.out.println("ResponseCode: " + verbind.getResponseCode());
            return (verbind.getResponseCode() == HttpURLConnection.HTTP_CREATED);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
Die Methode delete sendet die gewünschte id über den Pfad.
    static boolean delete(String url, String id) {
        try {
            HttpURLConnection verbind = (HttpURLConnection)
                    new URL(url+"/"+id).openConnection();
            verbind.setRequestMethod("DELETE");
            verbind.setDoOutput(true);
            verbind.setDoInput(true);
            System.out.println("DELETE ResponseCode: "
                    + verbind.getResponseCode());
            return verbind.getResponseCode()
                    == HttpURLConnection.HTTP_ACCEPTED;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
Im Hauptprogramm wird zunächst die Liste gezeigt, ob sie wirklich leer ist. Anschließend werden zwei Kunden eingefügt, einer davon wieder gelöscht und dann der Bestand angezeigt.
    public static void main(String[] args) {
        String url = "http://localhost:8080/RestKunde/app/kunde";
        get(url);
        post(url, "32", "Albert Tross", "Weltweit");
        post(url, "35", "Nikita", "Moskau");
        delete(url, "35");
        get(url);
    }
}