Neon [1] ist ein ultrakompakter, modular aufgebauter HTTP-Server auf der Grundlage des Java-Moduls jdk.httpserver zum Einbetten in Apps und Microservices.

Übersicht

Eine Anwendung kann mit Hilfe von Neon um einen eingebetteten HTTP Server erweitert werden und so selbst auf Anfragen über HTTP reagieren. Sie wird damit über das Netz steuerbar und kann eine Bedienoberfläche liefern, die in einem Web Browser funktioniert. Dies erfordert nur wenige Schritte:

  1. Einen oder mehrere Klassen des Typs Actor bauen

  2. Eine Serverbeschreibungsdatei erstellen

  3. Neon aus der Anwendung heraus starten

In diesem Dokument sind diese Schritte im Detail beschrieben.

Neon starten

Neon lässt sich wie folgt als Teil einer Anwendung starten.

Start von Neon
import de.uhilger.neon.Factory;

public class App() {

  public static void main(String[] args) {
    App app = new App();
  }

  public App() {
    Factory f = new Factory();
    try {
      NeonDescriptor d = f.readDescriptor(new File("server.json"));
      f.runInstance(d);
    } catch (Exception ex) {
      Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
    }
  }
}

Im obigen Code-Beispiel wird ein Objekt der Klasse de.uhilger.neon.Factory erzeugt und eine Beschreibungsdatei namens server.json gelesen. Anschließend wird die Instanz von Neon mit dem Aufruf der Methode runInstance der Factory gestartet.

Eine so gestartete Anwendung beinhaltet einen HTTP Server, der über die in der Serverbeschreibung konfigurierten Ports auf HTTP Anfragen lauscht und diese beantwortet. Der Server und mithin die Anwendung läuft wie ein Dienst ohne zeitliche Begrenzung und kann wie in Neon stoppen beschrieben gestoppt werden.

Serverbeschreibung

Die Datei server.json beschreibt die Elemente, die Neon als Server bereitstellen soll. Im einfachsten Fall ist dies ein einzelner HTTP-Kontext, wie er im folgenden Beispiel beschrieben ist.

die Beschreibungsdatei eines Neon-Servers
{
  "contentType": "text/json",
  "contentId": "neon-descriptor-0.1.0",
  "contentDescription":"Neon Descriptor Version 0.1.0",
  "instanceName": "Mein Server",
  "instanceDescription": "Beispiel-Serverinstanz",
  "actorPackages": [
    "com.example.my.app",
    "com.example.my.other.library"
  ],
  "server": [
    {
      "name": "mein-server",
      "port":7000,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "false",
          "contextPath": "/meine/app",
          "attributes": {
            "contextName": "srv-ctx"
          }
        }
      ]
    }
  ]
}

Nachfolgend sind die Elemente einer Beschreibungsdatei im Detail aufgeführt.

Element actorPackages

Eine Liste von Package-Namen, die nach Klassen des Typs de.uhilger.neon.Actor durchsucht werden. Die so vorgefundenen Actor-Klassen liefern den Code, der über HTTP-Aufrufe zugänglich wird. Eine Angabe von Packages auf diesem Wege verkürzt den Vorgang, weil nicht der gesamte Classpath nach Actor-Klassen durchsucht werden muss. Innerhalb der angegebenen Packages erfolgt die Suche einschließlich aller Unterpackages (rekursiv).

Element server

Ein oder mehrere Server-Elemente, eines je Port.

Element contexts

Ein oder mehrere Elemente, die von Neon als Objekte des Typs HttpContext angelegt werden. Jeder Kontext kann über die Kombination aus Port und contextPath aufgerufen werden und hat die folgenden Eigenschaften.

className

Mit dem Attribut className wird die Klasse angegeben, die für diesen Kontext als HttpHandler dient. Die Klasse de.uhilger.neon.Handler kann für alle Kontexte als generischer Handler dienen. Sie erzeugt zur Laufzeit dynamisch Objekte des Typs Actor aus den Klassen, die in actorPackages gefunden werden.

