Neon ist ein ultrakompakter, modular aufgebauter HTTP-Server auf der Grundlage des Java-Moduls jdk.httpserver zum Einbetten in Apps und Microservices. In diesem Dokument sind Module zur Erweiterung von Neon beschrieben.

Module

Die folgenden Module sind in diesem Dokument beschrieben.

Für jedes der obigen Module bildet ein Java-Archiv eine Klassenbibliothek (.jar-Datei), die zur Nutzung einfach dem Classpath einer App oder eines Microservice hinzugefügt wird. Nachfolgend sind die Funktionen der Module ausführlicher beschrieben.

http-base

http-base stellt die zentrale Funktion bereit, die auf der Grundlage des Java-Modula jdk.httpserver einen Server für statische Dateien bildet. Das folgende Beispiel zeigt, wie das Modul eingebunden wird.

statische Dateien ausliefern mit Hilfe der Klasse FileHandler aus http-base
// einen Server auf Port 9191 erzeugen
HttpServer server = HttpServer.create(new InetSocketAddress(9191, 0);

// einen neuen HttpContext auf dem Endpunkt /meine-app erstellen
// und einen FileHandler daran binden
HttpContext context = server.createContext("/meine-app", new FileHandler());

// den Pfad zu den Daten setzen
context.getAttributes.put(FileHandler.ATTR_FILE_BASE, "/home/fred/www");

// den Server starten
server.setExecutor(Executors.newCachedThreadPool());
server.start();

Mit dem obigen Code wird die Klasse FileHandler des Moduls http-base für den Kontext /meine-app aktiviert. Die App liefert damit Dateien im Ordner /home/fred/www über den Port 9191 aus.

Wird der obige Code als Teil einer App oder eines Microservice ausgeführt, ist der Inhalt von /home/fred/www der betreffenden Maschine anschließend über den Uniform Resource Locator (URL) http://localhost:9191/meine-app/ via HTTP zugänglich.

Die Klasse FileHandler liefert abhängig von den Angaben im HTTP-Header ganze Dateien oder umfangreiche Inhalte als Streams aus.

Welcome Files

Mit dem Attribut FileHander.ATTR_WELCOME_FILES können dem FileHandler Dateinamen angegeben werden, die probiert werden sollen, wenn kein Dateiname angegeben ist.

Dateinamen angeben, die von der Klasse FileHandler standardmäßig ausgeliefert werden sollen
// welcome files angeben
context.getAttributes.put(FileHandler.ATTR_WELCOME_FILES, "index.html,index.htm");

Wird zusätzlich zum vorherigen Code-Beispiel die obige Einstellung gemacht, gibt der Aufruf eines URL wie http://localhost:9191/meine-app/infos/ die Datei /home/fred/www/infos/index.html oder /home/fred/www/infos/index.htm aus, sofern eine der Dateien an diesem Ort abgelegt ist.

Helfer

Neben der Hauptfunktion zum Ausliefern statischer Inhalte enthält das Modul http-base noch Helfer zur Unterstützung häufiger Aufgaben.

PatternDelegator

Mit der Klasse PatternDelegator können HttpHandler an Muster geknüpft werden, die aus regulären Ausdrücken gebildet werden. So lassen sich beispielsweise alle Ressourcen mit einer bestimmten Dateiendung oder gewissen Namensbestandteilen an einen Handler binden. Ist ein Handler zusätzlich von NeonHandler abgeleitet, können dem Handler eigene Attribute mitgegeben werden. Auf diese Weise können verschiedene Instanzen derselben Handler-Klasse mit unterschiedlichen Parametern genutzt werden, ohne, dass die Attribute sich mit denen eines HttpContext überschneiden.

HandlerDescriptor

Die Klasse HandlerDescriptor erlaubt im Zusammenspiel mit einem PatternDelegator die Deklaration einer Handler-Klasse und ihrer Attribute.

Mit dem Gespann aus HandlerDescriptor und PatternDelegator lassen sich leichtgewichtigere Server implementieren, da die Klasse PatternDescriptor einen Handler jeweils erst beim HTTP-Aufruf instanziiert. Zudem können zur Laufzeit dynamisch unterschiedliche Klassen eingesetzt werden ohne den Code neu kompilieren zu müssen. Handler-Klassen müssen zu diesem Zweck einen Konstruktor ohne Parameter besitzen.

http-realm

Sollen Inhalte nur bestimmten Nutzern zugänglich sein, muss eine Authentifizierung feststellen, welcher Nutzer eine Anwendung bedient. Die Angaben des Nutzers für die dafür notwendige Authentisierung prüft die Anwendung gegen ein Nutzerverzeichnis und eine einfache Variante ist im Modul http-realm implementiert.

Die Schnittstelle Realm muss von Anwendungen implementiert werden, wenn eine Authentifizierung gegen ein Nutzerverzeichnis im Sinne des Moduls http-realm verwendet werden soll. Die Klasse SimpleRealm implementiert beispielhaft einen Realm auf der Grundlage einer Datei.

Beispiel für die Nutzung einer Datei mit Nutzerdaten über die Klasse SimpleRealm
SimpleRealm realm = new SimpleRealm();
realm.readFromFile(new File("conf", my.realm));
realm.setName("/meine-app");

Für die Datei my.realm erwartet die Klasse SimpleRealm Einträge in folgender Struktur.

Struktur der Einträge im Benutzerverzeichnis für die Klasse SimpleRealm
# Benutzer meiner App
# [userId]=[password],[roleId],[roleId],etc.
test=test,testRolle
ulrich=geheim,testRolle,andereRolle
fred=undisclosed,nutzerRolle,adminRolle

Der Ausdruck vor dem Gleichheitszeichen ist die Nutzer-Kennung, die zur Anmeldung dient. Der erste Eintrag nach dem Gleichheitszeichen ist das Kennwort, gefolgt von einer Liste mit Komma getrennter Rollen-IDs. http-realm liefert damit zugleich die Ausgangsbasis für andere Implementierungen wie beispielsweise gegen eine Datenbank oder einen Web-Service.

Erweiterungen oder Abwandlungen der Klasse SimpleRealm sollten auch eine Form der Verschlüsselung für das Kennwort einbauen, damit es nicht im Klartext für andere lesbar in der Datei vorliegt.

http-oauth

Mit http-oauth wird Neon um eine Möglichkeit der Authentifizierung nach RFC 6750 Bearer Token Authentication erweitert. Dieses Modul basiert auf JJWT sowie Gson und erfordert neben der Datei http-oauth.jar die Dateien jjwt-api.jar, jjwt-impl.jar, jjwt-gson.jar sowie gson.jar und ferner die Module http-base und http-realm.

BearerAuthenticator

Zum Einschalten der Authentifizierung für einen bestimmten HTTP-Service-Endpunkt kann die Klasse BearerAuthenticator mit folgender Vorgehensweise dienen.

Beispiel zum Einschalten der Bearer Token Authentifizierung
// einen Authenticator gegen unser Nutzerverzeichnis erstellen
BearerAuthenticator auth = new BearerAuthenticator();
auth.setRealm(realm);
auth.setExpireSeconds(7200); // 2 Stunden
auth.setPrincipalAuthRealm("/meine-app");
auth.setWWWAuthRealm("/meine-app");
auth.setRefreshExpireSeconds(86400); // 1 Tag
auth.setRefreshSeconds(3600); // 1 Stunde bis nach dem Refresh gefragt wird

context = server.createContext("/meine-app/admin", new Dateiablage("/home/fred/daten", "adminRolle"));
// der Authenticator stellt sicher, dass Nutzer sich authentifizieren
context.setAuthenticator(auth);

Im obigen Code ist die Klasse Dateiablage ein Beispiel, das symbolisiert, dass hier eine Funktion verwendet wird, die an eine Rolle geknüpft ist. Diese Rolle muss ein Benutzer haben, wenn die Funktion genutzt werden soll. Die Überprüfung der Rolle ist nicht Teil des Beispiels, erfordert aber, dass Nutzer sich authentifizieren.

BearerLoginHandler

Ist eine Ressource mit der Klasse BearerAuthenticator verbunden, wird gemäß Spezifikation eine Antwort mit Status 401 Unauthorized gesendet, wenn eine HTTP-Anfrage ohne Authentifizerung für diese Ressource eingeht.

Antwort auf eine nicht authentifizierte Anfrage einer Ressouce, die eine Authentifizerung erfordert
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example"

Der Client muss dann einen HTTP-Service-Endpunkt aufrufen, der eine Authentifizierung ermöglicht. Ein solcher Endpunkt zum Login kann mit der Klasse BearerLoginHandler wie in folgendem Beispiel implementiert werden.

Beispiel für einen Service-Endpunkt zur Authentifizierung
// einen HttpContext für den login erstellen
HttpContext context = server.createContext("/meine-app/login", new BearerLoginHandler());

// den Authenticator fuer das Modul http-oauth bekannt machen
context.getAttributes().put(BearerLoginHandler.ATTR_AUTHENTICATOR, auth);

Über den Kontext /meine-app/login kann eine Benutzeranmeldung erfolgen. Der BearerLoginHandler erwartet den Aufruf per HTTP POST mit {"name": "fred", "password": "secret"} im Body der Anfrage. Zudem wird dem Kontext über das Attribut BearerLoginHandler.ATTR_AUTHENTICATOR mitgegeben, welcher Authenticator beim Login verwendet werden soll.

Der Login Handler überprüft Nutzerangaben gegen den Realm des Authenticators und gibt bei erfolgreicher Authentifizierung den Authentifizerungs-Token und den Refresh-Token zurück. Diese benötigt der Client zur Angabe der Authentifizierung im Header von Anfragen an Ressourcen, die eine Authentifizierung erfordern.

Die Authentifizierungsangaben werden mit Hilfe der Klasse LoginResponse im spezifikationsgemäßen Format als Ausdruck in JavaScript Object Notation (JSON) zurückgegeben.

die Antwort auf eine erfolgreiche Authentifizierung
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"mF_9.B5f-4.1JqM",
  "token_type":"Bearer",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}

