Neon [1] 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.

Die folgenden Module müssen noch auf eine Verwendung mit Neon 2 umgebaut werden

  • http-cm - Dateien mit Neon verwalten

neon-image

Das Modul neon-image erzeugt verkleinerte Fassungen eines Originalbildes 'on demand'. Hierzu wird in der Serverbeschreibung [2] die Klasse ImageFilter einem HttpContext hinzugefügt. Neben neon-image.jar ist die Klassenbibliothek Thumbnailator [4] im Classpath erforderlich.

in der Serverbeschreibung den ImageFilter einem HttpContext hinzufügen
  "server": [
    {
      "name": "Mein Server",
      "port":7001,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "true",
          "contextPath": "/meine-app/dateien",
          "filter": [
            "de.uhilger.neon.image.ImageFilter"
          ],
          "attributes": {
            "contextName": "fsrv",
            "fileBase": "/home/fred/www-daten",
            "imageFilterPattern": ".+\.jpg|.+\.jpeg|.+\.png|.+\.bmp"
          }
        }
      ]
    }
  ]

Der im Beispiel gezeigte Eintrag filter im Element contexts der Serverbeschreibung fügt dem betreffenden HTTP-Kontext ein Objekt der Klasse ImageFilter hinzu. Die Annahme ist, dass der ImageFilter üblicherweise gemeinsam mit einem FileServer [5] eingesetzt wird. Der FileServer erfordert im Attribut fileBase die Angabe eines Ablageortes für Dateien. Die Verwendung des FileServer ist nicht zwingend für den Einsatz eines ImageFilter, die Angabe fileBase wird aber so auch vom ImageFilter benötigt.

Mit dem Attribut imageFilterPattern wird dem ImageFilter in Form einer Regular Expression signalisiert, welche Dateien verarbeitet werden sollen. Eine Angabe von .+\.bmp würde zum Beispiel alle Dateien betreffen, die mit .bmp enden. Standardmäßig werden vom ImageFilter alle Dateien verarbeitet, die mit .jpg, .jpeg oder .png enden, was einer Angabe von .+\.jpg|.+\.jpeg|.+\.png entspricht. Wenn diese Gruppe von Dateien verarbeitet werden soll, kann daher die Angabe des Attributs imageFilterPattern entfallen. Das Muster wird vom ImageFilter ohne Berücksichtigung von Groß- und Kleinschreibung angewendet.

Mit Hinzufügen eines ImageFilter zu einem HTTP-Kontext wird automatisch für alle Dateien, deren Name zum angegebenen Muster passt, der ImageFilter aktiv. Liegt unter /home/fred/www-daten beispielsweise die Bilddatei mein-bild.jpg, wird mit Aufruf von http://localhost:7001/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 [6] 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.

neon-adoc

Mit neon-adoc wird Neon um die Möglichkeit erweitert, Dokumente im AsciiDoc-Format [7] nach HTML oder PDF zu transformieren. Neben der Datei neon-adoc.jar sind die Java-Archive aus dem Paket AsciidoctorJ [8] erforderlich. Die Umwandlung geschieht 'on-demand', es muss kein besonderer Build-Prozess hergestellt werden. Zur Umwandlung von Text im AsciiDoc-Format wird lediglich der AdocFilter aktiviert wie nachfolgend beschrieben.

in der Serverbeschreibung den AdocFilter einem HttpContext hinzufügen
  "server": [
    {
      "name": "Mein Server",
      "port":7001,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "false",
          "contextPath": "/meine-app",
          "filter": [
            "de.uhilger.neon.adoc.AdocFilter"
          ],
          "attributes": {
            "contextName": "fsrv",
            "fileBase": "/home/fred/www-daten"
          }
        }
      ]
    }
  ]

Der im Beispiel gezeigte Eintrag filter im Element contexts der Serverbeschreibung fügt dem betreffenden HTTP-Kontext ein Objekt der Klasse AdocFilter hinzu. Die Annahme ist, dass der AdocFilter gemeinsam mit einem FileServer [5] eingesetzt wird. Der FileServer erfordert im Attribut fileBase die Angabe eines Ablageortes für Dateien. Die Verwendung des FileServer ist nicht zwingend für den Einsatz eines AdocFilter, die Angabe fileBase wird aber so auch vom AdocFilter benötigt.

Der AdocFilter prüft für jede HTTP-Anfrage, ob eine Datei mit Endung .adoc angefragt ist. In diesem Fall wird geprüft, ob eine aktuelle HTML-Version dieser Datei am angefragten Ort besteht. Wenn nicht, wird die HTML-Version erzeugt. Als Variante kann in der Anfrage der Zusatz ?pdf=true hinzugefügt werden. Damit wird zusätzlich eine PDF-Version aus der AsciiDoc-Quelldatei erzeugt.

Die Auslieferung von AsciiDoc-Inhalten in Form von HTML oder PDF muss dann die Anwendung übernehmen. Hierfür kann ein Actor wie folgt erstellt werden.

ein Beispiel für einen Actor zur Auslieferung von AsciiDoc-Inhalten
@Actor(name = "fileServer")
public class FileActor {

  @Action(handler = {"fsrv"}, route = "/", type = Action.Type.GET, handlesResponse = true)
  public void run(HttpExchange exchange) throws IOException {
    URI uri = exchange.getRequestURI();
    String path = uri.getPath();
    if (path.toLowerCase().endsWith(".adoc")) {
      String fileBase = (String) exchange.getHttpContext().getAttributes()
          .getOrDefault(FileServer.ATTR_FILE_BASE, FileServer.STR_EMPTY);
      String query = uri.getQuery();
      new HttpResponder().serveFile(exchange, getDocFile(fileBase, path, query));
    } else {
      new FileServer().serveFile(exchange);
    }
  }

