SEPIA-Framework / sepia-docs

Documentation and Wiki for SEPIA. Please post your questions and bug-reports here in the issues section! Thank you :-)
https://sepia-framework.github.io/
236 stars 16 forks source link

How to send HTTP request from Mesh-Node Plugin #219

Closed bambby closed 1 year ago

bambby commented 1 year ago

Hallo Florian, erstmal alle Hochachtung für dieses sehr komplexe Open-Source Projekt. Ich hab's auf einem Raspi installiert und es läuft. Ich würde gerne den Sprachassistenten benutzen, um meine SmartHome Automation damit zu steuern und Geräte abzufragen. Das Steuern von Aktoren funktioniert ohne Probleme. Über den Teach Server habe ich einen Link "gelehrt", um damit direkt ein Smart Device anzusteuern, z.B. http://192.168.178.131/light/0?turn=on. Um Daten von Sensoren via HTTP Request von meinem Gateway abzufragen (das auf einem anderen Raspi läuft und Gerätedaten kontinuierlich in eine SQL Datenbank speichert), benutze ich den sepia-mesh-nodes Server. Dazu habe ich ein Plugin erstellt, das einen entsprechenden Request an das Gateway schicken soll. Jedoch schickt das Plugin den Request nicht raus. Logausgabe ist:

2023-01-02 17:02:00 [main] INFO ConfigNode - loading settings from Settings/node.custom.properties... done.
2023-01-02 17:02:00 [main] INFO MeshNode - --- Running SEPIA-Mesh-Node with CUSTOM settings ---
2023-01-02 17:02:00 [main] INFO PluginLoader - Cleaning folder 'net' from plugin target directory.
2023-01-02 17:02:00 [main] INFO PluginLoader - Plugin class-loader has been reset.
2023-01-02 17:02:00 [main] INFO PluginLoader - Loading plugin from: Plugins/src/HelloPlugin.java
2023-01-02 17:02:03 [main] INFO PluginLoader - Plugin successfully compiled to: Plugins/compiled/
2023-01-02 17:02:03 [main] INFO PluginLoader - Loading plugin from: Plugins/src/TemperaturePlugin.java
2023-01-02 17:02:05 [main] INFO PluginLoader - Plugin successfully compiled to: Plugins/compiled/
2023-01-02 17:02:05 [main] INFO MeshNode - Total plugins loaded: 2
2023-01-02 17:02:05 [main] INFO MeshNode - Starting SEPIA-Mesh-Node v0.10.0 (custom)
2023-01-02 17:02:05 [main] INFO MeshNode - date: 02.01.2023 - 16:02:05 - GMT
2023-01-02 17:02:05 [main] INFO MeshNode - server running on port 20780
2023-01-02 17:02:05 [Thread-0] INFO log - Logging initialized @5402ms to org.eclipse.jetty.util.log.Slf4jLog
2023-01-02 17:02:05 [Thread-0] INFO EmbeddedJettyServer - == Spark has ignited ...
2023-01-02 17:02:05 [Thread-0] INFO EmbeddedJettyServer - >> Listening on 0.0.0.0:20780
2023-01-02 17:02:05 [Thread-0] INFO Server - jetty-9.4.48.v20220622; built: 2022-06-21T20:42:25.880Z; git: 6b67c5719d1f4371b33655ff2d047d24e171e49a; jvm 11.0.16+8-post-Raspbian-1deb11u1
2023-01-02 17:02:05 [Thread-0] INFO session - DefaultSessionIdManager workerName=node0
2023-01-02 17:02:05 [Thread-0] INFO session - No SessionScavenger set, using defaults
2023-01-02 17:02:05 [Thread-0] INFO session - node0 Scavenging every 600000ms
2023-01-02 17:02:05 [Thread-0] INFO AbstractConnector - Started ServerConnector@68b91e{HTTP/1.1, (http/1.1)}{0.0.0.0:20780}
2023-01-02 17:02:05 [Thread-0] INFO Server - Started @5760ms
2023-01-02 17:02:10 [qtp13245115-18] INFO TemperaturePlugin - DeviceID: 3
2023-01-02 17:02:11 [qtp13245115-18] ERROR PluginEndpoints - Plugin error! User 'anonymous' created exception: access denied ("java.net.URLPermission" "http://192.168.178.33:8080/v1/gateway/devices/3" "GET:")
2023-01-02 17:02:11 ERROR - TRACE: java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:553)
2023-01-02 17:02:11 ERROR - TRACE: java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119)
2023-01-02 17:02:11 ERROR - TRACE: net.b07z.sepia.server.mesh.plugins.TemperaturePlugin.execute(TemperaturePlugin.java:48)
2023-01-02 17:02:11 ERROR - TRACE: net.b07z.sepia.server.mesh.endpoints.PluginEndpoints.executePlugin(PluginEndpoints.java:94)
2023-01-02 17:02:11 ERROR - TRACE: net.b07z.sepia.server.mesh.server.StartNode.lambda$loadEndpoints$4(StartNode.java:31)

