Ultrakompakter HTTP Server
ca79593d7659cd9c6dcdd38692888b26dcbef4f5..c2e8cfdbc73a49939145f98e503ade4169ed508c
2024-11-30 ulrich
Lizenzheader ergaenzt
c2e8cf diff | tree
2024-11-30 ulrich
Fix: wireActors, wenn App als JAR laeuft
f4025a diff | tree
1 files added
3 files modified
285 ■■■■ changed files
src/de/uhilger/neon/Factory.java 77 ●●●●● patch | view | raw | blame | history
src/de/uhilger/neon/Handler.java 50 ●●●● patch | view | raw | blame | history
src/de/uhilger/neon/JarScanner.java 156 ●●●●● patch | view | raw | blame | history
src/de/uhilger/neon/RangeGroup.java 2 ●●● patch | view | raw | blame | history
src/de/uhilger/neon/Factory.java
@@ -23,6 +23,7 @@
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;
@@ -35,6 +36,9 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -42,6 +46,8 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * Einen Neon-Server aus einer Beschreibungsdatei herstellen
@@ -59,7 +65,7 @@
 * @author Ulrich Hilger
 * @version 1, 6.2.2024
 */
public class Factory {
public class Factory implements JarScannerListener {
  public Factory() {
    listeners = new ArrayList<>();
@@ -89,16 +95,16 @@
    return gson.fromJson(sb.toString(), NeonDescriptor.class);
  }
  
  public void runInstance(NeonDescriptor d)
  public void runInstance(Class c, NeonDescriptor d)
          throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
          IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
    this.runInstance(d, null, new ArrayList<>());
    this.runInstance(c, d, null, new ArrayList<>());
  }
  public void runInstance(NeonDescriptor d, List<String> packageNames)
  public void runInstance(Class c, NeonDescriptor d, List<String> packageNames)
          throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
          IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
    this.runInstance(d, packageNames, new ArrayList<>());
    this.runInstance(c, d, packageNames, new ArrayList<>());
  }
  /**
   * Einen Neon-Server gemaess einem Serverbeschreibungsobjekt herstellen und starten
@@ -115,7 +121,7 @@
   * @throws InvocationTargetException
   * @throws IOException
   */
  public void runInstance(NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp)
  public void runInstance(Class c, NeonDescriptor d, List<String> packageNames, List<DataProvider> sdp)
          throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
          IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
    List serverList = d.server;
@@ -128,7 +134,7 @@
      if(packageNames == null) {
        packageNames = d.actorPackages;
      }
      addContexts(d, server, sd.contexts, packageNames, sdp);
      addContexts(c, d, server, sd.contexts, packageNames, sdp);
      server.setExecutor(Executors.newFixedThreadPool(10));
      server.start();
@@ -156,7 +162,7 @@
    return auth;
  }
  private void addContexts(NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames,
  private void addContexts(Class c, NeonDescriptor d, HttpServer server, List contextList, List<String> packageNames,
          List<DataProvider> sdp) 
          throws ClassNotFoundException, NoSuchMethodException, InstantiationException, 
          IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
@@ -176,7 +182,7 @@
        ctxAttrs.putAll(cd.attributes);        
        if (h instanceof Handler) {         
          for (String packageName : packageNames) {
            wireActors(
            wireActors(c,
                    packageName, Actor.class, (Handler) h, 
                    cd.attributes.get("contextName"));
              ctx.getAttributes().put("serverDataProviderList", sdp);
@@ -254,27 +260,39 @@
    }
  }
  private void wireActors(String packageName, Class annotation, Handler h, String contextName) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    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 = Class.forName(packageName + "."
                  + line.substring(0, line.lastIndexOf('.')));
          if (actorClass != null && actorClass.isAnnotationPresent(annotation)) {
            wire(h, actorClass, contextName);
  private void wireActors(Class c, String packageName, Class annotation, Handler h, String contextName) {
    JarScanner js = new JarScanner();
    URI path;
    try {
      path = js.getPath(c);
      if(path.toString().endsWith(".class")) {
        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);
          }
        } catch (ClassNotFoundException ex) {
          // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
        }
      } else {
        wireActors(packageName + "." + line, annotation, h, contextName);
        ClassLoader cl = js.getUrlClassLoader(c);
        js.processZipContent(cl, new File(path), packageName, this, h, contextName);
      }
    } catch (URISyntaxException ex) {
      Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
    }
  }
  
@@ -346,4 +364,11 @@
      l.instanceStarted();
    }
  }
  /* -------------- JarScannerListener Implementierung --------------- */
  @Override
  public void actorFound(Class actorClass, Handler h, String contextName) {
    wire(h, actorClass, contextName);
  }
}
src/de/uhilger/neon/Handler.java
@@ -22,6 +22,7 @@
import de.uhilger.neon.Action.Type;
import de.uhilger.neon.entity.ActionDescriptor;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@@ -30,6 +31,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
 * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen NeonActor enthalten.