BearerRefreshHandler

Ist ein Authentifizierungs-Token abgelaufen, erhält der Client bei Anfrage einer Ressource, die eine Authentifizierung erfordert, eine Ablehnung.

Antwort auf die Anfrage einer Ressource, die eine Authentifzierung erfordert, wenn die Authentifizierung nicht mehr gültig ist
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                    error="invalid_token",
                    error_description="The access token expired"

Bei der Bearer Authentication muss der Client den Refresh Token aus der letzten Authentifizierung verwenden, um die Authentifizierung zu erneuern. Mit der Klasse BearerRefreshHandler liefert http-oauth das Mittel zur Bildung eines Service-Endpunkts, der einen Token erneuert.

Beispiel für einen Service-Endpunkt zum Erneuern einer Authentifizierung
// einen HttpContext fuer den token refresh erzeugen
HttpContext context = server.createContext("/meine-app/refresh", new BearerRefreshHandler());

// den Authenticator fuer das Modul http-oauth bekannt machen
context.getAttributes().put(BearerLoginHandler.ATTR_AUTHENTICATOR, auth);

Der Instanz von BearerRefreshHandler wird wie beim Login Handler wieder der Authenticator mitgegeben, mit dessen Hilfe Refresh Tokens geprüft werden. Mit Aufruf des Endpunkts /meine-app/refresh erwartet der BearerRefreshHandler eine HTTP-Anfrage im spezifikationsgemäßen Format wie folgt:

