REST

Willemers Informatik-Ecke

Das einzig seriöse Javabuch :-) Mehr...


Bei Amazon bestellen

Ferien an der Flensburger Förde
Falls ich Ihnen helfen konnte:!
Vielen Dank!!!
2017-03-05
REST steht für Representational State Transfer. Ein REST-Server stellt eine Ressource zur Verfügung, die durch ihre URI eindeutig global identifiziert wird.

Ein REST-Server unterstützt für die Ressourcen typischerweise die HTTP-Optionen GET, PUT, POST und/oder DELETE.

Die Server sind stateless, bauen also nicht auf den vorhergehenden Kommunikationen auf.

Der Client legt fest, in welchem Format er seine Daten transportiert sehen will. Dabei gibt es unter anderem folgende Formate:

Für Java stellt Jersey umfangreiche Hilfen zur Verfügung. Jersey müssen Sie allerdings dem Projekt hinzufügen. Mit JAX-RS 2.0 (JEE 7) steht auch eine Client-API zur Verfügung.

Eine Autorisierung durch Benutzer und Passwort ist möglich. Sie muss bei jeder Anfrage mitgesendet werden, da die Kommunikation ja stateless ist. Damit das Passwort nicht einfach von jedem Angreifer ausgelesen werden kann, ist HTTPS, also eine verschlüsselte Kommunikation, erforderlich.

Realisiserung in Eclipse

Laden Sie die Jersey-Jars aus dem Internet unter der URL jersey.java.net/download.html. In der ZIP-Datei jaxrs-ri-XXXX.zip befinden sich in den Unterverzeichnissen api, lib und ext mehrere Jar-Dateien. Kopieren Sie die Dateien in das Projektverzeichnis WebContent/WEB-INF/lib. Nun klicken Sie das Projekt mit der rechten Maustaste und wählen Properties. Unter Java Build Path klicken Sie Add JARs und fügen die Jersey-Jar-Dateien aus dem Projektpfad hinzu.

Legen Sie eine Java-Klasse unter dem Namen Hallo.java an.

package RestService;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hallo")
public class Hallo {

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String sagPlainText() {
    return "Ich bin der Jersey-Server";
  }

  @GET
  @Produces(MediaType.TEXT_XML)
  public String sagXML() {
    return "<?xml version=\"1.0\"?><hello>Jersey-Server</hello>";
  }