@@ -91,7 +94,17 @@
    //Logger.getLogger(Handler.class.getName())
    //        .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className});
    dispatcher.get(methodType).put(ad.route, ad);
    //dispatcher.get(methodType).put(ad.route, ad);
    Object adMapObj = dispatcher.get(methodType);
    if(adMapObj instanceof HashMap hashMap) {
      @SuppressWarnings("unchecked")
      HashMap<String, ActionDescriptor> map = hashMap;
      map.put(ad.route, ad);
      Logger.getLogger(Handler.class.getName()).log(Level.FINER, "ActionDescriptor route {0} className {1}", new Object[]{route, className});
    } else {
      Logger.getLogger(Handler.class.getName()).finer("ActionDescriptorMap nicht gefunden");
    }
  }
  /**
@@ -135,8 +148,9 @@
                    .getHttpContext()
                    .getPath()
                    .length());            
    Type requestMethod = Type.valueOf(exchange.getRequestMethod());
    String requestMethodStr = exchange.getRequestMethod();
    Logger.getLogger(Handler.class.getName()).log(Level.FINER, "method {0} route {1}", new Object[]{requestMethodStr, route});
    Type requestMethod = Type.valueOf(requestMethodStr);
    /*
      Es wird erst geprueft, ob zu einer bestimmten Route 
      ein Actor registriert wurde. Wenn kein Actor mit dieser 
@@ -147,10 +161,12 @@
    Object md = dispatcher.get(requestMethod);
    if (md instanceof Map) {
      int pos = route.lastIndexOf("/");
      Logger.getLogger(Handler.class.getName()).log(Level.FINER, "pos {0}", pos);
      Object o = ((Map) md).get(route);
      if (!(o instanceof ActionDescriptor)) {
        while (!found && (pos > -1)) {
          String routeRest = route.substring(0, pos);
          Logger.getLogger(Handler.class.getName()).log(Level.FINER, "pos {0} routeRest {1}", new Object[]{pos, routeRest});
          o = ((Map) md).get(routeRest);
          if (o instanceof ActionDescriptor) {
            found = true;
@@ -163,16 +179,24 @@
        handleRequest(exchange, o, route, route, requestMethod);
      }
      if (!found) {
        Logger.getLogger(Handler.class.getName()).log(Level.FINER, "{0} not found ", route);
        o = dispatcher.get(requestMethod).get("/");
        if (o instanceof ActionDescriptor) {
          handleRequest(exchange, o, route, route, requestMethod);
        } else {
          // kein ActionDescriptor für '/'
          Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Kein Actiondescriptor fuer '/'");
        }
      }
    } else {
      // keine Actions fuer HTTP Methode
      Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Kein Actions fuer HTTP-Methode {0}", requestMethodStr);
    }
  }
  private void handleRequest(HttpExchange exchange, Object o, String route, String subroute, Type requestMethod) throws IOException {
    Logger.getLogger(Handler.class.getName()).log(Level.FINER, "Handle Request route {0} subroute {1}", new Object[]{route, subroute});
    ActionDescriptor ad = (ActionDescriptor) o;
    String actorClassName = ad.className;
    try {
@@ -183,11 +207,20 @@
        if (action != null) {
          if ((action.route().equals("/") || action.route().startsWith(route)) && action.type().equals(requestMethod)) {
            Object[] actionArgs = getActionArgs(exchange, method, ad, subroute);
            Object actorObj = actorClass.getDeclaredConstructor().newInstance();
            addDataProvider(exchange, actorObj);
            Object antwort = method.invoke(actorObj, actionArgs);
            if (!action.handlesResponse()) {
              respond(exchange, antwort);
            @SuppressWarnings("unchecked")
            Object conObj = actorClass.getDeclaredConstructor();
            if(conObj instanceof Constructor) {
              Constructor con = (Constructor) conObj;
              Object actorObj;
              actorObj = con.newInstance();
              addDataProvider(exchange, actorObj);
              Object antwort = method.invoke(actorObj, actionArgs);
              if (!action.handlesResponse()) {
                respond(exchange, antwort);
              }
            } else {
              // kein Konstruktor
              Logger.getLogger(Handler.class.getName()).info("Kein Konstruktor gefunden");
            }
          }
        }
@@ -196,6 +229,7 @@
            InstantiationException | IllegalAccessException | IllegalArgumentException | 
            InvocationTargetException ex) {
      // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
      Logger.getLogger(Handler.class.getName()).finer("Kein passende Actor-Klasse gefunden");
    }
    //}
  }
src/de/uhilger/neon/JarScanner.java
New file
@@ -0,0 +1,156 @@
/*
  neon - Embeddable HTTP Server based on jdk.httpserver
  Copyright (C) 2024  Ulrich Hilger
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as
  published by the Free Software Foundation, either version 3 of the
  License, or (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.
  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package de.uhilger.neon;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
 * Die Klasse JarScanner enthaelt Methoden, um fuer eine Klasse zu bestimmen, in
 * welcher JAR-Datei sie liegt und diese JAR-Datei nach Klassen zu durchsuchen.
 *
 * @author Ulrich Hilger
 * @version 0.1, 30.11.2024
 */