Anfrage zum Erneuern einer Authentifizierung
POST /meine-app/refresh HTTP/1.1
Host: example.com

grant_type=refresh_token
&refresh_token=xxxxxxxxxxx
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

Ist der Refresh Token gültig, antwortet der BearerRefreshHandler so wie beim Login mit einem neuen Satz Tokens.

Zusammenfassung

Mit dem Modul http-oauth kann eine App oder ein Microservice den Bearer Authentication Mechanismus für Ressourcen verwenden, wenn diese nur an berechtigte Nutzer ausgegeben werden sollen.

  1. Zum Einschalten der Authentifizierung wird ein Service-Endpunkt mit einem Objekt der Klasse BearerAuthenticator verbunden.

  2. Mit einem Objekt der Klasse BearerLoginHandler wird ein Service-Endpunkt zur Authentifizerung geschaffen.

  3. Ein Objekt der Klasse BearerRefreshHandler erlaubt die Herstellung eines Service-Endpunktes zum Erneuern einer abgelaufenen Authentifizierung.

Damit ist für eine App oder einen Microservice eine Authentifizierung herstellbar, die stärker von etablierten Mechanismen wie Basic, Digest oder Form Based Authentication entkoppelt ist und zudem flexibler in evtl. bestehende andere Authentifizierungsdienste eingebunden werden kann.

Das Kapitel Authentifizierung zeigt ein Beispiel für den Einsatz eines Authenticators mit einem FileManager. Im Dokument Authentifizierung für Webclients ist zudem beschrieben, wie die Authentifizierung auf der Seite des Clients implementiert werden kann.

http-up

Das Modul http-up stellt die Klasse MultipartStream aus dem Projekt Apache Commons mit geringfügigen Anpassungen bereit, mit denen die Klasse ohne weitere Abhängigkeiten eingesetzt werden kann. Das folgende Code-Beispiel zeigt, wie die Klasse auf der Server-Seite z.B. in einem HttpHandler verwendet werden kann, um zum Server heraufgeladene Dateien entgegenzunehmen.

Beispiel für den Einsatz der Klasse MultipartStream
public static final String ATTR_FILENAME = "filename=";
public static final String TEMP_FILE_NAME = "temp.up";

private String fileBase = "/some/path/to/storage";

public void handle(HttpExchange exchange) throws IOException {
    Headers headers = exchange.getRequestHeaders();
    String ct = headers.getFirst(HttpHelper.CONTENT_TYPE);
    String[] parts = ct.split("=");
    String boundary = parts[1];
    InputStream is = exchange.getRequestBody();
    MultipartStream multipartStream = new MultipartStream(is, boundary.getBytes(), 4096, null);
    File file = new File(fileBase, TEMP_FILE_NAME);
    try {
      String value = "";
      boolean nextPart = multipartStream.skipPreamble();
      while (nextPart) {
        String header = multipartStream.readHeaders();
        if(header.contains(ATTR_FILENAME)) {
          // process file content
          // perhaps insert some file name processing here too
          // so that more than one uploaded files do not overwrite
          // each other
          OutputStream os = new FileOutputStream(file);
          multipartStream.readBodyData(os);
        } else {
          // read value
          ByteArrayOutputStream os = new ByteArrayOutputStream();
          multipartStream.readBodyData(os);
          value = os.toString().substring(fileContext.length());
        }
        nextPart = multipartStream.readBoundary();
      }
    } catch (MultipartStream.MalformedStreamException e) {
      // the stream failed to follow required syntax
    } catch (IOException e) {
      // a read or write error occurred
    }

    // processing of files 'temp.up', 'temp-2.up', 'temp-3.up' etc.
    // is required here, e.g. rename, move ..
}