  @GET
  @Produces(MediaType.TEXT_HTML)
  public String sagHtml() {
    return "<html><title>Jersey-Server</title>"
        + "<body><h1>Hallo, ich bin ein Jersey-Server.</h1></body></html>";
  }

}
Als nächstes muss die Datei web.xml verändert werden. Sie liegt im Projekt unter WebContent/WEB-INF. Unter dem Eintrag display-name wird ein Einschub gemacht:
<servlet>
    <servlet-name>Jersey Hallo</servlet-name>
    <servlet-class>
        org.glassfish.jersey.servlet.ServletContainer
    </servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>de.willemer.jersey.JerseyHallo</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Jersey Hallo</servlet-name>
    <url-pattern>/rest/*</url-pattern>
</servlet-mapping>
Das Projekt wird gestartet, indem Sie es mit der rechten Maustaste anklicken und dort Run As | Run on Server auswählen. Sie erhalten im Eclipse einen Browser, der zunächst einen Fehler anzeigt, weil der den Server nicht gefunden hat. Korrigieren Sie den Pfad nach

localhost:8080/RestService/rest/hallo

Der erste Teil des Pfades ist der Name des Projekts, der zweite steht in der web.xml unter url-pattern und der dritte unter @Path in der Serverklasse.

Anstatt die Konfiguration in der Daei web.xml abzulegen, können Sie auch eine Klasse HalloApplication erzeugen und dort in der Annotation @ApplicationPath den Pfad setzen.

package RestService;

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

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

REST-Client

Im gleichen Projekt oder auch in einem separaten Projekt - dann allerdings mit Jersey-Jars - können Sie einen REST-Client zum Testen schreiben und ihn als Java-Application ausführen.
package RestService;

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.Response;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.client.ClientConfig;

public class RestClient {

    static final String PACKAGE = "RestService";
    static final String URL = "restservice";
    static final String PATH = "hallo";

    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());
        Response response = target.path(URL).path(PATH).request()
                .accept(MediaType.TEXT_PLAIN).get(Response.class);
        String strHtml = target.path(URL).path(PATH).request()
                .accept(MediaType.TEXT_HTML).get(String.class);

        System.out.println(response.getStatus());
        System.out.println(strHtml);
    }
}

REST und CRUD

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).

Bei einem solchen Server steht eine Ressource im Mittelpunkt. Und diese Ressource wird für die weitere Programmierung als Java-Klasse realisiert. Zwischen der Java-Klasse, die ein Objekt der Ressource definiert und der REST-Serverklasse wurde hier eine weitere Klasse, die als DAO (Data Access Objekt) bezeichnet wird. Sie können die Ressource später gern als Liste, als Datei oder noch besser als Datenbank verwalten.

Erstellen des Projekts

Als Beispiel verwenden wir ein Kundenprojekt.

Die REST-Schnittstellen-Klasse

Nach außen steht die REST-Schnittstellen-Klasse, die KundenServer heißt. 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. Auch ein Singleton wäre hier passend. Es sollten nicht mehrere Instanzen des DAO-Objekts entstehen.
package kunde;

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

    @Context
    UriInfo uriInfo;
Die UriInfo wird später von der POST-Methode benötigt. Die GET-Methode getKundenXml liefert eine komplette Liste aller Kunden und bedient Anfragen, die XML-Daten erfragen. Die Methode getEinenKundenXml verwendet einen zusätzlich Pfad. Hier wird nur ein Kunde angefragt. Bei REST-Servern ist es üblich, den Schlüssel der Ressource an den Pfad zu hängen. Die Methode liest den Schlüssel aus und übergibt ihn an die Methode getKunde der DAO-Klasse.
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public List<Kunde> getKundenXml() {
        System.out.println("get application/xml angefragt");
        return kundeDao.getKunden();
    }

    @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;
    }

    @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;
    }
Die letzte Methode reagiet auf HTML-Textanfragen, wie sie typischerweise von einem Browser kommen.

Selbst wenn Sie nicht die Absicht haben, den Server mit einem Browser zu bearbeiten, erlaubt diese Methode durch Eingabe der Adresse einen schnellen Einblick in den Stand Ihres Servers.

Hier sind auch zwei POST-Methoden realisiert. Die eine verarbeitet XML-Daten. Die andere Methode verarbeitet Eingaben, wie sie ein Browser per URL aus einem Formular versendet. Erstens können Sie so wiederum einen Browser für kleinere Hilfen heranziehen. Außerdem ist es viel einfacher, Daten auf diesem Wege zu übermitteln. Allerdings müssen Sie darauf achten, dass Sie vor allem Leerzeichen durch die Zeichenfolge "%20" ersetzen.

    @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();
    }

    @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();
    }
Das Löschen erfolgt über den Schlüssel, der ebenso wie bei der Abfrage eines einzelnen Kunden über die URL abgegeben wird.
    @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 tut nichts selbst. Was zunächst ungeheuer faul wirkt, hat einen praktischen Sinn. Sie können es dieser Klasse überlassen, ob sie die Daten in einem Container, in einer Datei oder in einer Datenbank speichert. Das Kunden-DAO speichert die Daten einfach nur in einer ArrayList. Es ist aber ein Leichtes, eine Datenbank zu verwenden. Wie Sie mit Datenbanken umgehen, wird ja an anderer Stelle im Buch beschrieben.
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 KundeDao() {
    }

    public List<Kunde> getKunden() {
        return kunden;
    }

    public String neuKunde(Kunde kunde) {
        if (kunden==null) {
            kunden = new ArrayList<Kunde>();
        }
        kunden.add(kunde);
        return ""+kunde.getNr();
    }

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

    public Kunde getKunde(int id) {
        System.out.println("getKunde: " + id);
        if (kunden.size() < id && id >= 0) {
            return kunden.get(id);
        }
        return null;
    }

    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;
    }
}

Das Objekt 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. Natürlich kann man da mehr machen, aber zur Demonstration reicht das.
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; }
}
Die Getter und Setter habe ich am Ende etwas zusammengepresst. Das spart Platz im Buch. Die Methode toString ist für Debug-Zwecke sehr hilfreich. Das eigentlich Wichtige ist die Annotation @XmlRootElement. Sie ermöglicht es, dass Sie im KundenServer keinen Gedanken daran verschwenden müssen, wie Sie aus einem Objekt der Klasse Kunde eine XML-Struktur erzeugen.

Der Client für REST

Wenn Sie Jersey auch für Ihren Client nutzen, wird das Programm zwar fetter, aber der Quelltext schlanker. Hinzu kommt, dass Ihnen Jersey auch gleich die Umwandlung Ihrer Klassen in XML erledigt.

Das Programm führt folgende Schritte aus:

package kunde;

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.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;

import org.glassfish.jersey.client.ClientConfig;

public class TestKunde {
    static final String RESOURCE = "RestCrud";
    static final String URL = "rest";
    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;
        // Kunden anlegen
        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("Soll: 201 Ist: "+response.getStatus());
        // Erzeuge einen Kunden nach der Methode FORM:
        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 response " + response.getStatus());
        // Zeige alle Kunden an
        String xmlliste =  service.path(URL).path(PATH).request()
                .accept(MediaType.APPLICATION_XML).get(String.class);
        // Get XML for application
        System.out.println("Liste (XML): " + xmlliste);
        // Prüfe, ob es 007 noch gibt und zeige ihn an
        String suchstr = "/007";
        Response checkKunde = service.path(URL).path(PATH + suchstr).request()
                .accept(MediaType.APPLICATION_XML).get();
        System.out.println("check "+suchstr+ ": "+checkKunde.getStatus());
        System.out.println("Kunde "+suchstr+": " + service.path(URL)
                .path(PATH + suchstr).request()
                .accept(MediaType.APPLICATION_XML).get(String.class));
        // Den Kunde mit der id 007 ausschalten
        service.path(URL).path(PATH + suchstr).request().delete();
        // Zeige noch einmal alle Kunden
        System.out.println(service.path(URL).path(PATH).request()
                .accept(MediaType.APPLICATION_XML).get(String.class));
    }
}
Wenn es Ihnen nicht so leicht fällt, Jersey in den Client einzubinden, weil das Programm etwas schlanker sein soll, dann hilft Ihnen die folgende Lösung, die allein mit HttpUrl auskommt, was ja zur Standardausstattung gehört.
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 RestClient {

    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();
        }
    }

    static boolean post(String url, String id, String name, String adresse) {
        try {
            HttpURLConnection verbind = (HttpURLConnection)
                    new URL(url).openConnection();
            verbind.setRequestProperty("Accept",
                    "application/x-www-form-urlencoded");
            verbind.setRequestMethod("POST");
            verbind.setDoOutput(true);
            verbind.setDoInput(true);
            String command = "nr="+id+"&name="+name+"&adresse="+adresse;
            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;
    }

    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("ResponseCode: " + verbind.getResponseCode());
            return verbind.getResponseCode()
                    == HttpURLConnection.HTTP_ACCEPTED;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) {
        String url = "http://localhost:8080/RestCrud/rest/kunde";
        get(url);
        post(url, "32", "Albert%20Tross", "Weltweit");
        post(url, "34", "Nikita", "Moskau");
        get(url);
        delete(url, "34");
        get(url);

    }
}


Homepage - Java (C) Copyright 2016, 2017 Arnold Willemer