Transit macht die Funktionen einer Webanwendung für Aufrufe über HTTP zugänglich. Java-Code auf einem Server lässt sich mit Transit ohne statische Annotationen in Webclients nutzen. Dieses Dokument beschreibt die Verwendung von Transit.

1. Voraussetzungen

Transit setzt auf die Klassenbibliotheken XStream und Jettison zur dynamischen Serialisierung und Deserialisierung von Java-Objekten auf. Die Klassenbibliotheken müssen im Classpath enthalten sein wie nachfolgend beschrieben.

2. Installation

Damit eine Webanwendung die Funktionen von Transit nutzen kann müssen die folgenden Schritte ausgeführt werden

  • Herunterladen XStream und Jettison

  • Herunterladen von Transit

  • Kopieren der Dateien transit.jar, xstream.jar und jettison.jar ins Verzeichnis WEB-INF/lib der Webanwendung

Die Klassenbibliotheken werden so mit der Webanwendung verteilt und stehen zur Laufzeit zur Verfügung.

3. Konfiguration

Mit einem Eintrag im Deploment Descriptor, der Datei WEB-INF/web.xml einer Webanwendung werden beliebige Java-Methoden für Webclients nutzbar. Der Eintrag lautet wie folgt

Einträge zum Einschalten der Servlets TransitServlet und TransitServletRS im Deployment Descriptor einer Webanwendung
<servlet>
  <servlet-name>Transit</servlet-name>
  <servlet-class>
    de.uhilger.transit.web.TransitServlet
  </servlet-class>
</servlet>

<servlet>
  <servlet-name>TransitRS</servlet-name>
  <servlet-class>
    de.uhilger.transit.web.TransitServletRS
  </servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>Transit</servlet-name>
  <url-pattern>/api</url-pattern>
</servlet-mapping>

