Ultrakompakter HTTP Server
ulrich
2024-12-03 cc007e5339f7ffc35cdd9b94ce3b712596a1494e
src/de/uhilger/neon/Factory.java
@@ -67,8 +67,11 @@
 */
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) {
@@ -398,19 +407,26 @@
  /* -------------- ScannerListener Implementierung --------------- */
  @Override
  public void annotationFound(Class foundClass, Object[] params) {
    Handler h = null;
    String contextName = null;
    for(Object param : params) {
      if(param instanceof Handler) {
        h = (Handler) param;
      } else if(param instanceof String) {
        contextName = (String) param;
    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);
      }
    }
    if(h == null || contextName == null) {
      Logger.getLogger(Factory.class.getName()).log(Level.FINER, "Handler oder contextName ist null");
    } else {
      wire(h, foundClass, contextName);
    }
  }
}