Der Code ist:

package net.b07z.sepia.server.mesh.plugins;

import java.io.IOException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.b07z.sepia.server.core.tools.JSON;

public class TemperaturePlugin implements Plugin {

  private static final Logger log = LoggerFactory.getLogger(TemperaturePlugin.class);

  @Override
  public PluginResult execute(JSONObject data) {

    String deviceid = JSON.getString(data, "deviceid");
    log.info("DeviceID: " + deviceid);

    HttpClient client = HttpClient.newBuilder()
        .authenticator(new Authenticator() {
          @Override
          protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication("admin", "password".toCharArray());
          }
        })
        .build();

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("http://192.168.178.33:8080/v1/gateway/devices/" + deviceid))
        .GET()
        .build();

    String status = "failed";
    String body = "unknown";

    try {
      HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
      status = "success";
      body = response.body();

     log.info("Response body: " + body);

    } catch (IOException | InterruptedException e) {
      log.error("No or bad response from Web Server", e);
    }

    log.info("Response body: " + body);

    JSONArray measurements = JSON.parseStringToArrayOrFail(body);
    JSONObject deviceMeasures = JSON.parseString(measurements.get(0).toString());
    JSONObject deviceData = JSON.parseString(deviceMeasures.get("data").toString());
    String value = JSON.getString(deviceData, "temp").replace('.', ',');

    PluginResult result = new PluginResult(JSON.make(
        "status", status,
        "value", value));

    return result;
  }
}

Ist in den Settings noch irgendwas zu berücksichtigen? Generell funktioniert das Senden des Requests. Ich hab das Plugin nachgebaut und aus einem Tomcat Server heraus geschickt und es kommt an und wird beantwortet. Gibt es irgendwas in SEPIA, das das Senden von Requests verhindert?

Viele Grüße Achim

fquirin commented 1 year ago

Hi Achim :-)

Um Daten von Sensoren via HTTP Request von meinem Gateway abzufragen (das auf einem anderen Raspi läuft und Gerätedaten kontinuierlich in eine SQL Datenbank speichert), benutze ich den sepia-mesh-nodes Server

Coole Idee 🙂👍

Ist in den Settings noch irgendwas zu berücksichtigen? [...] Gibt es irgendwas in SEPIA, das das Senden von Requests verhindert?

Ich bin gerade nicht sicher, ob die security sandbox auch HTTP requests beeinflusst 🤔, aber versuch mal in Settings/node.custom.properties den Wert zu setzen: use_sandbox_security_policy=false. Ich prüfe währenddessen noch mal die Mesh-Node Einstellungen, ist ne Weile her, dass ich da rein geguckt habe ^^.

Es gibt übrigens in dem sepia.server.core.tools package auch die Klasse Connectors mit jede Menge nützlichen HTTP request Methoden wie z.B. apacheHttpGET, die das Plugin eventuell noch einfacher machen. Basic authentication kann in der Apache Methode z.B. über den Header gemacht werden.

fquirin commented 1 year ago

Ich habe mir den Code noch mal angeguckt und es kann sein dass die Klasse PasswordAuthentication durch den SecurityManager (und somit 'use_sandbox_security_policy') geblockt wird. Falls das tatsächlich der Fall ist sollte die Einstellung funktionieren, falls nicht versuch bitte mal die Authentifizierung über den Header:

String userpasswd = username + ":" + password;
String authData = Base64.getEncoder().encodeToString(userpasswd.getBytes());

Map<String, String> headers = Connectors.addAuthHeader(null, "Basic", authData);
headers.put("Accept", "application/json");

HttpClientResult res = Connectors.apacheHttpGET(myUrl, headers);

if (res.statusCode == 200){
    JSONObject myJson = JSON.parseStringOrFail(result.content);
}else{
    throw new RuntimeException("Failed to GET '" + myUrl + "' - response code: " + res.statusCode + " - Error: " + res.statusLine);
}

Ich hoffe der Code stimmt so, ich konnte es nicht live testen.

bambby commented 1 year ago

Hallo Florian, die Einstellung der Security Policy hat das Problem gelöst! Vielen Dank! Sind irgendwelche gravierenden Security Auswirkungen zu befürchten mit der neuen Einstellung?

fquirin commented 1 year ago

Solange du alle Plugins selbst verwaltest, sollte es keine Probleme geben. Die Sandbox soll eigentlich nur verhindern, dass in den Plugins kein Unfug getrieben wird :-)

bambby commented 1 year ago

Issue solved