/*
|
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;
|
|
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpHandler;
|
import de.uhilger.neon.Action.Type;
|
import java.io.IOException;
|
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.Method;
|
import java.lang.reflect.Parameter;
|
import java.util.EnumMap;
|
import java.util.HashMap;
|
import java.util.Iterator;
|
import java.util.List;
|
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.
|
*
|
* 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
|
*/
|
public class Handler implements HttpHandler {
|
|
private final Map<Type, Map> dispatcher;
|
|
/**
|
* Ein Objekt der Klasse Handler erzeugen
|
*/
|
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>());
|
}
|
|
/**
|
* Diesem Handler einen Actor hinzufuegen
|
*
|
* @param methodType HTTP Methode
|
* @param route die Route, ueber die der Actor aufgerufen werden soll
|
* @param className die Klasse, die die Methode enthaelt, die zur Verarbeitung der Route
|
* ausgefuehrt werden soll
|
*/
|
public void setActor(Type methodType, String route, String className) {
|
//Logger.getLogger(Handler.class.getName())
|
// .log(Level.INFO, "{0} {1} {2}", new Object[]{methodType, route, className});
|
dispatcher.get(methodType).put(route, className);
|
}
|
|
/**
|
* Eine Antwort senden
|
*
|
* Diese Methode kann ueberschrieben werden, falls die Antwort vor dem Senden noch weiter
|
* bearbeiten werden soll.
|
*
|
* @param exchange das Objekt mit Informationen zu HTTP Anfrage und -Antwort
|
* @param response das Ergebnisobjekt, das als Antwort gesendet werden soll
|
* @throws IOException wenn die Antwort nicht gesendet werden kann
|
*/
|
public void respond(HttpExchange exchange, Object response) throws IOException {
|
new HttpResponder().antwortSenden(exchange, HttpResponder.SC_OK, response.toString());
|
}
|
|
/**
|
* Das Objekt abrufen, das auf die Routen verweist, die von diesem Handler beantwortet werden.
|
*
|
* eine Map<Type, Map<String, String>> GET - route1 - Klassenname GET - route2 - Klassenname PUT -
|
* route1 - Klassenname PUT - route2 - Klassenname usw.
|
*
|
* @return das Dispatcher-Objekt dieses Handlers
|
*/
|
public Map<Type, Map> getDispatcher() {
|
return dispatcher;
|
}
|
|
/**
|
* Eine HTTP-Anfrage ausfuehren
|
*
|
* @param exchange das Objekt mit Informationen zu HTTP Anfrage und -Antwort
|
* @throws IOException
|
*/
|
@Override
|
public void handle(HttpExchange exchange) throws IOException {
|
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.
|
*/
|
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);
|
}
|
}
|
}
|
}
|
} 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/*, Map queryParams*/) {
|
int count = method.getParameterCount();
|
Parameter[] methodParams = method.getParameters();
|
Object[] actionArgs = new Object[count];
|
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)) {
|
queryParams = new HttpHelper().getQueryMap(exchange);
|
}
|
int k = 0;
|
for (Parameter methodParam : methodParams) {
|
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());
|
}
|
}
|
return actionArgs;
|
}
|
|
private void addDataProvider(HttpExchange exchange, Object actorObj) {
|
if (actorObj instanceof DataConsumer) {
|
DataConsumer consumer = (DataConsumer) actorObj;
|
Object sdpListObj = exchange.getHttpContext().getAttributes().get("serverDataProviderList");
|
if (sdpListObj instanceof List) {
|
List sdpList = (List) sdpListObj;
|
Iterator i = sdpList.iterator();
|
while (i.hasNext()) {
|
Object value = i.next();
|
if (value instanceof DataProvider) {
|
consumer.addDataProvider((DataProvider) value);
|
}
|
}
|
}
|
}
|
}
|
}
|