Ultrakompakter HTTP Server
ulrich
2024-02-21 5ebab9058498312ad234226040f0ec506509e476
src/de/uhilger/neon/Handler.java
@@ -20,6 +20,7 @@
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import de.uhilger.neon.Action.Type;
import de.uhilger.neon.entity.ActionDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -31,13 +32,12 @@
import java.util.Map;
/**
 * 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 NeonActor enthalten.
 * Deren mit NeonMethod 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 eigene Anwendung mitbringen muesste.
 * 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
 * eigene Anwendung mitbringen muesste.
 *
 * @author Ulrich Hilger
 * @version 1, 6.2.2024
@@ -51,10 +51,14 @@
   */
  public Handler() {
    dispatcher = new EnumMap<>(Type.class);
    dispatcher.put(Type.GET, new HashMap<String, String>());
    dispatcher.put(Type.PUT, new HashMap<String, String>());
    dispatcher.put(Type.POST, new HashMap<String, String>());
    dispatcher.put(Type.DELETE, new HashMap<String, String>());
    //dispatcher.put(Type.GET, new HashMap<String, String>());
    //dispatcher.put(Type.PUT, new HashMap<String, String>());
    //dispatcher.put(Type.POST, new HashMap<String, String>());
    //dispatcher.put(Type.DELETE, new HashMap<String, String>());
    dispatcher.put(Type.GET, new HashMap<String, ActionDescriptor>());
    dispatcher.put(Type.PUT, new HashMap<String, ActionDescriptor>());
    dispatcher.put(Type.POST, new HashMap<String, ActionDescriptor>());
    dispatcher.put(Type.DELETE, new HashMap<String, ActionDescriptor>());
  }
  /**
@@ -66,9 +70,28 @@
   * ausgefuehrt werden soll
   */
  public void setActor(Type methodType, String route, String className) {
    ActionDescriptor ad = new ActionDescriptor();
    ad.className = className;
    ad.routeParams = new HashMap<>();
    int pos = route.indexOf("{");
    if (pos > -1) {
      String paramStr = route.substring(pos);
      String[] params = paramStr
              .replaceAll("\\{", "")
              .replaceAll("\\}", "")
              .split("/");
      for (int i = 0; i < params.length; i++) {
        ad.routeParams.put(params[i], i);
      }
      ad.route = route.substring(0, pos - 1);
    } else {
      // Map kann leer bleiben
      ad.route = route;
    }
    //Logger.getLogger(Handler.class.getName())
    //        .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className});
    dispatcher.get(methodType).put(route, className);
    dispatcher.get(methodType).put(ad.route, ad);
  }
  /**
@@ -105,69 +128,80 @@
   */
  @Override
  public void handle(HttpExchange exchange) throws IOException {
    HttpHelper hh = new HttpHelper();
    HttpHelper hh = new HttpHelper();
    String route = hh.getRouteString(exchange);
    Type requestMethod = Type.valueOf(exchange.getRequestMethod());
    //Map queryParams = hh.getQueryMap(exchange);
    //Object o;
    /*
      Es wird erst geprueft, ob zu einer bestimmten Route 
      ein Actor registriert wurde. Wenn kein Actor mit dieser 
      Route existiert, wird geprueft, ob ein Actor 
      mit der Route '/' vorhanden ist.
     */
    boolean found = false;
    Object md = dispatcher.get(requestMethod);
    if(md instanceof Map) {
    Object o = ((Map) md).get(route);
    if (!(o instanceof String)) {
      o = dispatcher.get(requestMethod).get("/");
    }
    if (o instanceof String) {
      String actorClassName = (String) o;
      try {
        Class actorClass = Class.forName(actorClassName);
        Method[] methods = actorClass.getMethods();
        for (Method method : methods) {
          Action action = method.getAnnotation(Action.class);
          if (action != null) {
            if (action.route().equals("/") || action.route().equals(route)) {
              Object[] actionArgs = getActionArgs(exchange, method/*, queryParams*/);
              Object actorObj = actorClass.getDeclaredConstructor().newInstance();
              addDataProvider(exchange, actorObj);
              Object antwort = method.invoke(actorObj, actionArgs);
              if(!action.handlesResponse()) {
                respond(exchange, antwort);
              }
            }
    if (md instanceof Map) {
      int pos = route.lastIndexOf("/");
      Object o = ((Map) md).get(route);
      if (!(o instanceof ActionDescriptor)) {
        while (!found && (pos > -1)) {
          String routeRest = route.substring(0, pos);
          o = ((Map) md).get(routeRest);
          if (o instanceof ActionDescriptor) {
            found = true;
            handleRequest(exchange, o, routeRest, route.substring(routeRest.length()));
          }
          pos = routeRest.lastIndexOf("/");
        }
      } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
        // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
      } else {
        found = true;
        handleRequest(exchange, o, route, route);
      }
    }
      if (!found) {
        o = dispatcher.get(requestMethod).get("/");
        if (o instanceof ActionDescriptor) {
          handleRequest(exchange, o, route, route);
        }
      }
    }
  }
  private Object[] getActionArgs(HttpExchange exchange, Method method/*, Map queryParams*/) {
  private void handleRequest(HttpExchange exchange, Object o, String route, String subroute) throws IOException {
    ActionDescriptor ad = (ActionDescriptor) o;
    String actorClassName = ad.className;
    try {
      Class actorClass = Class.forName(actorClassName);
      Method[] methods = actorClass.getMethods();
      for (Method method : methods) {
        Action action = method.getAnnotation(Action.class);
        if (action != null) {
          if (action.route().equals("/") || action.route().startsWith(route)) {
            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);
            }
          }
        }
      }
    } catch (ClassNotFoundException | NoSuchMethodException | SecurityException |
            InstantiationException | IllegalAccessException | IllegalArgumentException |
            InvocationTargetException ex) {
      // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
    }
    //}
  }
  private Object[] getActionArgs(HttpExchange exchange, Method method, ActionDescriptor ad, String subroute) {
    int count = method.getParameterCount();
    Parameter[] methodParams = method.getParameters();
    Object[] actionArgs = new Object[count];
    String[] routeParams = subroute.split("/");
    Map queryParams = new HashMap();
    /*
      Lesen des Body der Anfrage geht nur einmal.
      bodyLesen soll nur in getQueryMap gerufen werden, wenn
      die Liste der Parameter mehr als einen Parameter umfasst
      oder wenn es nur ein Parameter ist, der nicht
      der HttpExchange ist.
      Anderenfalls sollte erst der Actor den Body aus dem
      HttpExchange lesen und nicht hier schon der Handler.
    */
    if(count > 1 || !methodParams[0].getType().equals(HttpExchange.class)) {
    if ((count > 1 && count > routeParams.length)
            || (methodParams.length > 0 && !methodParams[0].getType().equals(HttpExchange.class))) {
      queryParams = new HttpHelper().getQueryMap(exchange);
    }
    int k = 0;
@@ -175,12 +209,14 @@
      if (methodParam.getType().equals(HttpExchange.class)) {
        actionArgs[k] = exchange;
      } else {
        /*
          Konvention: Aktor-Parameter sind immer vom Typ String
          und Parametername der Methode ist gleich dem Namen in der Query
         */
        actionArgs[k++] = queryParams.get(methodParam.getName());
        Integer i = ad.routeParams.getOrDefault(methodParam.getName(), -1);
        if (i < 0) {
          actionArgs[k] = queryParams.get(methodParam.getName());
        } else {
          actionArgs[k] = routeParams[i + 1];
        }
      }
      ++k;
    }
    return actionArgs;
  }