sharedHandler

Über sharedHandler kann ein einzelnes Handler-Objekt für mehrere Kontexte verwendet werden.

contextPath

Jeder Kontext ist an einen bestimmten Pfad gebunden, der mit dem Attribut contextPath angegeben wird.

contextName

Das Attribut contextName gibt den Namen an, über den die Bindung von Klassen des Typs Actor erfolgt. Weitere Attribute werden aus der Beschreibungsdatei an den Kontext übergeben und können dort der Anwendung als Parameter dienen.

Actor-Klassen

Neon macht Java-Code selbsttätig über HTTP-Aufrufe zugänglich. Voraussetzung dafür ist, dass solcher Code mit den Annotationen Actor und Action versehen wird. Actor-Objekte werden erst beim Aufruf erzeugt. Für jeden HTTP-Aufruf erzeugt Neon ein Actor-Objekt, das mit Ende der Ausführung der mit Action annotierten Methode wieder freigegeben wird.

eine Klasse des Typs Actor
package com.example.my.app.actors;

import de.uhilger.neon.Actor;
import de.uhilger.neon.Action;

@Actor(name="beliebiger-name")
public class Example {

  @Action(handler={"srv-ctx"}, route="/eine/route/zum/beispiel", type=Action.Type.GET, handlesResponse = false)
  public void run() {
    // hier kann beliebiger Code ausgefuehrt werden
  }

}

Annotation Actor

Durch die Annotation Actor wird eine Klasse als vom Typ Actor erkennbar. Während der automatischen Erzeugung eines eingebetteten Servers können damit Methoden gefunden werden, die über den Server aufrufbar sein sollen. In Klassen, die als Actor annotiert sind, werden Methoden gesucht, die mit der Annotation Action versehen sind.

Annotation Action

Die Annotation Action kennzeichnet Methoden einer Klasse, die über HTTP aufrufbar sein sollen. Die folgenden Attribute kommen dabei zur Anwendung.

handler

Das Attribut handler={"name-laut-serverbeschreibung"} der Annotation Action verbindet die Methode einer Actor-Klasse mit der Konfiguration eines HTTP-Kontexts aus der Serverbeschreibung. Das Attribut contextPath des betreffenden Kontexts wird zur Laufzeit zur Bildung des URL zusammengesetzt, über den die Methode aufrufbar sein soll. Im Beispiel würde sich so der URL http://localhost:7000/meine/app ergeben. Das Attribut handler erlaubt es, mehrere HTTP-Kontexte anzugeben und damit die Methode über unterschiedliche Kombinationen aus Port und Kontext-Pfad ausführen zu können. Die Angabe mehrerer Kontexte erfolgt z.B. mit handler={"ctx-1", "ctx-2", "ctx-3"}.

route

Das Attribut route nennt die Route, an die der Aufruf der Methode geknüpft ist. Die im Beispiel genannte Route wird zur Laufzeit zur Bildung des URL http://localhost:7000/meine/app/eine/route/zum/beispiel herangezogen. Über diesen URL kann der Code der Methode run des Beispiels ausgeführt werden.

type

Mit dem Attribut type wird angegeben, mit welcher HTTP-Methode die betreffende Methode aufrufbar sein soll, GET, PUT, POST oder DELETE.

handlesResponse

Mit handlesResponse wird dem Handler mitgeteilt, ob die Action sich selbst um das Senden einer Antwort kümmert. Ein Eintrag von false veranlasst den Handler, eine Antwort zu erzeugen.

Der Name der Methode muss nicht run wie im Beispiel sondern kann beliebig lauten.

Mit der Einschränkung auf den Typ String bei Parametern und Rückgabewert bleibt die Verwendung allgemein und zugleich einfach. Die Kommunikation über HTTP erfolgt via Text, so dass komplexere Typen ohnehin Serialisiert und Deserialisiert werden müssen, beispielsweise mit Hilfe von Gson [4].