Die Methode handle im obigen Beispiel liest mit Hilfe der Klasse MultipartStream eine HTTP-Anfrage mit Content-Type multipart/form-data, wie sie bei HTTP-Uploads an den Server gesendet wird.

http-adoc

Mit http-adoc wird Neon um die Möglichkeit erweitert, Dokumente, die im Asciidoctor-Format geschrieben sind, in HTML- oder PDF-Dokumente zu verwandeln. Neben der Datei http-adoc.jar sind die Java-Archive aus dem Paket AsciidoctorJ erforderlich. Die Umwandlung geschieht 'on-demand', es muss kein besonderer Build-Prozess hergestellt werden. Die Aktivierung des AdocFilter für eine Ressource wie nachfolgend beschrieben genügt.

Beispiel für die Aktivierung von AdocHandler und AdocFilter
// einen Descriptor fuer den AdocHandler erstellen
HandlerDescriptor hd = new HandlerDescriptor();
hd.setHandlerClassName("de.uhilger.httpserver.adoc.AdocHandler");

// einen regulären Ausdruck fuer das Muster *.adoc erstellen
// und den AdocHandler daran binden
PatternDelegator handler = new PatternDelegator();
handler.addHandler(".+\\.adoc", hd);

// einen neuen HttpContext erzeugen
HttpContext context = server.createContext("/meine-app/dok", handler);

// den AdocFilter fuer den neuen HttpContext aktivieren
context.getFilters().add(new AdocFilter());

// den Pfad zu den Daten setzen
context.getAttributes.put(FileHandler.ATTR_FILE_BASE, "/home/fred/www-daten");

Im obigen Code-Beispiel werden alle Inhalte des Ordners /home/fred/www-daten über den Service-Endpunkt /meine-app/dok via HTTP ausgegeben. Zudem wird der AdocFilter für alle über /meine-app/dok zugängliche Inhalte eingeschaltet.

Beispiel

Beim Aufruf der Ressource http://localhost:9191/meine-app/dok/hilfe/anleitung.adoc wird mit obiger Konfiguration die Datei /home/fred/www-daten/hilfe/anleitung.adoc nötigenfalls nach HTML transformiert, als anleitung.html am selben Ort gespeichert und diese HTML-Datei ausgeliefert.

Bleibt die Quelldatei anleitung.adoc unverändert, wird bei weiteren Aufrufen der Ressource die HTML-Datei unverändert ausgeliefert. Nach Änderungen an der Quelldatei erfolgt vor der ersten Auslieferung zunächst automatisch wieder eine Transformation.

Klassen

AdocFilter

Der Filter prüft für jede HTTP-Anfrage, ob der Name der gewünschten Ressource mit .adoc endet. Wenn ja, wird geprüft, ob bereits eine HTML-Version der Datei existiert. Wenn nicht oder wenn die HTML-Version älter ist als die letzte Änderung der .adoc-Datei veranlasst der AdocFilter den AdocActor eine neue HTML-Version zu erzeugen.

AdocHandler

Die Klasse AdocHandler dient zum 'Einklinken' der Funktion in die HttpHandler-Logik des Moduls jdk.httpserver. Der AdocHandler leitet Anfragen an Ressourcen mit Endung .adoc um auf die Auslieferung ihrer HTML-Version.

AdocActor

Die eigentliche Transformation erledigt die Klasse AdocActor, die dafür die Funktionen von AsciidoctorJ nutzt. Der AdocActor kann so losgelöst von Neon oder den Strukturen von jdk.httpserver auch für andere Programme verwendet werden, wenn sie eine solche Transformation benötigen.

Übrigens: Die Klassen HandlerDescriptor, PatternDelegator und FileHandler sind im Modul http-base enthalten, das für die allermeisten Verwendungen von Neon vermutlich ohnehin immer eingebunden wird.

http-image

Das Modul http-image erzeugt verkleinerte Fassungen eines Originalbildes 'on demand'. Hierzu wird die Klasse ImageFilter einem HttpContext hinzugefügt. Neben http-image.jar sind die Klassenbibliotheken http-base sowie Thumbnailator im Classpath erforderlich.

Beispiel für die Aktivierung des ImageFilter
// einen HttpContext mit einem FileHandler erzeugen
HttpContext context = server.createContext("/meine-app/dateien", new FileHandler());

