Wie inzwischen bekannt sein dürfte, ist Serverless Computing der neue, heiße Scheiß in der IT-Welt, die eben auch nie um neuen, heißen Scheiß verlegen ist. Da mutet es fast schon ein wenig Retro an, wenn anstatt vermeintlich viel moderneren bzw. neueren Programmier-Umgebungen wie Node.js oder Googles Go tatsächlich mit PHP beschäftigen möchte.
Aber als bekennender PHP-Veteran lag es nahe, die Aufgabe mit PHP lösen zu wollen, wenn dies schon im Serverless Computing Dienst der Alibaba Cloud zur Verfügung steht. Aber bevor ich tatsächlich auch ein paar Zeilen Code zeigen möchte, bedarf es wiederum einiger Vorüberlegungen und Vorbereitungen.
Ein paar Gedanken zu statischen vs. dynamischen Seiten
Zunächst stellt sich die Frage, wieso überhaupt wieder programmiert werden muss respektive soll, schließlich stehen doch wie im letzten Teil beschrieben statische Web-Seiten im Netz, verbunden mit Vorteilen wie guter Performance und hervorragender Skalierbarkeit. Genau das ist der Grund – statische Seiten sind eben per Definition statisch, die Reaktion auf Benutzer-Eingaben kann allenfalls client-seitig, d.h. letztlich per JavaScript erfolgen. Damit stößt man jedoch schnell auf Grenzen, etwa falls eine Information an den Seitenbetreiber übermittelt werden soll. Sicherlich könnte man auch einfach eine Mailadresse einblenden oder auf Facebook, Twitter oder spezielle Dienstleister verweisen, die derartige Funktionalitäten bereit stellen, aber das wäre einerseits langweilig, andererseits ein gewisser Bruch im Workflow, ganz davon abgesehen, dass der Benutzer vielleicht gar nicht willens ist, seine Informationen nun noch einem Dritten zu übermitteln. In der vorherigen Version der Website, die hier als extrem einfaches Beispiel dient, wurde das Feedback-Formular mit den Bordmitteln und einem Plugin von Grav, einem „Flat-File-CMS“, realisiert. Dabei handelte es sich um ein Formular der einfachsten Sorte, wie seit mittlerweile Jahrzehnten im Web verwendet. Der Nutzer gibt seine Kontaktdaten und eine Nachricht ein, per Absenden-Button werden die Daten an den Server übermittelt und verarbeitet, anschließend erfolgt die Reaktion, d.h. es wird eine Seite zurück geliefert, auf der meistens eine Erfolgs- und seltener einer Fehlermeldung zu lesen ist. Gleichzeitig wird an den Seitenbetreiber vom verarbeitenden Server eine Mail generiert, in der alle (bereinigten) Daten aus dem Formular zu finden sind. Natürlich könnte man noch weitere Schritte durchführen, etwa Speicherung in einer Datenbank oder Übernahme in ein Ticket-System, aber Derartiges muss in diesem einfachen Beispiel erstmal außen vor bleiben.
Das Formular an sich ist auch mit Hugo schnell gebaut, nur bleibt die Frage nach der Übermittlung – also welcher Server soll die Formulardaten entgegen nehmen? Und wie sieht die Reaktion darauf aus? Im klassischen Fall wird dem Nutzer in direkter Abfolge eine Seite präsentiert, die einerseits alle Standard-Elemente der Website beinhaltet und auf der sich andererseits die Rückmeldung befindet. Ein Redirect auf eine statische Erfolgs- oder Fehlerseite wäre eine andere Möglichkeit. Ich habe mich jedoch für eine zeitgemäßere Lösung entschieden, und zwar für eine Single Page Application (SPA). Nun mag es vermessen und mittelschwer hochtrabend erscheinen, die Verarbeitung eines Feedback-Formulars als SPA zu bezeichnen, aber auch jene „Anwendung“ beinhaltet alle Komponenten einer größeren SPA: Zunächst wird client-seitig eine Vor-Verarbeitung realisiert, daraufhin erfolgt ein Request an den Server, selbstverständlich im JSON-Format, das Ergebnis wird ebenfalls im JSON-Format zurück geliefert, woraufhin ein wenig JavaScript dafür sorgt, dass dem Benutzer die Erfolgs- oder Fehlermeldung angezeigt wird. All das passiert auf derselben Seite, die jeweiligen Inhalte werden client-seitig erzeugt, fertig ist die Mini-SPA. Der Server wiederum ist losgelöst vom Client, er nimmt nur einen Request per JSON entgegen und liefert nach der Verarbeitung wiederum reine JSON-Daten zurück, muss sich somit nicht um den Aufbau von HTML- bzw. Seiten-Elementen kümmern, so dass die „Business-Logik“, wenn man dies bei diesem simplen Beispiel überhaupt so nennen möchte, von der Darstellung entkoppelt ist. Um nicht noch mehr Buzzwords aufgreifen zu müssen – auf JavaScript- bzw. SPA-Frameworks wie Angular, React oder meinen momentanen Favoriten Vue.js habe ich angesichts dieser minimalen Anforderungen jedoch verzichtet – vorstellbar und sinnvoll wäre deren Einsatz jedoch bei größeren Anwendungen.
Nachdem die Client-Seite geklärt war, blieb die Frage nach der Verwendung des Servers. Oder konkreter – was sollte auf der Server-Seite zum Einsatz kommen, und welche Art Server, welche Kapazitäten etc. wären dafür notwendig? Der klassische Weg wäre sicherlich, einen virtuellen Server einzusetzen und den notwendigen Code darauf zu platzieren. Bei den genannten Anforderungen würde jedoch auch ein kleiner Webspace-Account bei einem beliebigen Provider genügen, sofern eine Unterstützung von server-seitiger Programmierung vorhanden wäre. Es ist leicht zu erkennen, dass ein virtueller Server, dazu zähle ich nun auch diejenigen, die sich im Rahmen des Elastic Compute Service in der Alibaba Cloud einsetzen lassen, völlig übertrieben wäre, soll heißen, es wäre mit Kanonen auf Spatzen geschossen. Darüber hinaus würde der Betrieb eines Servers noch Verwaltungs-Aufwand bedeuten, den ich mir gerne erspart hätte. Natürlich hätte ich die „Anwendung“ auf einer bereits existierenden VM hosten können, doch andererseits wollte ich nun einmal Cloud-Dienste einsetzen, um das Ziel zu erreichen.
Function Compute – Serverless Computing mit Alibaba Cloud
Es lag also nahe, sich die Liste der Dienste in der Alibaba Cloud noch einmal anzusehen. Fündig geworden bin ich beim Dienst namens „Function Compute„. Dabei handelt es sich um so genanntes „Serverless Computing“, was eigentlich ein Widerspruch in sich ist, aber als neues Buzzword derzeit für Furore sorgt.
Natürlich ist das Einsatzszenario im Vergleich zu den auf der Website genannten eher rudimentär, aber um den Dienst ein wenig auszuprobieren und kennenzulernen genau richtig. Die Funktionsweise des Function Compute (FC) Dienstes ist eigentlich simpel – anhand eines vorab definierten Events wird Programmcode zur Ausführung gebracht. Nicht mehr und nicht weniger. Natürlich kann der Programmcode wiederum weitere Cloud-Dienste nutzen, Ergebnisse der Ausführung zurück geben usw., aber letztlich geht es darum, ein Programm auszuführen, ohne sich darum kümmern zu müssen, wo es läuft, d.h. auf welchem Server, welche Kapazitäten notwendig wären usw., und erst recht ist man von jeglichen Administrationstätigkeiten befreit. Function Compute sorgt für eine automatische Skalierung, bezahlt wird nach der Anzahl der Funktionsaufrufe, Dauer der Programmläufe und Traffic. Da sich die Preise schnell ändern können, werde ich hier nicht darauf eingehen, weitere Informationen dazu finden sich auf den Informationsseiten. Es ist also wie immer eine genaue Kalkulation notwendig, um festzustellen, ob sich der Einsatz rentiert. Immerhin ist ein gewisser Kontingent pro Monat kostenfrei verfügbar, der für Testzwecke mehr als ausreichend ist.
Nun lässt sich der Code im Function Compute Dienst nicht in jeder beliebigen Programmiersprache erstellen. Es werden Laufzeit- bzw. Ausführungsumgebungen zur Verfügung gestellt, die man wiederum nutzen muss, wenn man seinen Code in die Cloud bringen möchte. Insofern ist eine Anpassung bestehender Funktionen notwendig, falls man nicht lieber direkt mit einer Neuentwicklung startet. Function Compute ist somit kein Ersatz für klassische Server – es ist zumindest bis jetzt nicht möglich, mal eben ohne Aufwand das bevorzugte CMS, CRM, Forum o.ä. „serverless“ zu betreiben, und das ist letztlich auch gar nicht Sinn und Zweck dahinter.
Function Compute unterstützt momentan die Programmiersprachen bzw. Umgebungen Node.js, ergo serverseitiges JavaScript, Python, Java und PHP. Moment, PHP? Ja, richtig gelesen, denn im Gegensatz zu anderen Cloud-Dienstleistern, die ähnliche Dienste anbieten, etwa Amazon mit AWS Lambda oder Microsoft mit Azure Functions ist in der Alibaba Cloud tatsächlich der Einsatz von PHP möglich. AWS hingegen bringt Umgebungen für Node.js, Java, Python und darüber hinaus Go und .NET mit, während bei Microsoft PHP in einer Version 1.x der Laufzeitumgebung als „experimentell“ gekennzeichnet ist, in einer Version 2.x hingegen gar nicht zur Verfügung steht. Weitere Angaben finden sich in der etwas verwirrenden Übersicht bei Microsoft Azure. Bei der Google Cloud Platform ist mir PHP ebenfalls nicht auf den ersten Blick begegnet, wobei natürlich gilt, dass sich gerade im Bereich der Cloud sehr viel in kurzer Zeit ändern kann. Davon abgesehen wird die Unterstützung weiterer oder gar beliebiger Programmiersprachen bei AWS Lambda gerade ausgebaut, so dass der Einsatz von PHP ebenfalls in Kürze möglich sein sollte.
Der Einstieg gelingt am besten mit einem kleinen Beispiel, dankenswerterweise stellt Alibaba Cloud in der Admin-UI von Function Compute eine kleine Entwicklungsumgebung zur Verfügung, die sich insbesondere für das Testen des Codes eignet. Bevor ich die näher auf die Laufzeitumgebung und die eingangs erwähnte Anwendung eingehe, hier zunächst ein Einblick in die Admin-UI.
Die ersten Schritte mit Function Compute
Es dürfte nicht überraschen, dass man sich auch für die Nutzung der Function Compute Dienste analog zu den anderen Diensten anmelden muss – auf die Darstellung des Dialogs habe ich an dieser Stelle verzichtet.
Anlegen eines Services
Zur Strukturierung der jeweiligen Funktionen dienen die so genannten „Services“. D.h. zunächst muss ein „Service“ in der Function Compute (im Folgenden FC abgekürzt) Console angelegt werden, anschließend werden die einzelnen Funktionen in einem Service angelegt. Wenn man so will, sind die Services einfach eine Art Directory, in denen sich Dateien, in dem Fall Funktionen, befinden. Vorab muss jedoch der Ort gewählt werden, die Voreinstellung ist beim ersten Besuch der FC-Console ein Rechenzentrum in China, ich habe mich jedoch für Frankfurt entschieden – aus naheliegenden Gründen.
Nachdem die Location gewählt ist, wird der Service angelegt. Der folgende Screenshot zeigt den entsprechenden Dialog, jedoch hatte ich ihn erst im Nachhinein angefertigt, so dass man im Hintergrund bereits erkennt, dass der Service namens „gnsitetest“ angelegt wurde.
Die Option „Internet Access“ habe ich aktiviert, bei allen anderen Einstellungen den Default beibehalten.
Anlegen einer Funktion
Bewegt man sich per Klick auf den soeben angelegten Service-Namen in die Ebene des Services, erscheint eine entsprechende, noch leere Übersicht der Funktionen.
Eine Funktion will zunächst einmal angelegt werden. Bei Klick auf „Create Function“ gelangt man in einen mehrseitigen Dialog, in dessen erstem Schritt die Laufzeitumgebung, ergo Programmiersprache und ggf. eine Vorlage (Template) ausgewählt wird.
Ich habe mich für PHP entschieden, das in Version 7.2 vorliegt, insofern recht aktuell ist. Dafür stand zum Zeitpunkt der Erstellung auch nur ein Template einer leeren Funktion zur Verfügung.
Im nächsten Schritt wird der so genannte Trigger ausgewählt, also das Event, bei dem der Aufruf der Funktion erfolgen soll. Zur Zeit stehen folgende Trigger zur Verfügung.
Je nach gewähltem Trigger werden unterschiedliche Optionen angegeben, bei Wahl des Object Storage Service (OSS) beispielsweise muss das Bucket und das Event genannt werden, was sich wiederum auf Datei-Operationen wie Erzeugen, Löschen, Kopieren etc. beziehen kann. Damit würde etwa beim Anlegen einer Datei in OSS der Aufruf einer Funktion erfolgen – vorstellbar wären Analysen, Konvertierungen o.ä., der Fantasie sind hier letztlich keine Grenzen gesetzt. Ein Time Trigger kann, analog eines Cronjobs, zeitgesteuert aufgerufen werden, tatsächlich bezieht sich dieser Trigger sogar auf Cron und die Definition eines Cronjobs.
Im Beispiel nutze ich jedoch den HTTP Trigger, der die Funktion aufgrund eines HTTP-Requests aufruft – letztlich analog zu einem PHP-Skript auf einem normalen Web-Server. Es lassen sich die gängigsten Request-Methoden GET und POST wählen, ebenfalls möglich sind aber auch PUT, DELETE oder HEAD. Testweise fiel die Entscheidung auf GET und POST, wobei für den Regelbetrieb auch POST genügt, da die Formulardaten ausschließlich per POST übermittelt werden.
Anschließend werden der Service gewählt – was ich an der Stelle etwas überflüssig empfinde, da man sich bereits in einem Service befindet, andererseits kann während der Konfigurations-Schritte auch ein neuer Service erstellt werden. Wichtig sind jedoch Funktionsname und ggf. Beschreibung, wobei letztere optional ist. Auch wenn bereits die Runtime-Engine gewählt ist, kann an der Stelle erneut eine Auswahl erfolgen.
Scrollt man auf der Seite ein wenig herunter, finden sich darauf bereits das Code-Template und weitere Konfigurationsoptionen wie der maximal zur Verfügung stehende Speicher und die maximale Laufzeit der Funktion. Dabei haben ich es bei den Standard-Werten belassen, interessanter ist hier jedoch der Beispiel-Code, dazu gleich mehr.
Da ich zur Zeit der Aufnahme der Screenshots nur eine geringe Bildschirmauflösung zur Verfügung hatte, zeigt der folgende Screenshot die letzten Zeilen des Beispiel-Codes.
Im folgenden Tab kann eine Konfiguration der Zugriffsrechte erfolgen. Da dies erst relevant wird, wenn man von der Funktion aus auf andere Cloud-Dienste von Alibaba Cloud zugreifen möchte, habe ich dabei die Default-Einstellung beibehalten. Ansonsten können die entsprechend zur Verfügung gestellten Security Policies gewählt werden.
Der letzte Schritt zeigt eine Zusammenfassung bzw. Vorschau der zuvor gewählten Optionen. Ein Klick auf „Create“ erzeugt die Funktion innerhalb des Function Compute Dienstes.
Code-Management – Editor und Debugger
Nach dem Anlegen der Funktion kann die Funktion mit Leben gefüllt werden. Nach Anwahl durch Klick auf den Funktionsnamen gelangt man zunächst in das Overview-Tab (hier nicht dargestellt). Interessanter ist die Code-Ansicht im zweiten Tab. Im „Code-Management“ lassen sich die Quelltexte zwar auch von OSS importieren oder per ZIP-Datei oder als komplettes Verzeichnis hochladen, aber für einen ersten Einstieg eignet sich der zur Verfügung gestellte web-basierte Code-Editor bereits sehr gut.
Wie der folgende Screenshot zeigt, ist die zentrale handler()
-Funktion bereits mit einem Beispiel versehen und steht für eigene Tests bereit. Ebenfalls integriert ist ein web-basierter Debugger, den ich jedoch eher als Simulator bezeichnen würde. Darin lassen sich Requests ausführen und die Ergebnisse anzeigen. Parameter können als Query-String oder Path übermittelt werden, die Request-Methode lässt sich ebenfalls modifizieren, neben dem Body lassen sich die Response-Headerzeilen ansehen, der Statuscode wird ebenfalls dargestellt usw.. Dieser Simulator steht ebenfalls zur Verfügung, wenn man den In-Line-Editor nicht nutzt, sondern seinen Code beispielsweise per Kommandozeilen-Tool deployed hat.
Im Folgenden ein weiterer Screenshot des Request-Simulators und Ausgabe der Response-Header.
Die Ausführungsumgebung von PHP
Nun sehen diese Handler-Funktionen zwar einerseits wie PHP-Funktionen aus, andererseits sind sie im Vergleich zur üblichen Nutzung von PHP doch ein wenig anders. Immerhin bedient sich die PHP-Runtime von Function Compute einiger Standards, so ist der $request
-Parameter PSR-7-konform. Ein Beispiel der Nutzung des HTTP Trigger findet sich in der PHP Handler Dokumentation, es handelt sich um denselben Code, der auch als „leeres“ Template beim Anlegen der Funktion vorhanden ist. Damit lassen sich erste Tests durchführen, jedoch ist die Nutzung des Web-basierten Editors eher suboptimal, denn vermutlich möchte man lieber seine gewohnte Entwicklungsumgebung nutzen. Leider gibt Alibaba Cloud auch in der Dokumentation zur PHP Runtime keine Antwort darauf, um welche Umgebung es sich genau handelt und wie man diese lokal einrichten könnte. Zunächst habe ich daher einen Weg gesucht, um die handler-Funktion möglichst ähnlich einsetzen zu können, dazu wollte ich kein „großes“ Framework einsetzen, das kompatibel zu PSR-7-Response bzw. PSR-7-HTTP-Message-Interfaces ist, insofern fiel die Wahl auf das Slim-Framework. Nur nebenbei bemerkt – tatsächlich setzt das Kubeless Serverless Framework auf Slim für die PHP-Runtime, was mir aber erst später aufgefallen ist. Function Compute hingegen nutzt eine Eigenentwicklung, wobei die einzelnen Komponenten zumindest teilweise auf GitHub zur Verfügung stehen. Letztlich basiert die Umgebung wiederum auf Docker-Images für die jeweils unterstützten Programmiersprachen. Zwecks lokaler Entwicklung oder zum Testen stellt Alibaba Cloud auch die Quelltexte der Function Compute Umgebung zur Verfügung, auch wenn das README etliche chinesische Schriftzeichen beinhaltet, lassen sich die Docker-Kommandos wie gewohnt lesen. Da ich jedoch nicht nur ausführen und testen wollte, sondern daran interessiert war, welches Framework bzw. welche Technologie nun wirklich darunter liegt, habe ich ein wenig weiter gestöbert. Im Dockerfile der PHP-Runtime befindet sich der entscheidende Eintrag – und zwar wird zu Beginn eine Datei „php7.2.tgz
“ geladen (curl https://my-fc-testt.oss-cn-shanghai.aliyuncs.com/php7.2.tgz | tar -zx -C /
) und im Hauptverzeichnis entpackt. Genau dabei handelt es sich um die Ausführungsumgebung. Zwar habe ich dafür kein Repository auf GitHub gefunden, aber da die Datei öffentlich zur Verfügung steht, kann eigentlich nichts dagegen sprechen, sich die Inhalte einmal genauer anzusehen.
In der Datei /var/fc/runtime/php7.2/src/server.php
sind die entscheidenden Angaben vorhanden, und zwar nutzt die PHP-Runtime die ReactPHP Library. Das war auch gleichzeitig die Lösung des Rätsels, wieso das „superglobale“ Array $_POST
nicht gefüllt war – in ReactPHP findet dies default-mäßig keine Verwendung. Den Server-Teil konnte ich jedoch nicht modifizieren, d.h. keine entsprechende Middleware einsetzen, insofern blieb nur ein kleiner Umweg, den ich im Detail hier erläutert habe.
Davon abgesehen ist die Entwicklung recht komfortabel – auch Composer lässt sich wie gewohnt einsetzen, was auch in der Dokumentation erwähnt wird. So ließ sich Schritt für Schritt der notwendige Code aufbauen.
Endlich Code – ein Praxisbeispiel
Um das folgende Beispiel einfach zu halten, werde ich nur auf die relevanten Teile eingehen. Letztlich handelt es sich nur um die Entgegennahme einiger Variablen (E-Mail-Adresse, Nachricht, Google Recaptcha String), Prüfung derselben, Versand einer Mail und Rückgabe einer Antwort, die als JSON codiert ist. Für das Verständnis wichtig erscheinen mir der grundlegende Ablauf, die Composer-Datei und die Rückgabe. Alles andere, d.h. Validierung, Mailversand (mit Einschränkungen, dazu mehr im nächsten Beitrag) usw. unterscheidet sich jedoch nicht von „herkömmlicher“ Programmierung in PHP.
Zunächst einmal zur Strukturierung der Verzeichnisse bzw. Dateien.
geschke@gohlis:~/hugo/fc$ tree . ├── composer.json ├── composer.lock ├── index.php ├── src │ └── geschkenet │ ├── FeedbackAction.php │ └── SendFeedback.php └── vendor ├── autoload.php ├── composer │ ├── autoload_classmap.php │ ├── autoload_files.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_real.php │ ├── autoload_static.php │ ├── ClassLoader.php │ ├── installed.json │ └── LICENSE ├── google [...von Composer generiert...]
Im Hauptverzeichnis liegen die index.php
-Datei, in der sich die handler
-Funktion befindet und die beiden Composer-Dateien. In der index.php
befindet sich die handler()
-Funktion, die bereits aus den Beispielen bzw. dem leeren Template nach dem Anlegen einer Funktion in der Admin-UI bekannt ist. Composer generiert nach den Angaben in der composer.json
alle Dateien im vendor-Verzeichnis bzw. lädt die genutzten Libraries an die geeigneten Stellen – vollkommen analog zur üblichen Verfahrensweise. Der produktive Code befindet sich hier im Verzeichnis src/geschkenet/, insbesondere FeedbackAction,php
, während sich SendFeedback.php
nur um den Mailversand kümmert.
Die composer.json
sieht wie folgt aus:
{ "require": { "google/recaptcha": "^1.2", "ringcentral/psr7": "^1.2", "phpmailer/phpmailer": "^6.0" }, "autoload": { "psr-4": { "Geschkenet\\":"src/geschkenet" } } }
Als Libraries werden nur Googles Recaptcha, PHPMailer und das bereits im Function Compute Beispielcode zu findende Ringcentral/PSR7 verwendet. Dem Autoloader ebenfalls bekannt gegeben, und zwar für den Namespace „Geschkenet„, werden die Klassen im Verzeichnis „src/geschkenet/„, damit können die Klassen ohne explizites Include der Dateien direkt verwendet werden.
Die index.php
ist sehr kurz gehalten:
<?php require_once __DIR__ . "/vendor/autoload.php"; use RingCentral\Psr7\Response; use \Geschkenet\FeedbackAction; function handler($request, $context): Response { $fba = new FeedbackAction(); return $fba->handleFeedback($request); }
Auch hier finden sich keine Überraschungen – zunächst wird der Autoloader geladen, dann werden die Response- und die FeedbackAction-Klasse importiert. Die handleFeedback
-Methode wird mit dem $request
-Objekt aufgerufen, die Rückgabe der Methode ist ein Response
-Object, das wiederum als Rückgabe der handler()
-Funktion dient. Damit ist die Rückgabe-Typdeklaration erfüllt.
Der produktive Code befindet sich somit in der Klasse FeedbackAction, die relevanten Teile in aller Kürze:
<?php namespace Geschkenet; use \RingCentral\Psr7\Response; use \Geschkenet\SendFeedback; class FeedbackAction { public function __construct() { } [...] public function handleFeedback($request): Response { // filter stuff $errors = []; $data = []; $body = $request->getBody()->getContents(); // this is only possible once parse_str($body, $data); $email = isset($data['formemail']) ? $this->sanitizeEmail($data['formemail']) : ''; if ($email === '') { $errors[] = ['email' => 'Ungültige oder leere E-Mail-Adresse']; } [...more validation stuff...] if (count($errors)) { $result = ['success' => false, 'message' => 'Fehler bei der Verarbeitung Ihrer Anfrage', 'errors' => $errors]; } else { $result = ['success' => true, 'message' => 'Vielen Dank - Ihre Nachricht wurde übermittelt.']; } return new Response( 200, [ "Content-Type" => "application/json", ], json_encode($result) ); } }
Die Arbeit wird in der Methode handleFeedback()
erledigt. Im ersten Abschnitt werden mittels parse_str()
die per POST-Methode übermittelten Daten ins $data
-Array überführt. Danach folgt die Validierung bzw. Säuberung, hier beispielhaft anhand der E-Mail-Adresse im Feld $data['formemail']
dargestellt. Falls keine oder eine ungültige E-Mail-Adresse übermittelt wurde, wird das Array $errors
gefüllt und mit einer Fehlermeldung versehen. Analog werden Nachricht, Name und Recaptcha-Code geprüft.
Falls $errors
Inhalte enthält, wird eine allgemeine Fehler-Nachricht generiert und in das $result
-Array gesetzt, ebenso wird das komplette $errors
-Array dem Client wieder zur Verfügung gestellt, so dass dieser entsprechende Ausgaben für den User generieren kann. Wie hier zu erkennen ist, wird auch im Fehlerfall kein HTTP-Fehlercode zurück gegeben, denn alle notwendigen Angaben finden sich im $result
-Array. Anhand dessen sorgt der JavaScript-Code im Client dafür, dass dem User eine Erfolgs- bzw. Fehlermeldung angezeigt wird.
Die Nutzung der Response-Klasse ist bereits aus dem Code-Template bekannt, hier wird nur noch der Content-Type zusätzlich gesetzt. Das $result
-Array wird als JSON codiert zurück gegeben.
Wie man den produktiven Code letztlich strukturiert, ist natürlich jedem selbst überlassen. Ich habe mich hier für ein möglichst einfaches Vorgehen entschieden, schließlich handelt es sich um keine umfangreiche „Business-Logik“, sondern um eine ebenfalls sehr simple Funktionalität. Der Mailversand wird Thema des nächsten Artikels sein, dazu ist – beinahe möchte man sagen wie erwartet – wiederum ein wenig Vorarbeit notwendig.
CLI-Tool fcli – Deployment per Kommandozeile
Nachdem man möglicherweise lokal getestet hat, evtl. unter Zuhilfenahme der Function-Compute-Docker-Images, möchte man den Code natürlich im Function Compute Dienst deployen. Auch für derartige Zwecke stellt Alibaba Cloud ein Kommandozeilen-Tool zur Verfügung, getauft auf den Namen fcli. Es handelt sich erneut um ein Programm, das in Googles Go geschrieben ist. Nach dem Download erhält man ein Binary, das ich nach /usr/local/bin kopiert habe.
Aktuelle Versionen befinden sich im GitHub-Repository von fcli.
wget https://gosspublic.alicdn.com/fcli/fcli-v1.0.1-linux-amd64.zip unzip fcli-v1.0.1-linux-amd64.zip sudo mv fcli /usr/local/bin/
Vor der ersten Benutzung müssen fcli zunächst die Zugangsdaten bekannt gegeben werden:
geschke@gohlis:~$ fcli Config file does not yet exist: /home/geschke/.fcli/config.yaml ? Alibaba Cloud Account ID <alibaba cloud id> ? Alibaba Cloud Access Key ID <access key id> ? Alibaba Cloud Access Key Secret <secret key> ? Default region name eu-central-1 Store the configuration in: /home/geschke/.fcli fcli: function compute command line tools
Neben dem Haupt-User wäre hier auch die Nutzung von Sub-Usern denkbar, sofern sie die geeigneten Zugriffsrechte besitzen.
Das Tool fcli ist mächtig, leider ist die Dokumentation bislang rar gesät. Ich habe mich zunächst in der eingebauten Shell bewegt, mit der man interaktiv auf den Function Compute Service zugreifen kann.
geschke@gohlis:~$ fcli shell Welcome to the function compute world. Have fun! >>> help Commands: attach attach the policy to a role cd change the current resource clear clear the screen config config the fcli detach detach the policy from a role dlc download the function code exit exit the program [...gekürzt...] >>> ls gnsitetest >>> cd gnsitetest >>> ls gnsubmit >>> cd gnsubmit >>> ls gnsubmit >>> info { "Header": { "Access-Control-Expose-Headers": [ "Date,x-fc-request-id,x-fc-error-type,x-fc-code-checksum,x-fc-invocation-duration,x-fc-max-memory-usage,x-fc-log-result,x-fc-invocation-code-version" ], "Content-Length": [ "401" ], "Content-Type": [ "application/json; charset=utf-8" ], "Date": [ "Sun, 18 Nov 2018 22:35:40 GMT" ], "Etag": [ "8e6d86c7579686bdcecbf08a7df3f26b" ], "X-Fc-Request-Id": [ "dcf8c9b4-2d9d-d330-4f58-8fc891b115ec" ] }, "functionId": "843a476e-e117-4eb5-bcea-something", "functionName": "gnsubmit", "description": "", "runtime": "php7.2", "handler": "index.handler", "initializer": null, "timeout": 60, "initializationTimeout": 3, "memorySize": 512, "reservedContainerCount": 0, "codeSize": 471, "codeChecksum": "15985409832241242670", "environmentVariables": {}, "createdTime": "2018-11-03T23:23:12Z", "lastModifiedTime": "2018-11-16T17:35:04Z" }
Das Kommando ls
zeigt abstrakt „Ressourcen“ an – letztlich handelt es sich in der ersten Ebene um die Services und in der zweiten Ebene um die darin definierten Funktionen. Der oben angelegte Service „gnsitetest“ ist somit in der ersten Ebene zu finden, in die man wie in einer Shell gewohnt mit „cd <verzeichnisname>
“ wechseln kann. Danach wird die Liste der Funktionen ausgegeben – bislang nur „gnsubmit
„. Dazu können mit „info
“ diverse Informationen ausgegeben werden.
Ebenfalls kann fcli zum Anlegen von Funktionen und Hochladen des Codes verwendet werden. Das folgende Beispiel geht davon aus, dass sämtliche Dateien in einem Verzeichnis /home/geschke/hugo/fc/ liegen, und zwar in der weiter oben angegebenen Struktur. Von der fcli-Shell aus lässt sich das Anlegen und Hochladen in einem Schritt erledigen:
geschke@gohlis:~/hugo$ fcli shell Welcome to the function compute world. Have fun! >>> ls gnsitetest >>> cd gnsitetest >>> mkf feedbackfunc -h index.handler --runtime php7.2 -d /home/geschke/hugo/fc
Leider erfolgt kein sichtbares Feedback nach der Ausführung dieses Kommandos. Ob die Funktion wirklich angelegt wurde, lässt sich aber leicht überprüfen:
>>> info feedbackfunc { "Header": { "Access-Control-Expose-Headers": [ "Date,x-fc-request-id,x-fc-error-type,x-fc-code-checksum,x-fc-invocation-duration,x-fc-max-memory-usage,x-fc-log-result,x-fc-invocation-code-version" ], "Content-Length": [ "406" ], "Content-Type": [ "application/json; charset=utf-8" ], "Date": [ "Mon, 19 Nov 2018 09:36:31 GMT" ], "Etag": [ "87bd9ec1d0a2cfcd72b3a18ea6e6144b" ], "X-Fc-Request-Id": [ "ac9ed00c-533b-162a-1bc2-28eee86a04a0" ] }, "functionId": "9a5c30e0-ad36-438f-bf18-something new", "functionName": "feedbackfunc", "description": "", "runtime": "php7.2", "handler": "index.handler", "initializer": "", "timeout": 30, "initializationTimeout": 30, "memorySize": 128, "reservedContainerCount": 0, "codeSize": 271278, "codeChecksum": "1786756075938894711", "environmentVariables": {}, "createdTime": "2018-11-19T09:35:01Z", "lastModifiedTime": "2018-11-19T09:35:01Z" }
Damit ist die Funktion zwar angelegt worden, aber es fehlt noch die Definition eines Triggers, d.h. des Ereignisses, aufgrund dessen die Funktion ausgeführt werden soll. Zwar ist die Definition ebenfalls mit dem fcli-Tool möglich, aber ich habe mich dazu wieder in die web-basierte Admin-UI begeben.
Auch das Updaten der Funktion ist via fcli einfach möglich:
>>> upf feedbackfunc -h index.handler --runtime php7.2 -d /home/geschke/hugo/fc
Erneut wird kein Feedback ausgegeben, aber wie man sich mittels „info
„-Kommando überzeugen kann, wurde die Funktion aktualisiert und ggf. geänderter Code hochgeladen.
Falls man einmal unsicher sein sollte, welcher Code in welcher Version deployed worden ist, lassen sich die Quelltexte innerhalb der Admin-UI auch wieder herunter laden. Leider scheint es nicht möglich zu sein, andere Dateien außer dem index-Handler, d.h. der index.php
mittels In-line-Editor in der Admin-UI zu editieren.
Fazit und Ausblick
So viel – und diesmal war es wirklich viel – zum Thema Function Compute, dem „Serverless Computing“ Dienst bei Alibaba Cloud. Oder nein, fast wäre richtig, denn im nächsten Teil widme ich mich noch kurz dem Mailversand von PHP innerhalb der Function Compute Umgebung. Natürlich gäbe es noch weitaus mehr zu entdecken, etwa die Nutzung weiterer Dienste der Alibaba Cloud und deren Verwendung innerhalb des Function Compute Services, oder die Verknüpfung mit eigenen Domains, sowie natürlich die Nutzung der weiteren, zur Verfügung stehenden Programmiersprachen und -umgebungen. Jedoch habe ich es gerade als besonders sympathisch empfunden, dass der Serverless-Dienst der Alibaba Cloud eine PHP-Runtime bereit stellt, denn somit konnten die gewohnte Umgebung verwendet werden, die auch prima funktioniert. Tatsächlich verwende ich Function Compute für die hier geschilderte Feedback-Funktion momentan auch produktiv – angesichts des kostenlosen Kontingents auch bislang ohne einen Cent zu bezahlen.
Im nächsten und voraussichtlich letzten Teil werde ich schildern, welche Hürden es beim Mailversand innerhalb des PHP-Codes im Function Compute Service gab und wie diese zu überspringen waren.