Im Folgenden eine Übersicht der Vorgaben an eine Verwendung der Annotation Action.

Rückgabewert

Mit der Annotation Action angebundene Methoden müssen keinen Rückgabewert liefern. Wenn aber ein Rückgabewert geliefert wird, muss dieser vom Typ String sein.

Beispiel für eine Action-Methode mit Rückgabewert
@Actor(name="beliebiger-name")
public class Example {

  @Action(handler={"srv-ctx"}, route="/eine/route/zum/beispiel", type=Action.Type.GET, handlesResponse = false)
  public String run() {
    try {
        // hier kann beliebiger Code ausgefuehrt werden

        return "Die Methode wurde erfolgreich ausgefuehrt.";
    } catch(Exception ex) {
        // hier den Fehler behandeln
        return "Es ist ein Fehler aufgetreten: " + ex.getMessage();
    }
  }

}

Parameter

Parameter müssen vom Typ String sein. Zudem müssen die Parameter der HTTP-Anfrage dieselben Namen haben wie die Parameter der Methode, an die sie gebunden sind.

Beispiel für eine Action-Methode mit Parametern
@Actor(name="beliebiger-name")
public class Example {

  @Action(handler={"srv-ctx"}, route="/eine/route/zum/beispiel", type=Action.Type.GET, handlesResponse = false)
  public String run(String param1, String param2) {
    try {
        // hier kann beliebiger Code ausgefuehrt werden

        return "param1 lautet: " + param1 + ", param2 lautet: " + param2;
    } catch(Exception ex) {
        // hier den Fehler behandeln
        return "Es ist ein Fehler aufgetreten: " + ex.getMessage();
    }
  }

}

In obigem Beispiel muss der URL im Falle eines HTTP GET die Query ?param2=Inhalt 2&param1=Inhalt 1 enthalten oder bei PUT, POST oder DELETE diese Angaben im Body des HTTP-Aufrufes mitgeben. Eine weitere Möglichkeit ist es, Teile der Route als Parameter anzulegen, wie das folgende Beispiel zeigt.

Beispiel für eine Action-Methode mit Parametern als Teil der Route
@Actor(name="beliebiger-name")
public class Example {

  @Action(handler={"srv-ctx"}, route="/eine/route/zum/beispiel/{param2}/{param1}", type=Action.Type.GET, handlesResponse = false)
  public String run(String param1, String param2) {
    try {
        // hier kann beliebiger Code ausgefuehrt werden

        return "param1 lautet: " + param1 + ", param2 lautet: " + param2;
    } catch(Exception ex) {
        // hier den Fehler behandeln
        return "Es ist ein Fehler aufgetreten: " + ex.getMessage();
    }
  }

}

Auch hier müssen die Namen der Parameter im URL mit den Namen der Parameter der Methode übereinstimmen. Die Parameter müssen zudem am Ende der Route stehen, eine Route wie etwa /eine/route/{param2}/zum/{param1}/beispiel wird nicht verarbeitet.

Parameter HttpExchange

Wenn einer der Parameter als HttpExchange deklariert ist, übergibt Neon das entsprechende Objekt zur Laufzeit automatisch.

Beispiel für eine Action-Methode, die ein Objekt der Klasse HttpExchange benötigt
@Actor(name="beliebiger-name")
public class Example {

  @Action(handler={"srv-ctx"}, route="/eine/route/zum/beispiel", type=Action.Type.GET, handlesResponse = false)
  public String run(HttpExchange exchange, String param1, String param2) {
    try {
        // hier kann beliebiger Code ausgefuehrt werden

        String contextPfad = exchange.getContext().getPath();

        return "pfad: " + contextPfad + ", param1 lautet: " + parameter1 + ", param2 lautet: " + parameter2;
    } catch(Exception ex) {
        // hier den Fehler behandeln
        return "Es ist ein Fehler aufgetreten: " + ex.getMessage();
    }
  }

}