// den Ablageort von Inhalten angeben
context.getAttributes().put(FileHandler.ATTR_FILE_BASE, "/home/fred/www-daten"));

// den ImageFilter für den Context aktivieren
context.getFilters().add(new ImageFilter());

Damit wird automatisch für alle Dateien, die mit jpg, jpeg oder png enden, der ImageFilter aktiv. Liegt unter /home/fred/www-daten beispielsweise die Bilddatei mein-bild.jpg, wird mit Aufruf von http://example.com/meine-app/dateien/mein-bild_tn.jpg eine verkleinerte Fassung mit 120 Bildpunkten erzeugt. Die folgenden Bildgrößen werden dabei verarbeitet

Table 1. Vom ImageFilter erzeugte Bildgrößen
Bildpunkte Dateiname

120

_tn

240

_kl

500

_sm

700

_mt

1200

_gr

Desweiteren bewirkt der Zusatz _b64 die Erzeugung einer Base64-kodierten Fassung, die für Grafiken mit Data-URI dienen kann. Der Zusatz _b64 wird mit den obigen Namenszusätzen kombiniert indem er ihnen angehängt wird. Der Dateiname mein-bild_tn_b64.jpg erzeugt eine Base64-kodierte und auf 120 Bildpunkte verkleinerte Fassung des Originalbildes namens mein-bild.jpg.

http-cm

Das Modul http-cm etabliert Verwaltungsfunktionen für den Inhalt eines Ordners im Dateisystem. Es erfordert neben der Datei http-cm.jar die Dateien http-base.jar, http-image.jar, thumbnailator.jar sowie gson.jar.

Die Klasse FileManager wird wie folgt eingeschaltet.

Beispiel für die Aktivierung des FileManager aus dem Modul http-cm
// einen HttpContext mit einem FileManager erzeugen
HttpContext context = server.createContext("/daten/", new FileManager());

// den Ablageort von Inhalten angeben
context.getAttributes().put(FileHandler.ATTR_FILE_BASE, "/home/fred/www-daten"));

Damit werden alle Inhalte des Ordners /home/fred/www-daten für die Dateiverwaltung über den Indikator /daten/ zur Bearbeitung zugänglich. Nachfolgend sind die Funktionen der Dateiverwaltung aufgelistet und im Detail beschrieben. Das Kapitel Authentifizierung ergänzt Angaben zum Einschalten der Authentifizierung.

Für die weitere Beschreibung wird angenommen, dass der FileManager als Teil einer App gestartet wurde, die über den URL http://localhost:9292/ zugänglich ist.

Dateiinhalt lesen

Das Lesen von Dateiinhalten ist vergleichbar mit dem Aufruf eines URL im Webbrowser.

Beispiel

Aufruf via HTTP GET an http://localhost:9292/daten/texte/test.txt

Für einen URL, der nicht mit einem Schrägstrich endet ist die Annahme, dass eine Datei gemeint ist und der Inhalt der betreffenden Datei wird ausgeliefert. Der Inhalt der Datei /home/fred/www-daten/texte/test.txt wird in der Antwort zurückgegeben.

Dateien auflisten

Für den Aufruf eines URL, der einen Ordner bezeichnet, wird der Ordnerinhalt aufgelistet.

Beispiel

Aufruf via HTTP GET an http://localhost:9292/daten/pg/neon/

Wenn der URL mit einem Schrägstrich endet, wird der Inhalt des betreffenden Ordners als Dateiliste im JSON-Format geliefert. Mit obigem Aufruf wird der Inhalt des Ordners /home/fred/www-daten/pg/neon/ ausgegeben.

Die Liste eines Ordnerinhalts im JSON-Format
{
  "pfad": "/pg/neon/",
  "dateien": [{
      "name": "anleitung.adoc",
      "typ": "datei",
      "typKlasse": "icon-doc-inv",
      "bild": false
    }, {
      "name": "anleitung.html",
      "typ": "datei",
      "typKlasse": "icon-doc-inv",
      "bild": false
    }, {
      "name": "index.htmi",
      "typ": "datei",
      "typKlasse": "icon-doc-inv",
      "bild": false
    }, {
      "name": "module",
      "typ": "ordner",
      "typKlasse": "icon-folder",
      "bild": false
    }]
}

Bilder

Für Dateien mit den Endungen jpg, jpeg oder png erscheinen nur die Originaldateien in der Dateiliste. Varianten, wie sie mit dem Modul http-image entstehen, werden ausgeblendet. Ist etwa in einem Ordner eine Datei namens mein-bild.jpg abgelegt und es wurde am selben Ort eine kleinere Fassung namens mein-bild_kl.jpg gespeichert, erscheint nur der Eintrag für die Datei mein-bild.jpg in der Dateiliste.

Datei schreiben (PUT)

Aufruf von HTTP PUT fuer eine Datei ueberschreibt eine bestehende Datei mit dem im Body der HTTP-Nachricht uebergebenen Inhalt oder legt eine Datei mit diesem Inhalt an.