<servlet-mapping>
  <servlet-name>TransitRS</servlet-name>
  <url-pattern>/api-rs/*</url-pattern>
</servlet-mapping>

Damit werden Methodenaufrufe über die Endpunkte /api und /api-rs der Webanwendung eingerichtet. Läuft die Webanwendung beispielsweise auf dem Server example.com unter dem Kontext anwendung, gelingen mit obigem Eintrag Methodenaufrufe über die folgenden Uniform Resource Locators (URL)

http://example.com/anwendung/api

http://example.com/anwendung/api-rs/

Die Verwendung von Transit über einen solchen URL ist im Kapitel Verwendung näher erklärt.

4. Sicherheit

Zur sicheren Verwendung von Transit müssen Aufrufe beschränkt werden, damit nicht alle Klassen und Methoden auf einem Server zugänglich sind. Dem Transit-Servlet wird dazu ein Parameter im Deployment Descriptor mitgegeben, der Packages oder einzelne Klassen benennt. Nur die Methoden dieser Packages oder Klassen sind dann von außen verwendbar.

Der Parameter klassen im Deployment-Descriptor regelt den Zugriff von außen
<init-param>
  <param-name>klassen</param-name>
  <param-value>
    com.example.anwendung.api;
    com.example.anwendung.web
  </param-value>
</init-param>

Mit der Trennung durch Semikolon können mehrere Packages oder Klassen angegeben werden. Das obige Beispiel lässt nur Aufrufe von Methoden zu, die sich in Klassen der Packages com.example.anwendung.api oder com.example.anwendung.web oder deren Subpackages befinden.

4.1. Berechtigungen via Filter

In vielen Fällen genügt es, Aufrufe von Transit auf bestimmte Java-Klassen oder -Packages zu beschränken, wie es im vorigen Abschnitt beschrieben ist. Sollen darüber hinaus anwendungsspezifische Berechtigungen geprüft werden, kann die Schnittstelle RechtePruefer verwendet werden. Eine Klasse muss für Berechtigungsprüfungen lediglich diese Schnittstelle implementieren und über einen Filter einbinden.

Das Dokument Transit-Berechtigungen beschreibt die Vorgehensweise im Detail.

5. Verwendung

In diesem Abschnitt ist die Verwendung von Transit beschrieben. Zunächst wird betrachtet, wie Webclients Funktionen auf einem Server nutzen. Anschließend sind die verschiedenen Möglichkeiten erklärt, wie mit Transit auf dem Server solche Funktionen zugänglich sind.

5.1. Webclient

Auf der Seite eines Webclients werden Funktionen, die von einem Server bereitgestellt werden, über asynchrone Funktionsaufrufe genutzt. So ist es möglich, HTTP-Anfragen durchzuführen, während eine HTML-Seite angezeigt wird. Die Seite kann verändert werden ohne sie komplett neu zu laden. Das folgende Beispiel nutzt diese Möglichkeit zum Abruf der aktuellen Zeit von einem Server.

die Javascript-App serverzeit.js zum Abruf der Zeit von einem Server
function Serverzeit() {
  var self = this;

  this.abrufen = function(elem_id) {
    self.http_get("http://example.com/test/api/uhrzeit", self.antwort_zeigen, elem_id);
  };

  this.antwort_zeigen = function (antwort, id) {
    var elem = document.getElementById(id);
    elem.textContent = antwort;
  };

  this.http_get = function (url, callback, id) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (this.readyState === 4 && this.status === 200) {
        callback(this.responseText, id);
      }
    };
    xhr.open("GET", url, true);
    xhr.send();
  };
}

Die obige App wird wie folgt in einer HTML-Seite verwendet.

eine HTML-Seite zur Anzeige der Zeit von einem Server
<html>
  <head>
    <title>Serverzeit-Demo</title>
  </head>
  <body>
    <p>Zeit: <span id="zeitanzeige">wird abgerufen...</span></p>
    <script src="serverzeit.js"></script>
    <script>
      var app;
      document.addEventListener('DOMContentLoaded', function () {
        app = new Serverzeit();
        app.abrufen('zeitanzeige');
      });
    </script>
  </body>
</html>

In manchen Fällen ist es nötig, die Methode HTTP-POST anstelle von HTTP-GET zu verwenden, beispielsweise, wenn Daten zum Server geschickt werden, deren Umfang zu groß ist, um als Parameter im Query-Teil des URL übergeben zu werden. In diesem Fall würde auf der Seite des Webclients ein Code wie folgt verwendet werden.

Variante eines Funktionsaufrufes mit HTTP-POST
  this.http_post = function (url, data, callback, id) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (this.readyState === 4 && this.status === 200) {
        callback(this.responseText, id);
      }
    };
    xhr.open("POST", url, true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.send(data);
  };

Im zusätzlichen Parameter data werden die Parameter übergeben, die die Server-Methode erwartet. Die Reihenfolge muss ihrer Deklaration entsprechen und zum Beispiel für eine Methode, die drei Parameter erwartet, wie folgt angelegt sein:

var data = "p=Parameter 1&p=Parameter 2&p=Parameter 3";

Wichtig ist zudem, dass im Header der Content Type wie im Beispiel gesetzt wird, sonst werden die Inhalte von data nicht richtig verarbeitet.

In den folgenden Kapiteln ist beschrieben, wie Java-Methoden auf dem Server mit Hilfe von Transit für die hier gezeigten Aufrufe von Webclients zugänglich gemacht werden können.

5.2. Anwendungsfälle

Transit kann auf zwei Arten verwendet werden, mit dem TransitServlet und dem TransitServletRS. Jedes der Servlets wird auf dem Webserver über einen Eintrag im Deployment Descriptor einer Webanwendung eingeschaltet wie es im Kapitel Konfiguration beschrieben ist.

Aufruf des TransitServlet

http://example.com/anwendung/api?c=package.Klasse&m=methodenname&f=JSON&p=param1&p=param2

Das TransitServlet verarbeitet Aufrufe, bei denen Parameter im Query-Teil eines URL angegeben sind.

Aufruf des TransitServletRS

http://example.com/anwendung/api-rs/package.Klasse/methodenname/format/param1/param2

Beim TransitServletRS werden die Parameter als Teil des Pfads ausgedrückt.

Varianten des Aufrufes

Transit unterscheidet hierbei die folgenden Arten von Aufrufen:

Für jeden der obigen Fälle ist nachfolgend ein Beispiel erläutert, das mit Aufrufen der beiden Klassen TestKlasse und TestDatenKlasse veranschaulicht, wie die Verwendung von Transit funktioniert. Allen Fällen gemeinsam ist, dass sowohl Parameter als auch Rückgabewert in Textform übertragen werden. Unabhängig von ihrem Datentyp wie beispielsweise Zahl, Text, Datum, Objekt werden sie also stets als Text ausgetauscht.

5.3. Einfacher Rückgabewert

Die einfachste Form der Nutzung von Transit ist der Aufruf einer Java-Methode ohne Parameter. Ein Beispiel für diese Art der Nutzung ist die Methode TestKlasse.halloWelt. Sie erwartet keine Parameter und gibt einfach die Zeichenkette Hallo Welt zurück.

URL für Aufruf mit TransitServlet

http://example.com/anwendung/api?c=de.uhilger.transit.TestKlasse&m=halloWelt

URL für Aufruf mit TransitServletRS

http://example.com/anwendung/api-rs/de.uhilger.transit.TestKlasse/halloWelt/JSON/

Rückgabewert

Hallo Welt

5.4. Einfacher Parameter

Ein anderes Beispiel ist die Methode TestKlasse.gruss. Sie erwartet einen Parameter, mit dem der Name eines Benutzers angegeben werden kann. Als Beleg, dass der Parameter verarbeitet wurde sendet der Server als Rückgabewert die Zeichenkette "Hallo [name]!". Bei der Angabe des Parameters "Fred" lautet die Antwort des Servers "Hallo Fred!".

URL für Aufruf mit TransitServlet

http://example.com/anwendung/api?c=de.uhilger.transit.TestKlasse&m=gruss&p=Fred

URL für Aufruf mit TransitServletRS

http://example.com/anwendung/api-rs/de.uhilger.transit.TestKlasse/gruss/JSON/Fred

Rückgabewert

Hallo Fred!

5.5. Rückgabe eines Objekts

Sieht eine Java-Methode als Rückgabewert ein Java-Objekt vor, wird dieses von Transit als Text im JSON- oder XML-Format übermittelt. Ein Beispiel für diesen Anwendungsfall liefert die Methode TestKlasse.getTestDaten. Ihr werden drei einfache Werte als Parameter übermittelt, die der Server in ein Objekt der Klasse TestDatenKlasse verpackt welches an den Browser zurückgesendet wird.

URL für Aufruf mit TransitServlet

http://example.com/anwendung/api?c=de.uhilger.transit.TestKlasse&m=getTestDaten&p=16&p=testText&p=testName

URL für Aufruf mit TransitServletRS

http://example.com/anwendung/api-rs/de.uhilger.transit.TestKlasse/getTestDaten/JSON/16/testText/testName

Rückgabewert

{"TestDatenKlasse":{"id":16,"name":"testName","text":"testText"}}

Der Rückgabewert kann auf der Seite des Browsers mit der Funktion JSON.parse in ein JavaScript-Objekt verwandelt werden, dessen Typbezeichnung, Struktur und Inhalt mit denen des Java-Objekts identisch sind. Frameworks wie z.B. jQuery enthalten zudem Funktionen für Ajax-Aufrufe, die für Antworten im Format JSON direkt ein JSON-Objekt liefern (vgl. getJSON).

5.6. Objekt als Parameter

Erwartet die Methode einer Java-Klasse ein Objekt oder mehrere Objekte als Parameter, werden diese als Texte im JSON-Format übermittelt. Klasse und Struktur eines solchen Objekts sind gewöhnlich in der API-Dokumentation ersichtlich, wie es z.B. für die Klasse TestDatenKlasse von Transit der Fall ist. Sie wird von der Methode TestKlasse.testObjektVerarbeiten als Parameter erwartet. Mit den Informationen aus der Dokumentation kann das Objekt in JavaScript identisch angelegt werden, wie das folgende Beispiel zeigt.

die Klasse TestDatenKlasse in Javascript
function TestDatenKlasse(i, n, t) {
  this.id = i;
  this.name = n;
  this.text = t;
}

Die obige Deklaration der Klasse TestDatenKlasse kann verwendet werden, um ein Objekt dieser Klasse in Javascript zu erzeugen:

var testObjekt = new TestDatenKlasse(16, 'Fred', 'einText');

Das testObjekt wird dann als Parameter in einem Methodenaufruf über Transit verwendet. Es wird zu diesem Zweck zuvor in einen JSON-Ausdruck umgewandelt.

Die Hilfsfunktion serialisieren verwandelt ein Javascript-Objekt nach JSON
function serialisieren(obj) {
  return '{"' + obj.constructor.name + '":' + JSON.stringify(obj) + '}';
}

Der anschließende Aufruf der Methode TestKlasse.testObjektVerarbeiten erfolgt ähnlich wie in der Beschreibung im Kapitel Webclient wie folgt

Aufruf einer Methode, die ein Objekt als Parameter erwartet
var testObjekt = serialisieren(new TestDatenKlasse(16, 'Fred', 'einText'));
var url = 'http://example.com/anwendung/api?c=de.uhilger.transit.TestKlasse&m=testObjektVerarbeiten';
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (this.readyState === 4 && this.status === 200) {
    console.log(this.responseText);
  }
};
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send('p=' + testObjekt);

Im obigen Beispiel wird ein Javascript-Objekt TestDatenKlasse erzeugt und in die Variable testObjekt serialisiert. Die so serialisierte TestDatenKlasse wird per HTTP-POST als Parameter der Server-Methode testObjektVerarbeiten an den Server geschickt.

5.6.1. Einschränkungen

Beim Aufruf von Methoden, die Objekte als Parameter erwarten werden keine eindeutigen Klassennamen bestehend aus Package und Klasse verwendet. Es können daher nur Methoden gerufen werden, die als Parameter Objekte erwarten, deren Klassenname auch ohne Angabe der Package eindeutig ist.