File Server

Mit der Klasse de.uhilger.neon.FileServer beinhaltet Neon eine Möglichkeit zur Auslieferung der Inhalte von Dateien und damit die Funktion eines klassischen Webservers. Der FileServer liefert Dateiinhalte auf einmal oder als Stream, abhängig davon, ob die HTTP-Anfrage einen Range-Header enthält [5].

Die Beschreibungsdatei des Servers muss hierzu einen HTTP-Kontext eröffnen, über den Dateiinhalte ausgeliefert werden. Der Klasse FileServer wird dabei über das Attribut fileBase der Ablageort für Dateien mitgeteilt.

eine Beschreibungsdatei mit einem Kontext zur Auslieferung von Dateiinhalten
{
  "contentType": "text/json",
  "contentId": "neon-descriptor-0.1.0",
  "contentDescription":"Neon Descriptor Version 0.1.0",
  "instanceName": "File-Server",
  "instanceDescription": "Ein einfacher File Server",
  "actorPackages": [
    "de.uhilger.meine.app"
  ],
  "server": [
    {
      "name": "File Server",
      "port":7000,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "false",
          "contextPath": "/data",
          "attributes": {
            "contextName": "www",
            "fileBase": "/home/fred/dateien/www"
          }
        }
      ]
    }
  ]
}

In obiger Serverbeschreibung verweist die Angabe actorPackages auf de.uhilger.meine.app. Dort muss ein Actor wie der folgende hinterlegt sein.

Beispiel für eine Actor-Klasse zur Auslieferung von Dateiinhalten
@Actor(name="fileServer")
public class FileActor {

  @Action(handler={"www"}, route="/", type=Action.Type.GET, handlesResponse = false)
  public void run(HttpExchange exchange) throws IOException {
    new FileServer().serveFile(exchange);
  }
}

Mit der Angabe handler={"www"} wird die Methode run der Klasse FileActor an den HTTP-Kontext gebunden, der in der Serverbeschreibung angegeben ist. Dies bewirkt, dass Aufrufe wie folgt beantwortet werden.

http://localhost:7000/data/pfad/zu/hallo-welt.html

liefert den Inhalt von

/home/fred/dateien/www/pfad/zu/hallo-welt.html

Routen

Als Route wird der Teil des URL angesehen, der eine bestimmte Aktion auslöst. Ein Uniform Resource Locator (URL) lässt sich in verschiedene Teile zerlegen.

http://localhost:7000/meine/app/route/zur/aktion?param1=eins&param2=zwei

Protokoll: http
Domain: localhost
Port: 7000
Context-Path: /meine/app
Route: /route/zur/aktion
Query: param1=eins&param2=zwei

Die Route ergibt sich nach Wegnahme aller möglichen Kontext-Pfade, d.h., Neon probiert alle Kontext-Pfade, für die laut Serverbeschreibung ein HttpContext angelegt wurde. Wenn ein Kontext-Pfad passt, wird diesem Kontext die Anfrage weitergeleitet.

Im Kontext bestimmt die Klasse de.uhilger.neon.Handler die Route, wie sie nach Wegnahme des Kontext-Pfades entsteht und verwendet diese Route gegen den Routen-Ausdruck, der von der Annotation de.uhilger.neon.Action geliefert wird. Neben einer festen Angabe wie im obigen Beispiel kann in der Action auch die Route / verwendet werden. Damit werden alle Routen an den Actor weitergegeben.

Ein Actor, der über die Route / mehrere Routen entgegennimmt, verwendet üblicherweise ein Objekt der Klasse HttpExchange, um die angefragte Route zu bestimmen. Anstelle der Parameterübergabe per Query wie weiter oben gezeigt lassen sich Parameter auch als Teil der Route übergeben wie in folgendem Beispiel

