Ultrakompakter HTTP Server
ulrich
2024-11-14 ca79593d7659cd9c6dcdd38692888b26dcbef4f5
commit | author | age
e58690 1 /*
U 2   neon - Embeddable HTTP Server based on jdk.httpserver
3   Copyright (C) 2024  Ulrich Hilger
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU Affero General Public License as
7   published by the Free Software Foundation, either version 3 of the
8   License, or (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU Affero General Public License for more details.
14
15   You should have received a copy of the GNU Affero General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 package de.uhilger.neon;
19
20 import com.sun.net.httpserver.HttpExchange;
21 import com.sun.net.httpserver.HttpHandler;
22 import de.uhilger.neon.Action.Type;
5ebab9 23 import de.uhilger.neon.entity.ActionDescriptor;
e58690 24 import java.io.IOException;
U 25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Parameter;
28 import java.util.EnumMap;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33
34 /**
5ebab9 35  * Objekte der Klasse Handler nehmen Objekte entgegen die die Annotationen NeonActor enthalten.
U 36  * Deren mit NeonMethod annotierten Methoden stellt der Handler via HTTP bereit.
e58690 37  *
5ebab9 38  * Wird ein Neon-Server mit der Klasse NeonFactory erzeugt, kann mit der Verwendung dieses Handlers
U 39  * die NeonFactory den Server selbsttaetig erstellen, ohne zusaetzlichen Boilerplate Code, den eine
40  * eigene Anwendung mitbringen muesste.
e58690 41  *
U 42  * @author Ulrich Hilger
43  * @version 1, 6.2.2024
44  */
45 public class Handler implements HttpHandler {
46
47   private final Map<Type, Map> dispatcher;
48
49   /**
50    * Ein Objekt der Klasse Handler erzeugen
51    */
52   public Handler() {
53     dispatcher = new EnumMap<>(Type.class);
5ebab9 54     //dispatcher.put(Type.GET, new HashMap<String, String>());
U 55     //dispatcher.put(Type.PUT, new HashMap<String, String>());
56     //dispatcher.put(Type.POST, new HashMap<String, String>());
57     //dispatcher.put(Type.DELETE, new HashMap<String, String>());
58     dispatcher.put(Type.GET, new HashMap<String, ActionDescriptor>());
59     dispatcher.put(Type.PUT, new HashMap<String, ActionDescriptor>());
60     dispatcher.put(Type.POST, new HashMap<String, ActionDescriptor>());
61     dispatcher.put(Type.DELETE, new HashMap<String, ActionDescriptor>());
e58690 62   }
U 63
64   /**
65    * Diesem Handler einen Actor hinzufuegen
66    *
67    * @param methodType HTTP Methode
68    * @param route die Route, ueber die der Actor aufgerufen werden soll
69    * @param className die Klasse, die die Methode enthaelt, die zur Verarbeitung der Route
70    * ausgefuehrt werden soll
71    */
72   public void setActor(Type methodType, String route, String className) {
5ebab9 73     ActionDescriptor ad = new ActionDescriptor();
U 74     ad.className = className;
75     ad.routeParams = new HashMap<>();
76     int pos = route.indexOf("{");
77     if (pos > -1) {
78       String paramStr = route.substring(pos);
79       String[] params = paramStr
80               .replaceAll("\\{", "")
81               .replaceAll("\\}", "")
82               .split("/");
83       for (int i = 0; i < params.length; i++) {
84         ad.routeParams.put(params[i], i);
85       }
86       ad.route = route.substring(0, pos - 1);
87     } else {
88       // Map kann leer bleiben
89       ad.route = route;
90     }
91
e58690 92     //Logger.getLogger(Handler.class.getName())
U 93     //        .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className});
5ebab9 94     dispatcher.get(methodType).put(ad.route, ad);
e58690 95   }
U 96
97   /**
98    * Eine Antwort senden
99    *
100    * Diese Methode kann ueberschrieben werden, falls die Antwort vor dem Senden noch weiter
101    * bearbeiten werden soll.
102    *
103    * @param exchange das Objekt mit Informationen zu HTTP Anfrage und -Antwort
104    * @param response das Ergebnisobjekt, das als Antwort gesendet werden soll
105    * @throws IOException wenn die Antwort nicht gesendet werden kann
106    */
107   public void respond(HttpExchange exchange, Object response) throws IOException {
108     new HttpResponder().antwortSenden(exchange, HttpResponder.SC_OK, response.toString());
109   }
110
111   /**
112    * Das Objekt abrufen, das auf die Routen verweist, die von diesem Handler beantwortet werden.
113    *
114    * eine Map<Type, Map<String, String>> GET - route1 - Klassenname GET - route2 - Klassenname PUT -
115    * route1 - Klassenname PUT - route2 - Klassenname usw.
116    *
117    * @return das Dispatcher-Objekt dieses Handlers
118    */
119   public Map<Type, Map> getDispatcher() {
120     return dispatcher;
121   }
122
123   /**
124    * Eine HTTP-Anfrage ausfuehren
125    *
126    * @param exchange das Objekt mit Informationen zu HTTP Anfrage und -Antwort
127    * @throws IOException
128    */
129   @Override
130   public void handle(HttpExchange exchange) throws IOException {
880582 131     String route = exchange
U 132             .getRequestURI()
133             .getPath()
134             .substring(exchange
135                     .getHttpContext()
136                     .getPath()
137                     .length());            
138             
e58690 139     Type requestMethod = Type.valueOf(exchange.getRequestMethod());
U 140     /*
141       Es wird erst geprueft, ob zu einer bestimmten Route 
142       ein Actor registriert wurde. Wenn kein Actor mit dieser 
143       Route existiert, wird geprueft, ob ein Actor 
144       mit der Route '/' vorhanden ist.
145      */
5ebab9 146     boolean found = false;
63bcde 147     Object md = dispatcher.get(requestMethod);
5ebab9 148     if (md instanceof Map) {
U 149       int pos = route.lastIndexOf("/");
150       Object o = ((Map) md).get(route);
151       if (!(o instanceof ActionDescriptor)) {
152         while (!found && (pos > -1)) {
153           String routeRest = route.substring(0, pos);
154           o = ((Map) md).get(routeRest);
155           if (o instanceof ActionDescriptor) {
156             found = true;
982611 157             handleRequest(exchange, o, routeRest, route.substring(routeRest.length()), requestMethod);
e58690 158           }
5ebab9 159           pos = routeRest.lastIndexOf("/");
e58690 160         }
5ebab9 161       } else {
U 162         found = true;
982611 163         handleRequest(exchange, o, route, route, requestMethod);
e58690 164       }
5ebab9 165       if (!found) {
U 166         o = dispatcher.get(requestMethod).get("/");
167         if (o instanceof ActionDescriptor) {
982611 168           handleRequest(exchange, o, route, route, requestMethod);
5ebab9 169         }
U 170       }
63bcde 171     }
e58690 172
U 173   }
174
982611 175   private void handleRequest(HttpExchange exchange, Object o, String route, String subroute, Type requestMethod) throws IOException {
5ebab9 176     ActionDescriptor ad = (ActionDescriptor) o;
U 177     String actorClassName = ad.className;
178     try {
179       Class actorClass = Class.forName(actorClassName);
180       Method[] methods = actorClass.getMethods();
181       for (Method method : methods) {
182         Action action = method.getAnnotation(Action.class);
183         if (action != null) {
982611 184           if ((action.route().equals("/") || action.route().startsWith(route)) && action.type().equals(requestMethod)) {
5ebab9 185             Object[] actionArgs = getActionArgs(exchange, method, ad, subroute);
U 186             Object actorObj = actorClass.getDeclaredConstructor().newInstance();
187             addDataProvider(exchange, actorObj);
188             Object antwort = method.invoke(actorObj, actionArgs);
189             if (!action.handlesResponse()) {
190               respond(exchange, antwort);
191             }
192           }
193         }
194       }
195     } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | 
196             InstantiationException | IllegalAccessException | IllegalArgumentException | 
197             InvocationTargetException ex) {
198       // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
199     }
200     //}
201   }
202
203   private Object[] getActionArgs(HttpExchange exchange, Method method, ActionDescriptor ad, String subroute) {
63bcde 204     int count = method.getParameterCount();
e58690 205     Parameter[] methodParams = method.getParameters();
63bcde 206     Object[] actionArgs = new Object[count];
5ebab9 207     String[] routeParams = subroute.split("/");
880582 208     
U 209     
210     /*
211       Fall 1: Es sind mehr als ein Parameter zu uebergeben und die Route enthaelt 
212       weniger Parameter als die Methode erfordert.
213       Fall 2: Die Methode erwartet Parameter und der erste Parameter ist nicht 
214       vom Typ HttpExchange.
215       
216       Wenn einer dieser beiden Faelle eintritt, wird alles als Parameter an die Methode 
217       uebergeben, was eventuell als Teil einer Query im URL oder im Body enthalten ist.
8bab5f 218       Fuer Mthoden, die nicht vom Typ HTTP GET sind, kann ein Actor kann dann den Body 
U 219       nicht mehr lesen, weil das bereits an dieser Stelle gemacht wurde.
880582 220     */
63bcde 221     Map queryParams = new HashMap();
5ebab9 222     if ((count > 1 && count > routeParams.length)
U 223             || (methodParams.length > 0 && !methodParams[0].getType().equals(HttpExchange.class))) {
63bcde 224       queryParams = new HttpHelper().getQueryMap(exchange);
U 225     }
880582 226     
63bcde 227     int k = 0;
e58690 228     for (Parameter methodParam : methodParams) {
U 229       if (methodParam.getType().equals(HttpExchange.class)) {
230         actionArgs[k] = exchange;
231       } else {
5ebab9 232         Integer i = ad.routeParams.getOrDefault(methodParam.getName(), -1);
U 233         if (i < 0) {
234           actionArgs[k] = queryParams.get(methodParam.getName());
235         } else {
236           actionArgs[k] = routeParams[i + 1];
237         }
e58690 238       }
5ebab9 239       ++k;
e58690 240     }
U 241     return actionArgs;
242   }
243
244   private void addDataProvider(HttpExchange exchange, Object actorObj) {
245     if (actorObj instanceof DataConsumer) {
246       DataConsumer consumer = (DataConsumer) actorObj;
247       Object sdpListObj = exchange.getHttpContext().getAttributes().get("serverDataProviderList");
248       if (sdpListObj instanceof List) {
249         List sdpList = (List) sdpListObj;
250         Iterator i = sdpList.iterator();
251         while (i.hasNext()) {
252           Object value = i.next();
253           if (value instanceof DataProvider) {
254             consumer.addDataProvider((DataProvider) value);
255           }
256         }
257       }
258     }
259   }
260 }