Beispiel

Aufruf via HTTP PUT an http://localhost:9292/daten/texte/test.txt

Der Body des HTTP PUT Aufrufs wird in die angegebene Datei geschrieben. Mit obigem Aufruf wird der Inhalt im Body des Aufrufs in die Datei /home/fred/www-daten/texte/test.txt geschrieben. Wenn die Datei existiert, wird sie überschrieben, sonst wird sie mit diesem Inhalt angelegt.

Datei schreiben (POST)

Aufruf von HTTP POST fuer eine Datei legt eine neue Datei mit dem im Body uebergebenen Inhalt an oder erzeugt eine neue Datei mit einer laufenden Nummer, falls diese Datei schon existiert.

Beispiel

Aufruf via HTTP POST an http://localhost:9292/daten/texte/test.txt

Der Body des HTTP POST Aufrufs wird in die unter dem angegebenen Namen neu angelegte Datei geschrieben. Mit obigem Aufruf wird die Datei /home/fred/www-daten/texte/test.txt neu angelegt und der Inhalt im Body des Aufrufs in die Datei geschrieben. Wenn die Datei schon existiert, wird eine neue Datei angelegt, deren Name eine Nummer angehängt wird, z.B. test-1.txt, und besagter Inhalt geschrieben.

Die Antwort liefert den Namen der Datei, die geschrieben wurde.

Ordner anlegen

Aufruf von HTTP POST fuer einen Ordner legt einen neuen Ordner an wenn er noch nicht existiert oder erzeugt einen HTTP-Fehler 422.

Beispiel

Aufruf via HTTP POST an http://localhost:9292/daten/pg/neon/dok/

Der Ordner dok wird im Ordner /home/fred/www-daten/pg/neon/ angelegt, wenn er noch nicht existiert. Wenn es schon einen Ordner dok am angegebenen Ort gibt, wird der HTTP-Fehler 422 Unprocessable Entity zurückgegeben.

Löschen

HTTP DELETE loescht die Liste der Dateien und Ordner im Body.

Beispiel

Aufruf von HTTP DELETE an http://localhost:9292/daten/pg/neon/

Datei- und Ordnerliste im Body einer Anfrage
["test.txt","dok"]

Die Datei test.txt und der Ordner dok werden aus dem Ordner /home/fred/www-daten/pg/neon/ gelöscht. Das Löschen geschieht rekursiv, es werden also auch alle Inhalte von dok gelöscht.

Vorsicht: Es gibt keine Sicherungsanfrage vorab, der Client muss selbst für geeignete Sicherheitsfunktionen sorgen, die dem Löschen vorgeschaltet werden.

Bilder

Beim Löschen von Dateien mit den Endungen jpg, jpeg oder png werden alle Dateien gelöscht, auch Varianten, wie sie mit dem Modul http-image entstehen. Ist etwa in einem Ordner eine Datei namens mein-bild.jpg abgelegt und es wurde am selben Ort eine kleinere Fassung namens mein-bild_kl.jpg gespeichert, werden beide Fassungen gelöscht.

Kopieren

HTTP PUT mit dem Parameter ?copyFrom=pfad kopiert die Liste der Datei- oder Ordnernamen im Body der Anfrage vom Pfad in copyFrom zum Pfad dieser Anfrage.

Beispiel

Aufruf von HTTP PUT an http://localhost:9292/daten/pg/neon/?copyFrom=/entwurf/texte/

Datei- und Ordnerliste im Body der Anfrage
["anleitung.adoc","dok","ordner-2","bild.jpg"]

Die Dateien und Ordner aus der Liste im Body der Anfrage werden vom Ordner /home/fred/www-daten/entwurf/texte in den Ordner /home/fred/www-daten/pg/neon kopiert. Das Kopieren erfolgt rekursiv, d.h. alle Inhalte von Ordnern werden mit verschoben.

Jede Datei, die im Ziel bereits existiert, bekommt im Ziel einen neuen Namen mit einer laufenden Nummer. Bei Ordnern, die im Ziel bereits existieren, bekommt der betreffende Ordner im Ziel zunaechst einen neuen Namen mit einer laufenden Nummer, dann wird der Quellordner ans Ziel kopiert.

Die Angabe des Quellpfades in copyFrom bezieht sich auf einen Pfad relativ zum Pfad, mit dem der FileManager eingeschaltet wurde.

Bilder

Beim Kopieren von Dateien mit den Endungen jpg, jpeg oder png werden alle Dateien kopiert, auch Varianten, wie sie mit dem Modul http-image entstehen. Ist etwa in einem Ordner eine Datei namens mein-bild.jpg abgelegt und es wurde am selben Ort eine kleinere Fassung namens mein-bild_kl.jpg gespeichert, werden beide Fassungen kopiert.

Verschieben