http://localhost:7000/meine/app/route/zur/aktion/zwei/eins

Dies erfordert eine entsprechende Annotation im Actor.

Dynamischer Datenaustausch

Actor-Objekte werden zur Laufzeit selbsttätig aus dem HTTP-Kontext des Servers erzeugt. Zum Zeitpunkt des Entwurfs von Anwendungen ist eventuell noch nicht absehbar, welche Daten zwischen Actor-Objekten und einer Anwendung fließen oder wo sie genau herkommen werden. Aus diesem Grund stellt Neon mit den Schnittstellen DataProvider und DataConsumer einen dynamisch zu Laufzeit verwendbaren Weg bereit, Daten mit Actor-Objekten auszutauschen.

Diese Schnittstellen können verwendet werden, indem der Neon-Factory in einer Variante der Methode runInstance eine Liste mit DataProvider-Objekten übergeben wird. Für HTTP-Aufrufe übernimmt dann Neon die Bindung jener DataProvider-Objekten an Aktoren automatisch immer dann, wenn ein Actor-Objekt von Neon erzeugt wird, das die Schnittstelle DataConsumer implementiert. Nachfolgend ein Beispiel für einen solchen Actor.

Die Schnittstellen DataProvider und DataConsumer können aber auch ausserhalb des HTTP-Kontextes verwendet werden und erlauben es, Daten mit Actor-Objekten auszutauschen, ohne diese als Parameter oder Rückgabewerte von Methoden der Actor-Klasse festlegen zu müssen.

DataConsumer-Beispiel

Um einen beliebigen Actor zum DataConsumer zu machen, lässt sich beispielsweise eine abstrakte Klasse denken, die wie folgt angelegt ist.

eine abstrakte Basisklasse für DataConsumer
public abstract class ConsumerActor implements DataConsumer {

  protected List<DataProvider> provider = new ArrayList();

  public Object getData(String name) {
    Object o = null;
    Iterator<DataProvider> i = provider.iterator();
    while(o == null && i.hasNext()) {
      DataProvider p = i.next();
      o = p.getDataObject(name);
    }
    return o;
  }

  protected void clear() {
    provider.clear();
    provider = null;
  }

  @Override
  public void addDataProvider(DataProvider provider) {
    this.provider.add(provider);
  }

  @Override
  public void removeDataProvider(DataProvider provider) {
    this.provider.remove(provider);
  }
}

Ableitungen der Klasse ConsumerActor können so Daten einer Anwendung verwenden, ohne, dass bereits beim Entwurf der Anwendung im Code festgelegt werden muss, welche Daten ausgetauscht werden.

Beispiel für einen Actor, der als ConsumerActor fungiert
@Actor(name="meinAktor")
public class MeinActor extends ConsumerActor {

  @Action(handler={"einKontext"}, route="/gruss", type=Action.Type.GET, handlesResponse = false)
  public String run() {
    Object o = getData("user.name");
    if(o instanceof String) {
      gruss((String) o);
    }
    clear();
    return antwort;
  }

  private String gruss(String param) {
    return "Hallo " + param;
  }

}

Ein Szenario für obiges Beispiel könnte sein, dass der Aufruf von http://localhost:port/kontext/gruss stets die Ausgabe Hallo [Benutzername] produziert und für [Benutzername] der Name des angemeldeten Benutzers erscheint. Natürlich könnte der Benutzername in diesem Fall auch über ein Attribut des HTTP-Kontext dem Aktor zufließen. Allerdings könnte es sein, dass der betreffende HTTP-Kontext nichts vom Benutzer 'weiß' und die Angabe aus einem anderen Bereich der Anwendung kommen muss. Zudem kann auch der Name des Datenelements variabel bleiben und muss nicht mit dem Namen user.name wie im Beispiel hart codiert sein.

In diesen Fällen sind die Schnittstellen DataProvider und DataConsumer ein geeignetes Konstrukt.

Verschiedene Ports

