GitHub Webhooks: Webhook entgegennehmen
Dies ist Folgeartikel 2 aus meiner Serie zu GitHub-Webhooks.
Im letzten Artikel habe ich beschrieben, wie ich den PSGI-Server eingerichtet habe, aber die eigentliche Applikation zum Empfangen des Webhooks bin ich schuldig geblieben. Hier ist sie nun in Gänze:
Das Skript
- use Plack::App::GitHub::WebHook;
- use JSON::MaybeXS;
- use Time::HiRes;
- use constant TMPDIR => '/home/webhook/json/tmp';
- use constant QUEUEDIR => '/home/webhook/json/queue';
- Plack::App::GitHub::WebHook->new(
- hook => sub {
- my ($payload, $event, $delivery, $logger) = (@_);
- my $now = Time::HiRes::time();
- my $tmp_filename = sprintf '%s/%.6f.%d', TMPDIR, $now, $$;
- my $final_filename = sprintf '%s/%.6f.%d', QUEUEDIR, $now, $$;
- $logger->info("got `$event' event, will write to `$final_filename'");
- my $data = {
- 'event' => $event,
- 'delivery' => $delivery,
- 'received' => sprintf('%.0f', $now),
- 'payload' => $payload,
- };
- my $json = encode_json($data);
- open my $fh, '>', $tmp_filename or do {
- $logger->error("can't open `$tmp_filename': $!");
- return;
- };
- print $fh $json;
- close $fh or do {
- $logger->error("can't close `$tmp_filename': $!");
- return;
- };
- rename $tmp_filename, $final_filename or do {
- $logger->error("error moving `$tmp_filename' to `$final_filename': $!");
- return;
- };
- },
- secret => 'HEREgoesTHEpassword',
- access => 'all',
- )->to_app;
Integration
Diese Datei liegt, wie in der systemd-Servicebeschreibung
referenziert, unter /home/webhook/webhook-receiver.psgi
.
Das Modul Plack::App::GitHub::WebHook kümmert sich um die Details des
Github-Webhook-Protokolls. Per hook => sub{}
übergebe ich den Code,
der bei einem eingehenden Aufruf ausgeführt wird und per to_app
wird
das ganze in eine PSGI-Codereferenz umgewandelt, so dass plackup
etwas damit anfangen kann.
Ablauf
Der Hook bekommt als Eingabeparameter die $payload
, das sind die
(bereits dekodierten) JSON-Daten. Das Datenformat ist in der
GitHub-REST-API beschrieben. Die Art des Events steht dabei nicht
in der Payload, sondern wird als $event
übergeben (beim Aufruf des
Webhooks steht der Event-Typ in einem HTTP-Header, nicht in den
POST-Daten).
Danach baue ich mir die Dateinamen zusammen – ich nehme dafür einfache
Timestamps, dann sind die Dateinamen schon passend sortiert. Damit
sich die Nachrichten nicht gegenseitig überschreiben, benutze ich
Mikrosekunden-Auflösung und hänge nochmal die aktuelle PID ($$
)
hintendran.
Das Logging über den mitgelieferten $logger
funktioniert leider
nicht wirklich.[1]
Damit ich später weiß, was das für ein Event-Typ war (wie gesagt: die Information kommt extra), packe ich die Payload zusammen mit dem Event-Typ und ein paar anderen Daten nochmal in ein neues JSON-Objekt ein, welches ich dann per JSON::MaybeXS in einen String serialisiere und in eine Datei in ein Zwischenablage-Verzeichnis schreibe.
Wenn das passiert ist, schiebe ich die Datei von dort ins eigentliche Queue-Verzeichnis, wo sich dann der Queue-Handler drauf stürzen kann (siehe Folgeartikel).
Die getrennten Verzeichnisse und das atomare Verschieben[2] stellen sicher, dass das Folgeskript im eigentlichen Queue-Verzeichnis nur Dateien sehen kann, die bereits fertig geschrieben worden sind. Eine halbe Datei einzulesen würde nur zu Fehlern führen.
Konfiguration
In TMPDIR
wird das JSON geschrieben und dann, wenn es fertig
geschrieben ist, und per rename
nach QUEUEDIR
verschoben.
Das secret
ist der geheime Schlüssel (im Klartext), den man in der
GitHub-Weboberfläche bei Anlage des Hooks eintragen muss. Mein echter
Schlüssel ist natürlich ein anderer. Damit das mit dem Secret klappt,
muss man übrigens zusätzlich Plack::Middleware::HubSignature
installieren.
Wenn access
auf 'github'
steht, werden nur eingehende Anfragen von
GitHub-Servern erlaubt. Das klappt aber bei mir nicht, vermutlich
weil sich die IP-Ranges von GitHub geändert haben. Ich erlaube daher
den Zugriff für 'all'
und vertraue auf die
HTTPS-Transportverschlüsselung in Verbindung mit meinem Secret.
$logger
klappt es ebenfalls
weder unter plackup noch unter Starman.Letztendlich bin ich dem Ratschlag „nimm was richtiges, nimm uWSGI“ gefolgt (uWSGI ist nochmal eine Nummer größer und leistungsfähiger und kann nicht nur PSGI…) und war dann damit erfolgreich: STDOUT geht da zwar auch nicht, aber der
$logger
schreibt jetzt tatsächlich ins
Log! Dazu musste ich aber einiges umbauen, wofür ich jetzt aber nicht
den Vorgängerartikel wieder komplett umschreiben will. Wenn ich noch
Zeit und Lust habe, gibt es ganz am Ende einen Bonusartikel, wie man
uWSGI statt plackup benutzt. Zum einfachen Nachkochen reicht das
ja auch erstmal so ohne Logs.
Renee B am : Renee B via Twitter
Netz - Rettung - Recht am : Wellenreiten 01/2019