From 5ebab9058498312ad234226040f0ec506509e476 Mon Sep 17 00:00:00 2001 From: ulrich Date: Wed, 21 Feb 2024 13:17:06 +0000 Subject: [PATCH] Verarbeitung von Routen um variable Elemente erweitert (einstweilen noch experimentell) --- src/de/uhilger/neon/Handler.java | 154 +++++++++++++++++++++++-------------- src/de/uhilger/neon/entity/ActionDescriptor.java | 30 +++++++ 2 files changed, 125 insertions(+), 59 deletions(-) diff --git a/src/de/uhilger/neon/Handler.java b/src/de/uhilger/neon/Handler.java index 44c3b0d..6caa331 100644 --- a/src/de/uhilger/neon/Handler.java +++ b/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; } diff --git a/src/de/uhilger/neon/entity/ActionDescriptor.java b/src/de/uhilger/neon/entity/ActionDescriptor.java new file mode 100644 index 0000000..997f3c6 --- /dev/null +++ b/src/de/uhilger/neon/entity/ActionDescriptor.java @@ -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; +} -- Gitblit v1.9.3