phaziz.com

Startseite > 15.11.2017 Headless-CMS mit Slim3

15.11.2017 Headless-CMS mit Slim3

Headless-CMS mit Slim3

Slim Framework

Was bedeutet "kopflos"

Content Management System ist ein Begriff der beschreibt, dass Backend und Frontend (Verwaltungsoberfläche für die Inhalte und Struktur und eigentliche Internetseiten) aus einer Applikation kommen (z.B. mit Wordpress, oder Typo3, ...).

Ein Headless-CMS beschreibt im Gegensatz dazu nur das reine Backend (die Verwaltungsoberfläche) für Inhalte und Struktur. Es wird lediglich eine API zur Verfügung gestellt (JSON, XML, ...) um die vorhandenen Inhalte und Strukturen auszulesen - wie diese aber letztendlich im Browser, oder in einer nativen App, oder wo auch immer dargestellt werden, kümmert ein Headless-CMS nicht.

Dies bietet zum Beispiel den Vorteil, das man ein Backend für die Fütterung von mehreren Frontends, Internetseiten, Apps, ... benutzen kann. Eine zentrale Anlaufstelle in einem Backend für die Ausgabe von Inhalten und Strukturen in mehreren voneinander unabhängigen Endpunkten.

Headless mit Slim3

In Slim3 muss man für ein solches Szenario nicht viel ändern. Eigentlich fallen nur Teile für eine normale 1-Instanz weg - wie zum Beispiel die Template-Engine (Twig, ...). Die gesamte Grundausrüstung (Composer - Autoloading - App instanzieren - Routen gestalten - App ausliefern) bleibt erhalten.

Authentifizierung

Ich habe ein einfaches Setup gewählt mit vorgegebenen Benutzern und Token. Eine vollständige Umsetzung (mit zum Beispiel oAuth 2.0) weicht nur geringfügig davon ab. Als Ausgangssituation denken wir uns alo eine kleine SQLite-Datenbanktabelle user. Diese user-Tabelle besteht nur aus den 3 Spalten id, username und token.

Um jetzt Zugriffe auf unsere JSON-REST-Api bereitzustellen, erlauben wir Zugriffe über folgendes URL-Muster in den Routen:

./root/{username}/{token}/...

Erfolgt ein Zugriff über eine Route mit falschem Benutzernamen und/oder falschem Token, wird der Zugriff verwehrt. Slim3 bietet hierfür die Antwort mit entsprechenden HTTP-Stauscodes (404 - not found, 403 - Forbidden). Ist der Benutzer in der user-Tabelle vorhanden, gewähren wir den Zugriff auf den aufgerufenen Endpoint - ist der Benutzer unbekannt, wird die Anfrage nicht weiter bearbeitet.

Eine einfache Klasse die in den entsprechenden Routen inkludiert wird, könnte so aussehen:

class userController
{
  public static function checkUser($username, $token, $db)
  {
    $select_user = $db -> select() -> from('user') -> where('username', '=', $username) -> where('token', '=', $token) -> limit(1);
    $stmt = $select_user -> execute();
    $data = $stmt -> fetchAll();
    $data_countr = count($data);

    if($data_countr === 1)
    {
      return true;
    }
    else
    {
      return false;
    }
  }
}

Hier wird lediglich überprüft, ob der Benutzer und der Token in dieser Kombination in der Datenbanktabelle user vorhanden ist - true - andernfalls wird ein einfaches false zurückgeliefert.

API-Routing

Fragen wir ... unseren DVD-Bestand ab, den wir per definiertem Endpoint als JSON-REST-Api zur Verfügung stellen. Eine Beispiel-Route in der SLIM3-index.php könnte dabei wie folgt aussehen:

