Web-Services über HTTP und das SOAP-Protokoll überwinden locker Hürden wie unterschiedliche Betriebssysteme oder Programmiersprachen. Heute stellen wir die Windows-Applikation Photoshop über einen Web-Service ins lokale Netz und fernsteuern sie von einem Linux-PC aus.
Meine digitalen Fotos bearbeite ich üblicherweise mit Gimp, der so ziemlich alles, was Adobes Photoshop kann, nachbildet -- bis auf eine Ausnahme: Über die Funktion Autolevels, die die Belichtung eines Fotos nachträglich automatisch korrigiert, verfügt bislang nur das 600 Dollar teure Adobe-Produkt. Da ich meist auf einem Linux-PC arbeite und üblicherweise den Windows-PC mit Photoshop irgendwo im Netz stehen habe, aber zu faul bin, dauernd hin- und herzulaufen, dachte ich mir: Warum richte ich auf der Windows-Kiste nicht einfach einen Web-Service ein, über den ich von der Linux-Kommandozeile aus Photoshop aktivieren, ihm Bilder schicken, mittels AutoLevel korrigieren lassen und gleich wieder abholen kann?
Web-Services ist ja das neue Buzzword für Manager in der Softwareindustrie. Vielleicht werden die über das Web und das SOAP-Protokoll ansteuerbaren Programme bald den Weg von Video-On-Demand, Cue-Cat und Push-Technologie gehen und durch die Toilette des Internets gespült, aber, hey, wir sind ja noch jung und schrecken vor keinem Modegag zurück!
Mittels SOAP
, dem Simple Object Access Protocol, kann ein Client
im Netz einen Server anweisen, Objekte anzulegen, Methoden mit
Parametern auszuführen und die Ergebnisse wieder über SOAP
zurückzuliefern. SOAP
ist nur ein Protokoll, das Objekte, deren
innere physische Struktur üblicherweise stark von der verwendeten
Programmiersprache und dem Betriebssystem abhängt, auf höherere Ebene
logisch als XML darstellt, damit Client und Server sich unterhalten
können, auch wenn es sich z. B. um eine .net-Applikation auf einem Windows-PC
und ein Perl-Skript auf einer Linux-Kiste handelt. Oder ein
Visual-Basic-Script und eine Java-Applikation.
Als Transportmedium für das erzeugte XML dient üblicherweise das
Webprotokoll HTTP, es kann aber genausogut Mail-SMTP oder Jabber
sein, alles geht.
Das heute vorgestellte Skript soapc.pl
nimmt zwei Parameter: den
Hostnamen des Windows-Rechners, auf dem Photoshop installiert ist und
den Namen der Bilddatei. Wie Listing soapc.pl
zeigt, liest es
die Bilddatei ein, verbindet sich mittels des praktischen
Moduls SOAP::Lite
von Pavel Kulchenko mit dem Windows-Rechner
und teilt ihm über SOAP mit, die pshopit()
-Methode des
urn:Photoshop/Hoelle
-Service (ein Phantasiename, dient nur
der eindeutigen Identifizierung) aufzurufen und ihr die Bilddaten
(in $data
) zu übergeben.
Abbildung 1: Der Client schickt eine Anfrage mit Bild an den Server |
Der Service sendet die Daten des transformierten Bildes zurück,
die result()
-Methode in Zeile 27 in Listing soapc.pl
fängt sie ein und die Variable $rc
in soapc.pl
nimmt sie auf. Es braucht uns nicht zu kümmern,
dass das Bild binäre Daten enthält -- SOAP steht dafür grade,
dass der Datensalat schön in gültiges XML umgewandelt wird, indem
es sie Base64-kodiert.
Abbildung 1 zeigt den abgeschickten HTTP-Request mit den typischen und
einigen SOAP-spezifischen HTTP-Headern.
Anschließend
folgt im Body der Nachricht ein XML-Dokument, das dem Server
mitteilt, was zu tun ist und die Bilddaten (in Abbildung 1
mit /9j/4 ### BILDDATEN ### BQf/Z
abgekürzt,
da es sich um 64 Kilobytes Base64-Daten handelt) überreicht.
Eine ausgezeichnete Referenz zum SOAP-Protokoll mit vielen Anwendungsbeispielen
ist [3].
Der Server (implementiert in Listing soaps.pl
)
entpackt die Requestdaten, speichert das Bild unter
einem temporären Namen auf der Festplatte, nutzt das CPAN-Modul
Win32::OLE
, um Adobes Photoshop fernzusteuern. Letzteres
lädt die temporäre Bilddatei von den Platte, führt die Funktion
Image->Enhance->Autolevels aus, skaliert das Bild
auf 600 Pixel Breite bei gleichen Proportionen und speichert es
im PNG-Format auf der Platte ab. Der SOAP-Server kratzt das Bild
von der Platte und schickt es zurück an den SOAP-Client auf der
Linux-Kiste, der es auf STDOUT ausgibt. Abbildung 2 zeigt, was
tatsächlich über die Leitung geht.
Abbildung 2: Der Server antwortet mit dem konvertierten Bild |
Für SOAP gibt's sowohl client- als auch serverseitig haufenweise Implementierungen: ob in Java, Perl, C++ oder C#, alle reden (fast) die gleiche Sprache miteinander.
Da SOAP wegen eleganter Firewallaustricksung hauptsächlich über
HTTP serviert wird, liegt es nahe, unseren SOAP-Server mit
einem Webserver zu verkuppeln. Und da Performance in diesem Fall
keine Rolle spielt, implementieren wir's einfach, wie in
Listing soaps.pl
gezeigt, mittels SOAP::Lite
als CGI-Skript auf einem Apache-Server. Dem Client ist egal, wie
der Server implementiert ist -- Hauptsache er spricht SOAP.
Aber wie kommt ein Webserver auf ein Windows-98/2000/NT-Betriebssytem? Ganz
einfach: Mit einem fertig compilierten, kinderleicht zu installierenden
Apache von www.apache.org
, wie der Abschnitt ``Installation'' weiter
unten zeigt.
Der SOAP-Code in Client und Server ist wirklich kompakt. Schon mit
10 Zeilen Code und SOAP::Lite
schaffen wir ein voll
funktionsfähiges Client/Server-Paar!
Die Codezeilen 22 bis 27 im Client soapc.pl
und
die Zeilen 9 bis 11 im Server soaps.pl
erledigen alles.
Wie funktioniert der Client soapc.pl
?
Die Zeilen 7 und 8 in soapc.pl
schalten
Perls Warnungen bei schlampigem Code an, zwingen zu Disziplin
beim Deklarieren von Variablen und verbieten zweifelhafte Perl-Konstrukte
wie weiche Referenzen und benutzerdefinierte Barewords.
Zeile 10 zieht das allmächtige SOAP::Lite
-Modul herein.
Zeile 12 erfasst die beiden hoffentlich vorliegenden
Kommandozeilenparameter, die den Namen (oder die IP-Adresse) der
Windows-Kiste und den Namen der
zu bearbeitenden Bilddatei angeben. Fehlt einer oder beide, bricht
Zeile 14 das Programm ab. Die Zeilen 17 bis 20 öffnen die JPG-Datei
und lesen sie ganz in den Skalar $data
ein.
Zeile 22 erzeugt ein neues SOAP::Lite
-Objekt, dessen Methoden
uri()
,
proxy()
,
pshopit()
und
result()
die nachfolgenden
Zeilen mit der ->
-Syntax aufrufen -- der Trick ist einfach, dass
jede Methode wieder eine Referenz auf das ursprüngliche
SOAP::Lite
-Objekt zurückgibt und so die ``Verkettung'' dieser
Aufrufe erlaubt. Sogar die new
-Methode kann entfallen, da
SOAP::Lite
diese automatisch aufruft.
uri()
spezifiziert einen eindeutigen Uniform Resource Name
für die Applikation.
proxy()
legt über einen URL den Einstiegspunkt auf dem
dem SOAP servierenden HTTP-Server fest.
pshopit()
ist jedoch keine standardmäßig von SOAP::Lite
zur Verfügung gestellte Methode -- vielmehr wird sie samt
dem Wert des Parameters $data
an den SOAP-Server
weitergeleitet, der sie ausführt und die Ergebnisse zurückschickt,
sodass sie result()
aufschnappt und an $rc
weitergibt.
Ist beispielsweise
die IP-Adresse des Windows-Rechners
auf dem internen Netzwerk
192.168.0.3, die JPG-Datei führt den
Namen in.jpg
und die Ergebnisdatei soll in out.png
liegen,
führt folgender Aufruf zum Ziel:
soapc.pl 192.168.0.3 in.jpg >out.png
Wen die hin- und herflitzenden XML-Nachrichten interessieren, der kann mit
use SOAP::Lite +trace => qw(debug);
in soapc.pl
den Debug-Modus anstellen
und STDERR auf der Kommandozeile in eine Logdatei umleiten:
soapc.pl 192.168.0.3 in.jpg 2>log.txt >out.png
01 #!/usr/bin/perl 02 ########################################### 03 # SOAP client 04 # soapc.pl host image_file 05 # Mike Schilli, 2002 (m@perlmeister.com) 06 ########################################### 07 use warnings; 08 use strict; 09 10 use SOAP::Lite; 11 12 my($windows_host, $file) = @ARGV; 13 14 die "usage: $0 host file\n" 15 unless defined $file; 16 17 open FILE, "<$file" or 18 die "Cannot open $file"; 19 my $data = join '', <FILE>; 20 close FILE; 21 22 my $rc = SOAP::Lite 23 -> uri("urn:Photoshop/Hoelle") 24 -> proxy("http://$windows_host" . 25 "/cgi-bin/soaps.pl") 26 -> pshopit($data) 27 -> result; 28 29 print $rc;
Auch soaps.pl
ist sehr kompakt -- nur die Zeilen 7 bis 11 implementieren
den eigentlichen SOAP-Server als CGI-Skript. Zeile 10 teilt dem Skript
mit, dass es einfach alle ankommenden Methodenaufrufe an das Package
Photoshop::Hoelle
weitergeben soll, das weiter unten definiert ist.
Die oben erwähnte Methode pshopit()
ist so ein Fall: Der Client
schickt sie, und der Server macht einfach
Photoshop::Hoelle->pshopit()
daraus. Fertig ist der SOAP-Server.
Was soaps.pl
etwas haariger gestaltet, ist, dass das CGI-Skript
eine proprietäre Windows-Applikation wie Photoshop fernsteuern muss.
Das geht in der Windows-Welt über die
OLE-(Object Linking and Embedding)-Schnittstelle, der so ziemlich jedes
Windows-Programm (wie auch Word, Excel etc.) gehorcht. [2] und [5] schildern
detailliert, wie das geht.
Prinzipiell zieht man einfach das ActivePerl standardmäßig beiligende
Modul Win32::OLE
herein. use Win32::OLE::Const
mit dem Parameter
'Photoshop'
lädt die unter Windows gängige Type Library für
Photoshop und kann so auf Hunderte von Photoshopvariablen zugreifen.
Oder wusste jemand bereits, dass der Code für die Autolevel-Funktion
1098216559
ist? Ich dachte es mir.
Adobe bietet unter [4] ein SDK (Software Developer Kit) an, das Beispiele anführt, wie man Photoshop über OLE fernsteuern kann. Sogar ein kleines Perl-Skript ist dabei! Allerdings ist die Dokumentation unvollständig und so etwas führt gerade bei einem Closed-Source-Produkt schnell in die Sackgasse.
Die Methode
pshopit()
erhält neben dem Klassennamen (es wird mit
Photoshop::Hoelle->pshopit()
aufgerufen) auch
die binären Bilddaten als $data
und speichert sie in den Zeilen 30-34 sofort in einer temporären
Datei auf der Festplatte, da die Photoshop-Fernsteuerung sich darauf
beschränkt, Menüpunkte wie File->Open auszuwählen, wir
aber keine Daten direkt ``reinpumpen'' können.
Die vorgestellte
Implementierung geht davon aus, dass jeweils nur ein Client gleichzeitig
andockt -- hämmerten mehrere gleichzeitig, kämen sich die temporären
Dateien ins Gehege und auch Photoshop käme wahrscheinlich ins Schleudern.
Die binmode
-Funktion in Zeile 32 ist unter Windows lebenswichtig, da
sonst binäre Daten nicht richtig abgespeichert werden.
Zeile 37 öffnet Photoshop. Falls es schon lief, kommt die bereits laufende Instanz zum Einsatz. Zeile 39 lässt es vom Desktop verschwinden, damit eventuell unter Windows arbeitende Leute nicht gestört werden.
Aktionen in Photoshop wie das Öffnen einer Datei, der Aufruf der
Autolevel-Funktion, das Verkleinern oder das Abspeichern unter
einem Namen in einem bestimmten Format erledigt die Play()
-Methode
eines sogenanten Control-Objektes, das wiederum die
MakeControlObject
-Methode des Win32::OLE
-Objektes erzeugt,
das letzteres nicht mal interpretiert sondern an Photoshop weiterreicht.
Verlangt die auszuführende Photoshop-Funktion Parameter,
gelangen diese über ein Deskriptor-Objekt hinein, das von Photoshop mit
der MakeDescriptor
-Methode des Win32::OLE
-Objektes erzeugt wird und
eine Reihe von Wertepaaren verschiedener Typen aufnehmen kann.
Mit PutPath
, PutBoolean
, PutObject
etc. gelangen die Wertepaare
in das Deskriptor-Objekt.
Woher ich die Parameter für diese irren Funktionen weiss? Adobe hat sie auch nicht dokumentiert, sondern liefert im SDK die Sourcen für einen sogenannten Listener-Plugin mit, den man in Photoshop einstöpselt, die gewünschten Aktionen von Hand mit der Maus ausführt, und der dann eine Art C-Code in eine Logdatei schreibt, der diese Aufgaben automatisiert.
Ganz sauber ist die Sache nicht -- so gibt es Probleme, falls etwas schiefgeht, denn die Methoden melden auftretende Fehler nicht. Für einfache Zwecke, in denen meist eh alles glatt geht, reicht es jedoch.
So lassen die Zeilen 42 bis 46 Photoshop die temporäre Datei öffnen, 49 bis 52 führen die Autolevel-Funktion aus, 56 bis 67 verkleinern das Bild und 69 bis 90 speichern es als PNG ab. Die Parameter habe ich 1:1 vom Listener-Plugin übernommen.
Zeile 81 wandelt den *.jpg
-Dateinamen in eine *.png
-Endung um
und anschließend speichert Photoshop die Datei im PNG-Format auf
der Platte ab. Die Zeilen 92 bis 96 lesen die binären Daten
(wieder wichtig: das binmode
-Kommando) in den Skalar $data
ein,
der sie in Zeile 100 an den Aufrufer und damit an den SOAP-Client
zurückgibt. Die Zeilen 97 und 98 räumen die temporären Dateien
wieder ab.
001 #!/perl/bin/perl 002 ############################################ 003 # Photoshop SOAP server 004 # Mike Schilli <m@perlmeister.com> 005 ############################################ 006 007 use SOAP::Transport::HTTP; 008 009 SOAP::Transport::HTTP::CGI 010 ->dispatch_to("Photoshop::Hoelle") 011 ->handle; 012 013 ############################################ 014 package Photoshop::Hoelle; 015 016 use Win32::OLE; 017 use Win32::OLE::Const 'Photoshop'; 018 019 ############################################ 020 sub pshopit { 021 ############################################ 022 my($self, $data) = @_; 023 my($desc, $control, $pngfile); 024 025 my $class = "Photoshop.Application"; 026 my $tmpfile = "c:\\tmp\\ps.jpg"; 027 028 unlink $tmpfile; 029 030 open FILE, ">$tmpfile" or 031 die "Cannot open tmp file $tmpfile"; 032 binmode FILE; 033 print FILE $data; 034 close FILE; 035 036 # An Photoshop andocken 037 my $ps = Win32::OLE->new($class); 038 039 $ps->{Visible} = 0; 040 041 # Datei öffnen 042 $desc = $ps->MakeDescriptor(); 043 $control = $ps->MakeControlObject(); 044 $desc->PutPath(phKeyNull, "$tmpfile"); 045 $control->Play(phEventOpen, $desc, 046 phDialogSilent); 047 048 # AutoLevels 049 $desc = $ps->MakeDescriptor(); 050 $control = $ps->MakeControlObject(); 051 $desc->PutBoolean(phKeyAuto, 1); 052 $control->Play(phEventLevels, $desc, 053 phDialogSilent); 054 055 # Resize auf 600*800 056 $desc = $ps->MakeDescriptor(); 057 $control = $ps->MakeControlObject(); 058 $desc->PutUnitDouble(phKeyWidth, 059 phUnitPixels, 600); 060 $desc->PutBoolean( 061 phKeyConstrainProportions, 1); 062 $desc->PutEnumerated( 063 phKeyInterfaceIconFrameDimmed, 064 phTypeInterpolation, 065 phEnumBicubic); 066 $control->Play(phEventImageSize, $desc, 067 phDialogSilent); 068 069 $control = $ps->MakeControlObject(); 070 my $d1 = $ps->MakeDescriptor(); 071 my $d2 = $ps->MakeDescriptor(); 072 $d2->PutEnumerated( 073 phKeyPNGInterlaceType, 074 phTypePNGInterlaceType, 075 phEnumPNGInterlaceNone); 076 $d2->PutEnumerated(phKeyPNGFilter, 077 phTypePNGFilter, 078 phEnumPNGFilterAdaptive); 079 $d1->PutObject(phKeyAs, 080 phClassPNGFormat, $d2); 081 ($pngfile = $tmpfile) =~ s/jpg$/png/; 082 $d1->PutPath(phKeyIn, $pngfile); 083 $d1->PutBoolean(phKeyLowercase, 1); 084 $control->Play(phEventSave, $d1, 085 phDialogSilent); 086 087 $desc = $ps->MakeDescriptor(); 088 $control = $ps->MakeControlObject(); 089 $control->Play(phEventClose, $desc, 090 phDialogSilent); 091 092 open FILE, "<$pngfile" or 093 die "Cannot open tmp file $pngfile"; 094 binmode FILE; 095 my $data = join '', <FILE>; 096 close FILE; 097 unlink $pngfile; 098 unlink $tmpfile; 099 100 return $data; 101 }
Auf www.apache.org
unter
dist/httpd/binaries/win32
steht apache_1.3.24-win32-x86-no_src.msi
für Windows als vollautomatisches Installationspaket bereit.
Schnell durch den Installationsdialog durchgeklickt und immer die
Standardeinstellungen übernommen, kommt ohne Mühe
-- und ohne Reboot! -- ein Apache auf die Windows-Machine, der über
einen Eintrag in der Startleiste unter
``Apache Group''->``Apache Server''->``Start Apache in Console''
gestartet wird.
Falls auf der Windows-Maschine noch kein Perl installiert ist, holen wir das schnell nach, indem wir das gute ActivePerl von www.activestate.com runterladen und ebenfalls durch den Installationsdialog klicken.
Das Modul SOAP::Lite
lässt sich mit dem ActivePerl beiliegenden
Paketmanager ppm
abholen
und installieren:
dos> ppm dos> install SOAP::Lite
Anschließend muss das heute vorgestellte CGI-Skript soaps.pl
in das
cgi-bin
-Verzeichnis des Webservers wandern, typischerweise
C:\Program Files\Apache Group\Apache\cgi-bin
. Es ist darauf
zu achten, dass die erste Zeile von soaps.pl
den korrekten Pfad
zur Perl-Installation enthält -- im Gegensatz zum sonst gängigen
#!/usr/bin/perl
kann es unter Windows auch schon mal
#!/perl/bin/perl
oder ähnliches sein.
Neben dem Webserver sollte man auch noch Photoshop auf der Windows-Kiste
anwerfen -- soaps.pl
startet es zwar, falls es noch nicht läuft,
aber die lange Verzögerung, bis das Monstrum startet, lässt den
Client nicht zurückkehren, sodass man ihn sonst das erste Mal
mit Control-C stoppen und nochmal starten muss.
Abbildung 3: Das CGI-Skript mit dem SOAP-Server kommt einfach ins C |
Auf der Linux-Seite müssen wir nur SOAP::Lite
vom CPAN holen und
installieren, wie üblich mit der CPAN-Shell:
$ perl -MCPAN -eshell cpan> install SOAP::Lite
Und schon geht's los: Ein digitales JPG-Bild
(z.B. test.jpg
) auf der Linux-Kiste und die IP-Addresse
der Windows-Box (z.B. 192.168.0.3
ausfindig gemacht, und
soapc.pl 192.168.0.3 test.jpg >test.png
eingetippt -- je nach Bildgröße dauert es ein Weilchen, aber dann kommt das fertige Bild zurück!
Das vorgestellte Verfahren, Windows-Applikationen ueber Webservices fernzusteuern, ist natuerlich nicht auf Photoshop beschränkt. Alle OLE-fähigen Windows-Programme lassen sich so kontrollieren -- probiert fleißig!
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |