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.
-
neon-image - Bilder mit Neon verwenden
-
neon-adoc - AsciiDoc mit Neon transformieren
-
neon-up - Dateien zu Neon heraufladen
-
neon-template - Mustache-Vorlagen mit Neon rendern
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.
"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
Bildpunkte | Dateiname |
---|---|
120 |
|
240 |
|
500 |
|
700 |
|
1200 |
|
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.
"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.
@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.
@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.
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.
<!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.
Ä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
[4] Thumbnailator
[5] Neon: FileServer
[7] AsciiDoc
[8] AsciidoctorJ
[10] Apache License 2.0
[11] Mustache