Das Java-Modul jdk.httpserver, auf dem Neon beruht, sieht vor, für jeden Port ein Objekt der Klasse Server auszuführen. Beispielsweise ließe sich denken, dass eine Anwendung einige Funktionen öffentlich bereitstellt und gewisse andere Funktionen mit z.B. administrativer Natur nur über ein lokales Netzwerk zugänglich macht.

In diesem Fall kann eine Serverbeschreibung veranlassen, zwei Ports über HTTP nutzbar zu machen.

die Beschreibungsdatei eines Neon-Servers, der HTTP-Aufrufe über zwei Ports bereitstellt
{
  "contentType": "text/json",
  "contentId": "neon-descriptor-0.1.0",
  "contentDescription":"Neon Descriptor Version 0.1.0",
  "instanceName": "Mein Server",
  "instanceDescription": "Beispiel-Serverinstanz",
  "actorPackages": [
    "com.example.my.app",
    "com.example.my.other.library"
  ],
  "server": [
    {
      "name": "oeffentlicher-server",
      "port":7000,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "false",
          "contextPath": "/meine/app",
          "attributes": {
            "contextName": "pub"
          }
        }
      ]
    },
    {
      "name": "admin-server",
      "port":7001,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "false",
          "contextPath": "/meine/app",
          "attributes": {
            "contextName": "admin"
          }
        }
      ]
    }

  ]
}

Mit einer Serverbeschreibung wie im obigen Beispiel würde eine Anwendung HTTP-Anfragen über folgende URLs entgegen nehmen:

http://localhost:7000/meine/app
http://localhost:7001/meine/app

Über das Attribut contextName lassen sich unterschiedliche Actor-Objekte an die verschiedenen Ports binden.

Shared Handler

Mit der Eigenschaft sharedHandler kann dasselbe Handler-Objekt von verschiedenen HTTP-Kontexten genutzt werden. Sollen beispielsweise die Funktionen des öffentlichen Kontexts nicht nur über Port 7000 sondern auch über Port 7001 zugänglich sein, wird in der Serverbeschreibung ein zusätzlicher Kontext angelegt.

die Beschreibungsdatei eines Neon-Servers, der HTTP-Aufrufe über zwei Ports bereitstellt
{
  "contentType": "text/json",
  "contentId": "neon-descriptor-0.1.0",
  "contentDescription":"Neon Descriptor Version 0.1.0",
  "instanceName": "Mein Server",
  "instanceDescription": "Beispiel-Serverinstanz",
  "actorPackages": [
    "com.example.my.app",
    "com.example.my.other.library"
  ],
  "server": [
    {
      "name": "oeffentlicher-server",
      "port":7000,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "true",
          "contextPath": "/meine/app",
          "attributes": {
            "contextName": "pub"
          }
        }
      ]
    },
    {
      "name": "admin-server",
      "port":7001,
      "contexts": [
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "false",
          "contextPath": "/meine/app",
          "attributes": {
            "contextName": "admin"
          }
        },
        {
          "className": "de.uhilger.neon.Handler",
          "sharedHandler": "true",
          "contextPath": "/meine/app",
          "attributes": {
            "contextName": "pub"
          }
        }
      ]
    }

  ]
}

Damit werden alle Actor-Objekte, die dem Kontext pub zugeordnet sind, sowohl über Port 7000 als auch über Port 7001 zugänglich, ohne, dass ein weiterer Handler dafür erzeugt werden muss.

Neon stoppen

Eine laufende Java-Anwendung kann mit dem Befehl System.exit(0); gestoppt werden. Auf eine App mit eingebettem HTTP-Server angewendet beendet dies auch den in die Anwendung eingebetteten Server. Vor dem Beenden der Anwendung sollte der Server aber zuvor auch explizit erst gestoppt werden. Dies erlaubt ein geordnetes Herunterfahren von Server und Anwendung.