  private File getDocFile(String fileBase, String path, String query) throws IOException {
    String ext = "html";
    if(query.toLowerCase().contains("pdf=true")) {
      ext = "pdf";
    }
    String dot = ".";
    String noExt = path.substring(0, path.lastIndexOf(dot));
    return new File(fileBase, noExt + dot + ext);
  }
}

Im obigen Beispiel wird ein Actor an den HTTP-Kontext fsrv gebunden, der laut Serverbeschreibung wiederum an den Kontext-Pfad /meine-app gebunden ist. Der Actor verarbeitet mit dem Attribut route = "/" alle Routen, die an diesen HTTP-Kontext gerichtet sind. Eine Anfrage wie beispielsweise die unten gezeigte gelangt so an den oben dargestellten Actor.

http://localhost:7001/meine-app/pfad/zum/dokument.adoc

Für Anfragen, deren Pfad mit .adoc endet, wird anstelle der angefragten .adoc-Datei die gleichnamige HTML- oder PDF-Datei ausgegeben. Hierbei wird angenommen, dass zuvor der AdocFilter die angefragte AsciiDoc-Datei nach HTML oder PDF transformiert hat.

Andere Dateien werden an eine Instanz des FileServer von Neon [5] weitergereicht. Auf diese Weise werden alle angefragten Inhalte von diesem Actor ausgegeben, aber Anfragen nach AsciiDoc-Inhalten in ihrer nach HTML oder PDF transformierten Fassung.

neon-up

Das Modul neon-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 in einem Neon Actor verwendet werden kann, um zum Server heraufgeladene Dateien entgegenzunehmen.

Beispiel für den Einsatz der Klasse MultipartStream in einem Neon Actor
@Actor(name = "uploader")
public class UploadActor {

 public static final String ATTR_FILENAME = "filename=";
 public static final String TEMP_FILE_NAME = "temp.up";

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

 @Action(handler = {"up"}, route = "/upload", type = Action.Type.PUT, handlesResponse = false)
 public void run(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 run 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. Dabei ist der Einfachheit halber im Beispiel der Ablageort für die hogeladene Datei hart kodiert auf den Ort /some/path/to/storage.

Eine hochgeladene Datei erhält ebenfalls im Beispiel hart kodiert den Namen temp.up. Mit Abschluss des Uploads liegt also eine Datei /some/path/to/storage/temp.up vor, die von dort weiterverarbeitet, also z.B. von dort umbenannt und verschoben werden kann.

Diese Logik zur weiteren Verarbeitung einer oder mehrerer so hochgeladenen Dateien ist nicht im Beispiel enthalten und nur im Kommentar // processing of files symbolisiert. Die im Beispiel hart kodierten Variablen können in einer Anwendung zum Beispiel über die Serverbeschreibung von Neon [2] als Attribute eines HTTP-Kontext übergeben werden, wie es in vorangegangenen Kapiteln dieses Dokuments verschiedentlich zu sehen ist.

neon-template

Das Modul neon-template erweitert Neon um die Verarbeitung von Vorlagen (Templates) auf der Grundlage von Mustache [11]. Neben neon-template.jar ist die Klassenbibliothek compiler.jar von Mustache im Classpath erforderlich.

Die Klasse TemplateWorker des Moduls neon-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 Actors, der eine Mustache-Vorlage rendert (Auszug)
public void run(HttpExchange exchange) throws IOException {
  // die gewuenschte Datei bestimmen
  String fileBase = (String) exchange.getHttpContext().getAttributes()
                 .getOrDefault(FileServer.ATTR_FILE_BASE, FileServer.STR_EMPTY);
  String fileName = new HttpHelper().getFileName(exchange);
  File file = new File(fileBase, fileName);
  // 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 TemplateWorker().render(exchange, data, "vorlage.mustache");
  // die Antwort ausgeben
  new HttpResponder().antwortSenden(exchange, HttpResponder.SC_OK, html);
}

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

Die Angabe der Vorlage erfolgt als einfacher Dateiname in Form eines String. Der TemplateWorker versucht, diese vom Ablageort zu lesen, der im Attribut FileServer.ATTR_FILE_BASE für den HTTP-Context 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 TemplateWorker 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.

http-cm

Wartet auf Neufassung: Dateien mit Neon verwalten

Lizenz

Alle hier beschriebenen Module außer dem Modul neon-up werden unter den Bedigungen der GNU Affero General Public License 3.0 bereitgestellt [9]. Das Modul neon-up unterliegt den Bedingungen der Apache License 2.0 [10].

Änderungsverlauf

Version 1

Vom 18. Juni 2021

http-base - Dateien und Streams ausliefern
http-realm - Nutzerverzeichnis zur Authentifizierung
http-oauth - Bearer Authentication für Neon
http-up - Dateien zu Neon heraufladen
http-adoc - Asciidoctor mit Neon transformieren
http-image - Bilder mit Neon verwenden

Version 2

Vom 2. Januar 2022

http-cm - Dateien mit Neon verwalten
http-template - Mustache-Vorlagen mit Neon verarbeiten

Version 3

Vom 26. Februar 2024

neon-auth [3] - Bearer Token Authentication für Neon 2
neon-image - Bilder mit Neon 2 verwenden
neon-adoc - AsciiDoc mit Neon 2 transformieren
neon-up - Dateien zu Neon 2 heraufladen
neon-template - Mustache-Vorlagen mit Neon 2 verwenden

Verweise