Ultrakompakter HTTP Server
ulrich
2024-02-21 5ebab9058498312ad234226040f0ec506509e476
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 {
5ebab9 131     HttpHelper hh = new HttpHelper();
e58690 132     String route = hh.getRouteString(exchange);
U 133     Type requestMethod = Type.valueOf(exchange.getRequestMethod());
134     /*
135       Es wird erst geprueft, ob zu einer bestimmten Route 
136       ein Actor registriert wurde. Wenn kein Actor mit dieser 
137       Route existiert, wird geprueft, ob ein Actor 
138       mit der Route '/' vorhanden ist.
139      */
5ebab9 140     boolean found = false;
63bcde 141     Object md = dispatcher.get(requestMethod);
5ebab9 142     if (md instanceof Map) {
U 143       int pos = route.lastIndexOf("/");
144       Object o = ((Map) md).get(route);
145       if (!(o instanceof ActionDescriptor)) {
146         while (!found && (pos > -1)) {
147           String routeRest = route.substring(0, pos);
148           o = ((Map) md).get(routeRest);
149           if (o instanceof ActionDescriptor) {
150             found = true;
151             handleRequest(exchange, o, routeRest, route.substring(routeRest.length()));
e58690 152           }
5ebab9 153           pos = routeRest.lastIndexOf("/");
e58690 154         }
5ebab9 155       } else {
U 156         found = true;
157         handleRequest(exchange, o, route, route);
e58690 158       }
5ebab9 159       if (!found) {
U 160         o = dispatcher.get(requestMethod).get("/");
161         if (o instanceof ActionDescriptor) {
162           handleRequest(exchange, o, route, route);
163         }
164       }
63bcde 165     }
e58690 166
U 167   }
168
5ebab9 169   private void handleRequest(HttpExchange exchange, Object o, String route, String subroute) throws IOException {
U 170     ActionDescriptor ad = (ActionDescriptor) o;
171     String actorClassName = ad.className;
172     try {
173       Class actorClass = Class.forName(actorClassName);
174       Method[] methods = actorClass.getMethods();
175       for (Method method : methods) {
176         Action action = method.getAnnotation(Action.class);
177         if (action != null) {
178           if (action.route().equals("/") || action.route().startsWith(route)) {
179             Object[] actionArgs = getActionArgs(exchange, method, ad, subroute);
180             Object actorObj = actorClass.getDeclaredConstructor().newInstance();
181             addDataProvider(exchange, actorObj);
182             Object antwort = method.invoke(actorObj, actionArgs);
183             if (!action.handlesResponse()) {
184               respond(exchange, antwort);
185             }
186           }
187         }
188       }
189     } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | 
190             InstantiationException | IllegalAccessException | IllegalArgumentException | 
191             InvocationTargetException ex) {
192       // Klasse nicht gefunden. Muss das geloggt oder sonstwie behandel werden?
193     }
194     //}
195   }
196
197   private Object[] getActionArgs(HttpExchange exchange, Method method, ActionDescriptor ad, String subroute) {
63bcde 198     int count = method.getParameterCount();
e58690 199     Parameter[] methodParams = method.getParameters();
63bcde 200     Object[] actionArgs = new Object[count];
5ebab9 201     String[] routeParams = subroute.split("/");
63bcde 202     Map queryParams = new HashMap();
5ebab9 203     if ((count > 1 && count > routeParams.length)
U 204             || (methodParams.length > 0 && !methodParams[0].getType().equals(HttpExchange.class))) {
63bcde 205       queryParams = new HttpHelper().getQueryMap(exchange);
U 206     }
207     int k = 0;
e58690 208     for (Parameter methodParam : methodParams) {
U 209       if (methodParam.getType().equals(HttpExchange.class)) {
210         actionArgs[k] = exchange;
211       } else {
5ebab9 212         Integer i = ad.routeParams.getOrDefault(methodParam.getName(), -1);
U 213         if (i < 0) {
214           actionArgs[k] = queryParams.get(methodParam.getName());
215         } else {
216           actionArgs[k] = routeParams[i + 1];
217         }
e58690 218       }
5ebab9 219       ++k;
e58690 220     }
U 221     return actionArgs;
222   }
223
224   private void addDataProvider(HttpExchange exchange, Object actorObj) {
225     if (actorObj instanceof DataConsumer) {
226       DataConsumer consumer = (DataConsumer) actorObj;
227       Object sdpListObj = exchange.getHttpContext().getAttributes().get("serverDataProviderList");
228       if (sdpListObj instanceof List) {
229         List sdpList = (List) sdpListObj;
230         Iterator i = sdpList.iterator();
231         while (i.hasNext()) {
232           Object value = i.next();
233           if (value instanceof DataProvider) {
234             consumer.addDataProvider((DataProvider) value);
235           }
236         }
237       }
238     }
239   }
240 }