public class JarScanner {
  public void processZipContent(ClassLoader urlCL, File archive, String packageName, JarScannerListener l, Handler h, String contextName) {
    try {
      ZipFile zipfile = new ZipFile(archive);
      Enumeration en = zipfile.entries();
      while (en.hasMoreElements()) {
        ZipEntry zipentry = (ZipEntry) en.nextElement();
        if (!zipentry.isDirectory()) {
          processZipEntry(urlCL, zipentry, packageName, l, h, contextName);
        } else {
          // ZIP-Dir muss nicht bearbeitet werden
        }
      }
      zipfile.close();
    } catch (IOException ex) {
      log(Level.SEVERE, ex.getLocalizedMessage());
    }
  }
  private void processZipEntry(ClassLoader urlCL, ZipEntry zipentry, String packageName, JarScannerListener l, Handler h, String contextName) {
    log(Level.FINEST, zipentry.getName());
    String zName = zipentry.getName();
    if (zName.toLowerCase().endsWith(".class")) {
      int pos = zName.indexOf(".class");
      String fullClassName = zName.substring(0, pos);
      log(Level.FINEST, "full class name: " + zName);
      String fullClassNameDots = fullClassName.replace('/', '.');
      log(Level.FINEST, "full class name dots: " + fullClassNameDots);
      String pkgName = getPackageName(fullClassNameDots);
      log(Level.FINEST, " -- package name: " + pkgName);
      if (null != urlCL && pkgName.toLowerCase().startsWith(packageName)) {
        Class c = null;
        try {
          c = urlCL.loadClass(fullClassNameDots);
          if (c != null) {
            if (c.isAnnotationPresent(Actor.class)) {
              log(Level.FINER, " ---- ACTOR ---- " + fullClassNameDots);
              l.actorFound(c, h, contextName);
            } else {
              log(Level.FINER, "kein Actor " + fullClassNameDots);
            }
          } else {
            log(Level.FINER, "class NOT loaded: " + zName);
          }
        } catch (ClassNotFoundException ex) {
          log(Level.FINER, " +++++ Class not found: " + ex.getMessage());
        }
      }
    }
  }
  private String getPackageName(String fullClassName) {
    String packageName;
    int pos = fullClassName.lastIndexOf(".");
    if (pos > 0) {
      packageName = fullClassName.substring(0, pos);
    } else {
      packageName = fullClassName;
    }
    return packageName;
  }
  public ClassLoader getUrlClassLoader(Class c) {
    URL url;
    ClassLoader urlCL = null;
    try {
      url = getPath(c).toURL();
      log(Level.FINER, "url: " + url.getPath());
      urlCL = new URLClassLoader(new URL[]{url});
    } catch (URISyntaxException ex) {
      log(Level.SEVERE, ex.getMessage());
    } catch (MalformedURLException ex) {
      log(Level.SEVERE, ex.getMessage());
    } finally {
      return urlCL;
    }
  }
  public URI getPath(Class c) throws URISyntaxException {
    //Class c = this.getClass();
    String className = c.getName();
    finer("this name: " + className);
    int pos = className.indexOf(".class");
    if(pos > -1) {
      String classNameWoExt = className.substring(0, pos);
    }
    String classNameWoPkg = className.substring(className.lastIndexOf(".") + 1);
    finer("Class name: " + classNameWoPkg);
    String classPath = c.getResource(classNameWoPkg + ".class").getPath();
    pos = classPath.indexOf("!");
    String jarPath;
    if(pos > -1) {
      jarPath = /*"jar:" + */ classPath.substring(0, pos);
    } else {
      jarPath = classPath;
    }
    finer("path: " + jarPath);
    return new URI(jarPath);
  }
  private void finer(String msg) {
    log(Level.FINER, msg);
  }
  private void log(Level l, String msg) {
    Logger.getLogger(JarScanner.class.getName()).log(l, msg);
  }
  public interface JarScannerListener {
    public void actorFound(Class actorClass, Handler h, String contextName);
  }
}
src/de/uhilger/neon/RangeGroup.java
@@ -35,7 +35,7 @@
     * Ein neues Objekt der Klasse RangeGroup erzeugen
     */
    public RangeGroup() {
      ranges = new ArrayList();
      ranges = new ArrayList<>();
    }
    /**