HTTP PUT mit dem Parameter ?moveFrom=pfad verschiebt die Liste der Datei- oder Ordnernamen im Body der Anfrage vom Pfad in moveFrom zum Pfad dieser Anfrage.

Beispiel

Aufruf von HTTP PUT an http://localhost:9292/daten/pg/neon/?moveFrom=/entwurf/texte/

Datei- und Ordnerliste im Body der Anfrage
["anleitung.adoc","dok","ordner-2","bild.jpg"]

Die Dateien und Ordner aus der Liste im Body der Anfrage werden vom Ordner /home/fred/www-daten/entwurf/texte in den Ordner /home/fred/www-daten/pg/neon verschoben. Das Verschieben erfolgt rekursiv, d.h. alle Inhalte von Ordnern werden mit verschoben.

Jede Datei, die im Ziel bereits existiert, bekommt im Ziel einen neuen Namen mit einer laufenden Nummer. Bei Ordnern, die im Ziel bereits existieren, bekommt der betreffende Ordner im Ziel zunaechst einen neuen Namen mit einer laufenden Nummer, dann wird der Quellordner ans Ziel verschoben.

Die Angabe des Quellpfades in moveFrom bezieht sich auf einen Pfad relativ zum Pfad, mit dem der FileManager eingeschaltet wurde.

Bilder

Beim Verschieben von Dateien mit den Endungen jpg, jpeg oder png werden alle Dateien verschoben, auch Varianten, wie sie mit dem Modul http-image entstehen. Ist etwa in einem Ordner eine Datei namens mein-bild.jpg abgelegt und es wurde am selben Ort eine kleinere Fassung namens mein-bild_kl.jpg gespeichert, werden beide Fassungen verschoben.

Duplizieren

HTTP PUT mit dem Parameter ?duplicate legt die Kopie einer Datei am gegenwärtigen Ablageort an.

Beispiel

Aufruf von HTTP PUT an http://localhost:9292/daten/texte/test.txt?duplicate

Im Ordner /home/fred/www-daten/texte wird ein Duplikat der Datei test.txt unter dem Namen test-Kopie.txt angelegt. Wenn es die Datei bereits gibt, wird dem Namen eine laufende Nummer angehängt, z.B. test-Kopie-2.txt.

Umbenennen

HTTP PUT mit dem Parameter ?renameTo=neuer Name benennt die Datei oder den Ordner um, sofern der neue Name noch nicht vergeben ist.

Beispiel

Aufruf von HTTP PUT an http://localhost:9292/daten/texte/test.txt?renameTo=textdatei.txt

Die Datei /home/fred/www-daten/texte/test.txt erhält den neuen Namen textdatei.txt, sofern eine Datei solchen Namens in diesem Ordner noch nicht existiert.

Bilder

Beim Umbenennen von Dateien mit den Endungen jpg, jpeg oder png werden alle Dateien umbenannt, auch Varianten, wie sie mit dem Modul http-image entstehen. Ist etwa in einem Ordner eine Datei namens mein-bild.jpg abgelegt und es wurde am selben Ort eine kleinere Fassung namens mein-bild_kl.jpg gespeichert, werden beide Fassungen umbenannt.

Packen

HTTP PUT mit dem Parameter ?zip packt den Ordner.

Beispiel

Aufruf von HTTP PUT an http://localhost:9292/daten/texte/?zip

Der Inhalt des Ordners /home/fred/www-daten/texte wird komprimiert und als Datei /home/fred/www-daten/texte.zip erstellt. Das Packen erfolgt rekursiv, d.h. alle Inhalte des betreffenden Ordners werden ins komprimierte ZIP-Archiv gepackt.

Entpacken

HTTP PUT mit dem Parameter ?unzip entpackt eine ZIP-Archivdatei.

Beispiel

Aufruf von HTTP PUT an http://localhost:9292/daten/archive/mein-archiv.zip?unzip

Der Inhalt der ZIP-Archivdatei /home/fred/www-daten/archive/mein-archiv.zip wird in den Ordner /home/fred/www-daten/archive entpackt.

Authentifizierung

Funktionen zur Verwaltung von Inhalten sind gewöhnlich nur autorisierten Nutzern zugänglich, was einen Authentifizierungsmechanismus erfordert. Dafür sind die Module http-oauth.jar, http-realm.jar, jjwt-api.jar, jjwt-impl.jar und jjwt-gson.jar nötig.

Am Beispiel eines FileManager wird hier stellvertretend für andere Module gezeigt, wie die Authentifizierung für ein Neon-Modul eingeschaltet wird. Wenn wie im Beispiel zum Einschalten der Bearer Token Authentifizierung ein Authenticator erstellt wurde, kann dieser dem FileManager aus dem vorangegangenen Beispiel wie folgt beigegeben werden.

Beispiel für die Aktivierung eines Authenticators
// den Authenticator fuer das Modul http-oauth bekannt machen
context.getAttributes().put(BearerLoginHandler.ATTR_AUTHENTICATOR, auth);

