Ultrakompakter HTTP Server
ulrich
2024-12-05 7189ff3d683f97870fc11ec4e14de3f23abe3c4b
src/de/uhilger/neon/Factory.java
@@ -17,13 +17,13 @@
 */
package de.uhilger.neon;
import de.uhilger.neon.entity.ActorDescriptor;
import com.google.gson.Gson;
import com.sun.net.httpserver.Authenticator;
import com.sun.net.httpserver.Filter;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import de.uhilger.neon.JarScanner.JarScannerListener;
import de.uhilger.neon.entity.ContextDescriptor;
import de.uhilger.neon.entity.NeonDescriptor;
import de.uhilger.neon.entity.ServerDescriptor;
@@ -48,6 +48,8 @@
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.uhilger.neon.Scanner.ScannerListener;
import java.util.Set;
/**
 * Einen Neon-Server aus einer Beschreibungsdatei herstellen
@@ -65,10 +67,13 @@
 * @author Ulrich Hilger
 * @version 1, 6.2.2024
 */
public class Factory implements JarScannerListener {
public class Factory implements ScannerListener {
  private Map<String, List<ActorDescriptor>> actorMap;
  public Factory() {
    listeners = new ArrayList<>();
    actorMap = new HashMap<>();
  }
  /**
@@ -129,17 +134,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;
      }
      Scanner scn = new Scanner(starter, Actor.class);
      for (String packageName : packageNames) {
        scn.process(this, packageName, new Object[]{});
        // ctx.getAttributes().put("serverDataProviderList", sdp);
      }
      addContexts(starter, d, server, sd.contexts, packageNames, sdp);
      addContexts(d, server, sd.contexts, sdp);
      server.setExecutor(Executors.newFixedThreadPool(10));
      server.start();
@@ -167,11 +177,11 @@
    return auth;
  }
  private void addContexts(Class c, 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,16 +192,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) {
            wireActors(c,
                    packageName, Actor.class, (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)) {
@@ -202,22 +208,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);
            }
          }
@@ -262,36 +259,6 @@
            | InvocationTargetException ex) {
      // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
      return null;
    }
  }
  @SuppressWarnings("unchecked")
  private void wireActors(Class c, String packageName, Class annotation, Handler h, String contextName) {
    JarScanner js = new JarScanner(c, annotation);
    if (!js.isJar()) {
      ClassLoader cl = c.getClassLoader();
      InputStream stream = cl
              .getResourceAsStream(packageName.replaceAll("[.]", "/"));
      BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
      Iterator i = reader.lines().iterator();
      while (i.hasNext()) {
        String line = i.next().toString();
        if (line.endsWith(".class")) {
          try {
            Class actorClass = cl.loadClass(packageName + "."
                    + line.substring(0, line.lastIndexOf('.')));
            if (actorClass != null && actorClass.isAnnotationPresent(annotation)) {
              wire(h, actorClass, contextName);
            }
          } catch (ClassNotFoundException ex) {
            // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
          }
        } else {
          wireActors(c, packageName + "." + line, annotation, h, contextName);
        }
      }
    } else {
      js.processZipContent(packageName, this, h, contextName);
    }
  }
@@ -362,22 +329,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<ActorDescriptor> actorList = actorMap.get(contextName);
    Iterator<ActorDescriptor> i = actorList.iterator();
    while(i.hasNext()) {
      ActorDescriptor actor = i.next();
      h.setActor(actor.getHttpMethod(), actor.getRoute(), actor.getActorClassName());
    }
  }
@@ -395,6 +372,8 @@
  public void destroy() {
    this.listeners.clear();
    this.listeners = null;
    this.actorMap.clear();
    this.actorMap = null;
  }
  private void fireServerCreated(HttpServer server) {
@@ -427,9 +406,29 @@
    }
  }
  /* -------------- JarScannerListener Implementierung --------------- */
  /* -------------- 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) {
           ActorDescriptor tempActor = new ActorDescriptor();
           tempActor.setContextName(contextName);
           tempActor.setHttpMethod(action.type());
           tempActor.setRoute(action.route());
           tempActor.setActorClassName(foundClass.getName());
           List<ActorDescriptor> actorList = actorMap.get(contextName);
           if(actorList == null) {
             actorList = new ArrayList<>();
           }
           actorList.add(tempActor);
           actorMap.put(contextName, actorList);
        }
      }
    }
  }
}