$app -> get('/{username}/{token}/media/dvd/get', function($request, $response, $args) use ($app)
  {
    $username = filter_var($args['username'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
    $token = filter_var($args['token'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);

    return dvdController::getAllItems($app, $request, $response, $this -> logger, $this -> uuid, $this -> db, $username, $token);
  }
);

Wir definieren die komplette Route /{username}/{token}/media/dvd/get - darin enthalten unser username und der token und das ganze per GET-Request. Als kleines Quäntchen Sicherheit filtern wir die übergebenen Variablen und leiten diese an unseren entsprechenden Controller dvdController weiter - bzw. an dessen statische Funktion getAllItems.

Der Response

Zur Wiederholung - Überprüfung der Benutzerdaten - kleine Verzweigung (true/false) bzw. den Zugriff gewähren, oder untersagen - im Falle von richtigen Benutzerdaten eine qualifizierte Antwort auf die Anfrage im JSON-Format als Response liefern. Unser dvdController:

...

class dvdController
{
  public static function getAllItems(Slim\App $app, Request $request, Response $response, $logger, $uuid, $db, $username, $token, $args = [])
  {
    header('Access-Control-Allow-Origin: \*');

    $usercheck = userController::checkUser($username, $token, $db);

    if($usercheck == true)
    {
      $select_dvd = $db -> select() -> from('dvd') -> orderBy('id');
      $stmt_dvd = $select_dvd -> execute();
      $data_dvd = $stmt_dvd -> fetchAll();
      $data_dvd_countr = count($data_dvd);

      if($data_dvd_countr > 0)
      {
        $json_dvd_data = json_encode($data_dvd);

        return $response -> withJson($data_dvd, 200);
      }
      else
      {
        return $response -> withJson(NULL, 404);
      }
    }
  }

  ...

Fertig

Ist unsere kleine API die über die entsprechenden Routen und Controller erweitert werden kann.

Ein HTML-Frontend für die JSON-Daten

Wir können jetz an beliebiger Stelle im Internet eine HTML-Seite bereitstellen, auf der wir unsere DVD-Sammlung präsentieren wollen... Abfragen können wir unsere API zum Beispiel per Javascript (jQuery) - eine Funktion hierfür könnte wie folgt aussehen:

initLoaderDVD = function()
{
  $('#textareadvd').html("loading...");

  $.get("https://{URL}/{username}/{token}/media/dvd", function( data )
    {
      let ret = '';
      let i = 1;

      $.each( data, function( key, val )
        {
          ret += i + ' ' + val.title + '<br>';
          i++;
        }
      );

      $('#textareadvd').html( ret );
    }
  ).fail(function()
    {
      console.log('Es wurden keine DVDs gefunden...');
    }
  );
};

initLoaderDVD();

Hier in diesem Beispiel fragen wir unseren definierten DVD-Endpunkt per GET-Request ab und erhalten als Antwort eine Sammlung unserer DVDs. Diese parsen wir als ein Javascript-Objekt und stellen die gewünschten Informationen dar (title). Den generierten HTML-Inhalt fürgen wir in einen Container (textareadvd) ein. Fertig!

Wirklich Headless?

Ja, denn wenn wir jetzt unsere DVD-Liste zum Beispiel in einer .NET-Core-Anwendung einbauen wollen, können wir dies einfach über den definierten Endpoint /{username}/{token}/media/dvd/get aus unserer Routen-Sammlung tun:

...

Console.WriteLine("https://{URL}/{Benutzername}/{TOKEN}/media/dvd/get");
Console.WriteLine(" Alle DVDs ");

var client = new WebClient();
client.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome / 58.0.3029.110 Safari / 537.36");

var response = client.DownloadString(address: "https://{URL}/{Benutzername}/{TOKEN}/media/dvd/get");
var objDVD = JsonConvert.DeserializeObject(response);
Console.WriteLine(objDVD);

...

Kaffee-Liebhaber unter uns? Was ist mit Java?

Stellen wir eine kleine Java-Konsolenanwendung her um die API abzufragen:

package RestClient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class RestClient {

    public static void main(String[] args)
    {
        try
        {
            URL url = new URL("https://{URL}/{Benutzername}/{TOKEN}/media/dvd/getallby?sorting=2");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Accept", "application/json");

            if (conn.getResponseCode() != 200)
            {
                throw new RuntimeException("Fehler : HTTP-Error-Code: " + conn.getResponseCode());
            }

            BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

            String output;

            System.out.println("Computer sagt:\n");

            while ((output = br.readLine()) != null) {
                    System.out.println(output);
            }

            conn.disconnect();
        }
        catch (MalformedURLException e)
        {
            System.out.println(e.getMessage());
        }
        catch (IOException e)
        {
            System.out.println(e.getMessage());
        }
    }
}

Als Antwort erhält unser Java-Client eine Antwort die wie bei allen anderen Abfragen auch aus einem JSON-String besteht und beliebig weiterverarbeitet werden kann:

[{"id":"28","title":"#9","regisseur":"Shane Acker"},{"id":"203","title":"21 Gramm","regis...

Fazit

Hier in diesem Beispiel beliefern wir also gleichzeitig ein HTML-Frontend eine .NET-Core-Anwendung in C# und eine kleine Java-Konsolenanwendung. Die Abfragen können wir auch in beliebigen anderen Internetseiten, oder Apps einbauen...fertig ist unser Headless-CMS mit Slim3.

Suche

Suchbegriffe mit mindestens 3 Zeichen! Suchvorgang mit ENTER-Taste starten.