Eine Actor-Klasse kann diese Schritte in geeigneter Weise ausführen und zugleich über HTTP aufrufbar machen.

ein Actor zum Stoppen von Neon
@Actor(name="serverStopper")
public class ServerStopper {

  @Action(handler={"admin"}, route="/server/stop", type=Action.Type.GET, handlesResponse = false)
  public String run(HttpExchange exchange) {
    HttpContext adminContext = exchange.getContext();
    HttpServer server = adminContext.getServer();
    Timer timer = new Timer();
    if(server != null) {
      timer.schedule(new ServerStopperTask(server), 1);
    } else {
      Logger.getLogger(ServerStopper.class.getName()).log(Level.INFO, "server ist null");
    }
    timer.schedule(new AppStopperTask(), 1200);
    return "Server wird gestoppt.";
  }

  /**
   * Die Klasse ServerStopperTask erm&ouml;glicht das asnychrone bzw.
   * zeitgesteuerte Stoppen eines HttpServers.
   */
  class ServerStopperTask extends TimerTask {
    private final HttpServer server;
    private final int port;

    public ServerStopperTask(HttpServer server) {
      this.server = server;
      this.port = server.getAddress().getPort();
    }

    @Override
    public void run() {
      Logger.getLogger(ServerStopper.class.getName()).log(Level.INFO, "rufe server.stop fuer Port " + port);
      server.stop(1);
    }
  }

  /**
   * Die Klasse AppStopperTask erm&ouml;glicht das asnychrone bzw.
   * zeitgesteuerte Stoppen der Anwendung.
   */
  class AppStopperTask extends TimerTask {

    @Override
    public void run() {
      System.exit(0);
    }
  }
}

Die im Beispiel gezeigte Actor-Klasse verwendet in der Methode run den Parameter exchange, um den Server zu ermitteln, der gestoppt werden soll. Mit Hilfe eines Timers wird ein TimerTask eingeplant, der diesem Server ein Stopp-Signal gibt.

Der Aufruf server.stop(1) im TimerTask bewirkt, dass die Socket-Verbindung des Servers geschlossen wird und damit keine weiteren HTTP-Anfragen mehr entgegen genommen und keine HTTP-Exchange-Objekte mehr vom Server erzeugt werden. Der Aufruf blockiert die Ausführung anderer Operationen im Thread dieses TimerTasks bis alle noch bestehenden exchange handler des Servers ausgeführt wurden oder eine Sekunde verstrichen ist, was immer früher eintritt. Dann werden alle offenen TCP-Verbindungen geschlossen und der Hintergrund-Thread des Servers, d.h. die Methode start() des Servers, endet. Auf diese Weise gestoppt kann der Server nicht wieder gestartet werden.

Mit dem Timer wird zugleich ein weiterer TimerTask eingeplant, der 1,2 Sekunden auf diese Schritte wartet und dann die Anwendung beendet.

Mehrere Server beenden

Macht eine Anwendung mehrere Ports auf, wie in Verschiedene Ports beschrieben, sollte jeder Server beendet werden, bevor die Anwendung gestoppt wird. Dies kann erreicht werden, indem mit Hilfe eines FactoryListener beim Start Referenzen auf jeden weiteren Server als Attribut im Kontext des Stopp-Actors hinterlegt werden.

Der Actor aus Neon stoppen kann diese Referenzen nutzen, um alle Server zu stoppen.

Voraussetzungen, Lizenz

Zur Verwendung von Neon wird die Datei neon.jar dem Classpath der Anwendung beigefügt. Zudem sind Java [3] und Gson [4] erforderlich.

Neon wird unter den Bedingungen der GNU Affero General Public License [6] bereitgestellt.

Änderungsverlauf

Version 1

Juni 2021

Version 2

Februar 2024

Verweise

[1] Produktseite von Neon

[2] Übersicht von Modulen für Neon

[3] Java auf der Webseite von AdoptOpenJDK

[4] Gson