Ultrakompakter HTTP Server
ulrich
2024-02-21 5ebab9058498312ad234226040f0ec506509e476
Verarbeitung von Routen um variable Elemente erweitert (einstweilen noch experimentell)
1 files added
1 files modified
184 ■■■■■ changed files
src/de/uhilger/neon/Handler.java 154 ●●●●● patch | view | raw | blame | history
src/de/uhilger/neon/entity/ActionDescriptor.java 30 ●●●●● patch | view | raw | blame | history
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;
  }
src/de/uhilger/neon/entity/ActionDescriptor.java
New file
@@ -0,0 +1,30 @@
/*
  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.entity;
import java.util.Map;
/**
 *
 * @author Ulrich Hilger
 */
public class ActionDescriptor {
  public String route;
  public Map<String, Integer> routeParams;
  public String className;
}