// die Rolle festlegen, die autorisierte Benutzer besitzen muessen
context.getAttributes().put(FileManager.ATTR_ROLE, "dateiAdminRolle");

// den Authenticator fuer diesen HttpContext aktivieren
context.setAuthenticator(auth);

Der Authenticator stellt sicher, dass nur angemeldete Benutzer, die die Rolle dateiAdminRolle besitzen, die Funktionen des FileManager verwenden.

Im Beispiel oben antwortet der Authenticator auf alle nicht authentifizierten Anfragen an /daten/ mit Login- und Refresh-Aufforderungen gemäß der Spezifikation für Bearer Token Authentication. Als nicht authentifiziert gelten dabei alle Anfragen ohne Token oder mit ungültigem Token.

Clients müssen als Reaktion darauf Login- und Refresh-Aufrufe implementierten. Die Einrichtung der serverseitigen Service-Endpunkte für diese Client-Aufrufe ist beim Modul http-oauth beschrieben. Wie sich diese Authentifizierung auf der Seite des Clients integrieren lässt beschreibt das Dokument Authentifizierung für Webclients.

http-template

Das Modul http-template erweitert Neon um die Verarbeitung von Vorlagen (Templates) auf der Grundlage von Mustache. Neben http-template.jar sind die Klassenbibliotheken http-base.jar sowie compiler.jar im Classpath erforderlich.

Die Klasse TemplateActor des Moduls http-template wird zur Mischung von Daten mit einer Vorlage verwendet. Ihre Methode render fügt dem HTML aus einer Mustache-Vorlage die Daten hinzu, die ihr als HashMap übergeben werden.

Beispiel eines HttpHandlers, der eine Mustache-Vorlage rendert (Auszug)
public void handle(HttpExchange exchange) throws IOException {
  // die gewuenschte Datei bestimmen
  File file = getFile(exchange);
  // die Daten fuer die Vorlage bereitstellen
  Map data = new HashMap();
  data.put("title", file.getName());
  data.put("content", getFileContent(file));
  // die Daten in die Vorlage eintragen
  String html =
    new TemplateActor().render(exchange, data, "vorlage.mustache");
  byte[] bytes = html.getBytes();
  // die Antwort ausgeben
  exchange.sendResponseHeaders(FileHandler.SC_OK, bytes.length);
  OutputStream os = exchange.getResponseBody();
  os.write(bytes, 0, bytes.length);
  os.flush();
  os.close();
}

public String getFileContent(File file) {
  BufferedReader reader = new BufferedReader(new FileReader(file));
  StringWriter writer = new StringWriter();
  String line = reader.readLine();
  while (line != null) {
    writer.write(line);
    writer.write("\r\n");
    line = reader.readLine();
  }
  reader.close();
  return writer.toString();
}

public File getFile(HttpExchange exchange) {
  HttpHelper helper = new HttpHelper();
  String relativePath = helper.getFileName(exchange);
  Map attributes = exchange.getHttpContext().getAttributes();
  String fileBase = helper.getAttrStr(attributes,
             FileHandler.ATTR_FILE_BASE, App.STR_DOT);
  File file;
  if(relativePath.endsWith(App.STR_SLASH)) {
    file = helper.tryWelcomeFiles(exchange, relativePath);
  } else {
    file = new File(fileBase, relativePath);
  }
  return file;
}

Die Angabe der Vorlage erfolgt als einfacher Dateiname in Form eines String. Der TemplateActor versucht, diese vom Ablageort zu lesen, der im Attribut FileHandler.ATTR_FILE_BASE für den HttpContext angegeben wurde. Gelingt dies nicht, wird versucht, die Vorlage aus dem Classpath zu lesen. Ist das Lesen der Vorlage erfolgreich, werden die Daten aus der HashMap data in die Vorlage eingebaut und das Ergebnis ausgegeben. Die HashMap data wird dazu mit den Angaben beladen, die in der Vorlage als Platzhalter eingetragen sind.

Beispiel einer Mustache-Vorlage für eine HTML-Datei
<!DOCTYPE html>
<html>
  <head>
    <title>{{title}}</title>
  </head>
  <body>
    <div class="inhalt">
      {{content}}
    </div>
  </body>
</html>

Die Logik zur Bestimmung der Vorlage zum jeweiligen Inhalt ist in der Klasse TemplateActor bewußt einfach gehalten. Sie lässt sich mit einer eigenen Implementierung der Klasse beliebig abwandeln. Zudem beinhaltet die Klassenbibliothek compiler.jar, die die Java-Implementierung für Mustache bereitstellt, umfangreiche zusätzliche Möglichkeiten der Nutzung von Mustache. Die hier vorgestellte Implementierung stellt nur ein Beispiel und mithin den Ausgangspunkt dar, wie Mustache-Vorlagen aus Neon heraus verwendbar sind.

Änderungshistorie

Version 1

Vom 18. Juni 2021

Version 2

Vom 2. Januar 2022