Ultrakompakter HTTP Server
1 files added
3 files modified
222 ■■■■■ changed files
src/de/uhilger/neon/Factory.java 105 ●●●●● patch | view | raw | blame | history
src/de/uhilger/neon/Handler.java 8 ●●●● patch | view | raw | blame | history
src/de/uhilger/neon/Scanner.java 58 ●●●●● patch | view | raw | blame | history
src/de/uhilger/neon/TempActor.java 51 ●●●●● patch | view | raw | blame | history
src/de/uhilger/neon/Factory.java
@@ -66,9 +66,12 @@
 * @version 1, 6.2.2024
 */
public class Factory implements ScannerListener {
  private Map<String, List<TempActor>> actorMap;
  public Factory() {
    listeners = new ArrayList<>();
    actorMap = new HashMap<>();
  }
  /**
@@ -129,17 +132,22 @@
    Logger.getLogger(Factory.class.getName()).log(Level.FINER, System.getProperty("java.class.path"));
    List serverList = d.server;
    Iterator<ServerDescriptor> serverIterator = serverList.iterator();
    while (serverIterator.hasNext()) {
      ServerDescriptor sd = serverIterator.next();
    List<ServerDescriptor> serverList = d.server;
    for (ServerDescriptor sd : serverList) {
      HttpServer server = HttpServer.create(new InetSocketAddress(sd.port), 0);
      fireServerCreated(server);
      if (packageNames == null) {
        packageNames = d.actorPackages;
      }      
      addContexts(new Scanner(starter, Actor.class), d, server, sd.contexts, packageNames, sdp);
      Scanner scn = new Scanner(starter, Actor.class);
      for (String packageName : packageNames) {
        scn.process(this, packageName, new Object[]{});
        // ctx.getAttributes().put("serverDataProviderList", sdp);
      }
      addContexts(d, server, sd.contexts, sdp);
      server.setExecutor(Executors.newFixedThreadPool(10));
      server.start();
@@ -167,11 +175,11 @@
    return auth;
  }
  private void addContexts(Scanner scn, NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames,
  private void addContexts(NeonDescriptor d, HttpServer server, List<ContextDescriptor> contextList,
          List<DataProvider> sdp)
          throws ClassNotFoundException, NoSuchMethodException, InstantiationException,
          IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
    Map<String, HttpHandler> sharedHandlers = new HashMap();
    Map<String, HttpHandler> sharedHandlers = new HashMap<>();
    Iterator<ContextDescriptor> contextIterator = contextList.iterator();
    Authenticator auth = null;
    while (contextIterator.hasNext()) {
@@ -182,14 +190,12 @@
        Map<String, Object> ctxAttrs = ctx.getAttributes();
        /*
          Achtung: Wenn verschiedene Elemente dasselbe Attribut 
          deklarieren, ueberschreiben sie sich die Attribute gegenseitig.
          deklarieren, ueberschreiben sich die Attribute gegenseitig.
         */
        ctxAttrs.putAll(cd.attributes);
        if (h instanceof Handler) {
          for (String packageName : packageNames) {
            scn.process(this, packageName, (Handler) h, cd.attributes.get("contextName"));
            ctx.getAttributes().put("serverDataProviderList", sdp);
          }
        ctxAttrs.put("serverDataProviderList", sdp);
        if (h instanceof Handler handler) {
          wire(handler, cd.attributes.get("contextName"));
        }
        if (cd.authenticator instanceof String) {
          if (!(auth instanceof Authenticator)) {
@@ -200,22 +206,13 @@
            ctx.getAttributes().putAll(d.authenticator.attributes);
            fireAuthenticatorCreated(ctx, auth); // event umbenennen in etwas wie authAdded oder so
          }
        }
        //Authenticator auth = createAuthenticator(d);
        //if (auth instanceof Authenticator && cd.authenticator instanceof String) {
        //    ctx.setAuthenticator(auth);
        //    ctx.getAttributes().putAll(d.authenticator.attributes);
        //    fireAuthenticatorCreated(ctx, auth);
        //}
        if (cd.filter != null) {
          for (String filterClassName : cd.filter) {
            //
            Object filterObj = Class.forName(filterClassName)
                    .getDeclaredConstructor().newInstance();
            if (filterObj instanceof Filter) {
              Filter filter = (Filter) filterObj;
            if (filterObj instanceof Filter filter) {
              ctx.getFilters().add(filter);
            }
          }
@@ -330,22 +327,32 @@
    Kontextpfades ausgefuehrt werden soll, muss die Action 
    als Route '/' angeben.
   */
  /*
   Tradeoff:
      Es muss bei Initialisierung die Actor-Klasse ganz durchlaufen werden, um alle Methoden
      zu finden, die eine Action-Annotation haben. Der Handler 'merkt' sich lediglich den Namen der
      Actor-Klassen. Daher muessen bei jedem Aufruf eines Actors ueber den Handler abermals
      alle Methoden dieses Actors durchsucht werden, um diejenige Methode zu finden, die mit
      der zur Route des Requests passenden Action annotiert ist.
      Dieser Tradeoff bewirkt, dass nicht grosse Geflechte aus Klassen- und Methodenobjekten
      fuer die gesamten Actors einer Anwendung im Speicher gehalten werden muessen
      sondern dynamisch zur Laufzeit instanziiert werden.
  */
  /**
   *
   * Actor-Klassen dem Handler hinzufuegen
   *
   * @param h Handler, dem der Actor hinzugefuegt wird, falls der Kontext uebereinstimmt
   * @param c hinzuzufuegende Actor-Klasse
   * @param contextName Name des Kontext, dem der Actor hinzugefuegt wird
   * @param contextName Name des Kontext, dem der Handler zugeordnet ist
   */
  private void wire(Handler h, Class c, String contextName) {
    Method[] methods = c.getMethods();
    for (Method method : methods) {
      Action action = method.getAnnotation(Action.class);
      if (action != null) {
        List actionHandlers = Arrays.asList(action.handler());
        if (actionHandlers.contains(contextName)) {
          h.setActor(action.type(), action.route(), c.getName());
        }
      }
  private void wire(Handler h, String contextName) {
    List<TempActor> actorList = actorMap.get(contextName);
    Iterator<TempActor> i = actorList.iterator();
    while(i.hasNext()) {
      TempActor actor = i.next();
      h.setActor(actor.getHttpMethod(), actor.getRoute(), actor.getActorClassName());
    }
  }
@@ -363,6 +370,8 @@
  public void destroy() {
    this.listeners.clear();
    this.listeners = null;
    this.actorMap.clear();
    this.actorMap = null;
  }
  private void fireServerCreated(HttpServer server) {
@@ -397,7 +406,27 @@
  /* -------------- ScannerListener Implementierung --------------- */
  @Override
  public void annotationFound(Class foundClass, Handler h, String contextName) {
    wire(h, foundClass, contextName);
  public void annotationFound(Class foundClass, Object[] params) {
    Method[] methods = foundClass.getMethods();
    for (Method method : methods) {
      Action action = method.getAnnotation(Action.class);
      if (action != null) {
        List<String> actionHandlers = Arrays.asList(action.handler());
        for (String contextName : actionHandlers) {
           TempActor tempActor = new TempActor();
           tempActor.setContextName(contextName);
           tempActor.setHttpMethod(action.type());
           tempActor.setRoute(action.route());
           tempActor.setActorClassName(foundClass.getName());
           List<TempActor> actorList = actorMap.get(contextName);
           if(actorList == null) {
             actorList = new ArrayList<>();
           }
           actorList.add(tempActor);
           actorMap.put(contextName, actorList);
        }
      }
    }
  }
}
src/de/uhilger/neon/Handler.java
@@ -35,11 +35,11 @@
import java.util.logging.Logger;
/**
 * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen NeonActor enthalten.
 * Deren mit NeonMethod annotierten Methoden stellt der Handler via HTTP bereit.
 * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen Actor enthalten.
 * Deren mit Action annotierten Methoden stellt der Handler via HTTP bereit.
 *
 * Wird ein Neon-Server mit der Klasse NeonFactory erzeugt, kann mit der Verwendung dieses Handlers
 * die NeonFactory den Server selbsttaetig erstellen, ohne zusaetzlichen Boilerplate Code, den eine
 * Wird ein Neon-Server mit der Klasse Factory erzeugt, kann mit der Verwendung dieses Handlers
 * die Factory den Server selbsttaetig erstellen, ohne zusaetzlichen Boilerplate Code, den eine
 * eigene Anwendung mitbringen muesste.
 *
 * @author Ulrich Hilger
src/de/uhilger/neon/Scanner.java
@@ -70,7 +70,7 @@
  public Class getAnnotation() {
    return annotation;
  }
  /**
   * Klassen suchen, die die dem Konstruktor gegebene Annotation besitzen.
   *
@@ -81,28 +81,17 @@
   * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
   * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
   * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
   * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
   * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
   * @param params Parameter, die an den Listener weitergereicht werden
   */
  public void process(ScannerListener l, String packageName, Handler h, String contextName) {
  public void process(ScannerListener l, String packageName, Object... params) {
    if (isJar()) {
      processZipContent(packageName, l, h, contextName);
      processZipContent(packageName, l, params);
    } else {
      processClasses(l, packageName, h, contextName);
      processClasses(l, packageName, params);
    }
  }
  /**
   * Den Inhalt einer Jar-Datei nach Klassen durchsuchen, die die dem Konstruktor gegebene
   * Annotation besitzen.
   *
   * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
   * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
   * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
   * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
   * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
   */
  private void processZipContent(String packageName, ScannerListener l, Handler h, String contextName) {
  private void processZipContent(String packageName, ScannerListener l, Object... params) {
    try {
      ZipFile zipfile = new ZipFile(new File(path));
      Enumeration en = zipfile.entries();
@@ -110,7 +99,7 @@
      while (en.hasMoreElements()) {
        ZipEntry zipentry = (ZipEntry) en.nextElement();
        if (!zipentry.isDirectory()) {
          processZipEntry(zipentry, packageName, l, h, contextName);
          processZipEntry(zipentry, packageName, l, params);
        } else {
          // ZIP-Dir muss nicht bearbeitet werden
        }
@@ -119,9 +108,17 @@
      log(Level.SEVERE, ex.getLocalizedMessage());
    }
  }
  @SuppressWarnings("unchecked")
  private void processZipEntry(ZipEntry zipentry, String packageName, ScannerListener l, Handler h, String contextName) {
  /**
   * Den Inhalt einer Jar-Datei nach Klassen durchsuchen, die die dem Konstruktor gegebene
   * Annotation besitzen.
   *
   * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
   * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
   * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
   * @param params Parameter, die an den Listener weitergereicht werden
   */
  private void processZipEntry(ZipEntry zipentry, String packageName, ScannerListener l, Object... params) {
    finest(zipentry.getName());
    String zName = zipentry.getName();
    if (zName.toLowerCase().endsWith(".class")) {
@@ -138,7 +135,7 @@
          if (c != null) {
            if (c.isAnnotationPresent(annotation)) {
              finest(" ---- ACTOR ---- " + fullClassNameDots);
              l.annotationFound(c, h, contextName);
              l.annotationFound(c, params);
            } else {
              finest("kein Actor " + fullClassNameDots);
            }
@@ -151,18 +148,16 @@
      }
    }
  }
  /**
   * Einen Ordner mit Klassen durchsuchen
   *
   * @param packageName Name der Package, die einschl. Unterpackages durchsucht wird, nur Klassen
   * dieser Package und ihrer Unterpackages werden geladen und auf die Anotation ueberprueft
   * @param l ein Objekt, das verstaendigt wird, wenn eine annotierte Klasse gefunden wurde
   * @param h der Handler, dem die gefundene Klasse hinzugefuegt werden soll
   * @param contextName Name des Kontext, dem gefundene Klassen hinzugefuegt werden sollen
   * @param params Parameter, die an den Listener weitergereicht werden
   */
  @SuppressWarnings("unchecked")
  private void processClasses(ScannerListener l, String packageName, Handler h, String contextName) {
  private void processClasses(ScannerListener l, String packageName, Object... params) {
    ClassLoader cl = getCl();
    InputStream stream = cl.getResourceAsStream(packageName.replaceAll("[.]", "/"));
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
@@ -175,18 +170,18 @@
                  + line.substring(0, line.lastIndexOf('.')));
          if (actorClass != null && actorClass.isAnnotationPresent(getAnnotation())) {
            //wire(h, actorClass, contextName);
            l.annotationFound(actorClass, h, contextName);
            l.annotationFound(actorClass, params);
          }
        } catch (ClassNotFoundException ex) {
          // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
        }
      } else {
        //wireActors(js, packageName + "." + line, h, contextName);
        processClasses(l, packageName + "." + line, h, contextName);
        processClasses(l, packageName + "." + line, params);
      }
    }
  }
  private String getPackageName(String fullClassName) {
    String packageName;
    int pos = fullClassName.lastIndexOf(".");
@@ -262,7 +257,6 @@
  }
  public interface ScannerListener {
    public void annotationFound(Class foundClass, Handler h, String contextName);
    public void annotationFound(Class foundClass, Object... params);
  }
}
src/de/uhilger/neon/TempActor.java
New file
@@ -0,0 +1,51 @@
package de.uhilger.neon;
import de.uhilger.neon.Action.Type;
/**
 *
 * @author Ulrich Hilger
 * @version 0.1, 03.12.2024
 */
public class TempActor {
  private String contextName;
  private Type httpMethod;
  private String route;
  private String actorClassName;
  public String getContextName() {
    return contextName;
  }
  public void setContextName(String contextName) {
    this.contextName = contextName;
  }
  public Type getHttpMethod() {
    return httpMethod;
  }
  public void setHttpMethod(Type httpMethod) {
    this.httpMethod = httpMethod;
  }
  public String getRoute() {
    return route;
  }
  public void setRoute(String route) {
    this.route = route;
  }
  public String getActorClassName() {
    return actorClassName;
  }
  public void setActorClassName(String actorClassName) {
    this.actorClassName = actorClassName;
  }
}