Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Ruby und das Web



Für Ruby ist das Internet nichts Fremdes. Man kann nicht nur einen eigenen SMTP-Server in Ruby schreiben, einen FTP-Dämon oder einen Web-Server, man kann Ruby auch für üblichere Aufgaben nutzen, wie CGI-Programmierung oder als Ersatz von PHP.

CGI-Scripts schreiben

Man kann mit Ruby ganz einfach CGI-Scripte schreiben. Damit Ruby-Script HTML ausgibt, braucht man nur folgendes machen

#!/usr/bin/env ruby
print "HTTP/1.0 200 OK\r\n"
print "Content-type: text/html\r\n\r\n"
print "<html><body>Hello World!</body></html>\r\n"

Man kann die Möglichkeiten der regulären Ausdrücke in Ruby nutzen, um ankommende Anfrage-Strings zu parsen, Umgebungs-Variablen zu bestimmen, Tags abzuprüfen, Schablonentext zu ersetzen, spezielle Zeichen zu filtern, HTML zu formattieren und das alles dann auszudrucken.

Man kann aber auch die Klasse CGI verwenden.

Verwendung von cgi.rb

Die Klasse CGI liefert Hilfe beim Schreiben von CGI-Scripten. Mit ihr kann man Formulare, Cookies und die Umgebung manipulieren, maintain stateful sessions, and so on. Die genaue Dokumentation gibt es im Referenz-Abschnitt ab Seite 501, aber wir riskieren hier schon mal einen schnellen Blick auf die Möglichkeiten.

Quoting

Wenn man mit URLs und HTML-Code zu tun hat, muss man bestimmte Zeichen sorgfältig auskommentieren. Zum Beispiel hat ein Slash (``/'') eine besondere Bedeutung in einer URL und muss deshalb ``escaped'' werden, wenn er nicht Teil des Pfadnamens ist. Dazu wird jeder ``/'' im Anfrageteil der URL in den String ``%2F'' übersetzt und muss vor der Benutzung wieder in ``/'' zurückübersetzt werden. Dafür gibt es in CGI die Routinen CGI.escape und CGI.unescape:

require 'cgi'
puts CGI.escape( "Nicholas Payton/Trumpet & Flugel Horn" )
erzeugt:
Nicholas+Payton%2FTrumpet+%26+Flugel+Horn

Genauso muss man mit speziellen HTML-Zeichen umgehen:

require 'cgi'
puts CGI.escapeHTML( '<a href="/mp3">Click Here</a>' )
erzeugt:
&lt;a href=&quot;/mp3&quot;&gt;Click Here&lt;/a&gt;

Um es richtig spaßig zu machen, kann man sich auch entscheiden, nur ganz bestimmte Elemente innerhalb eines Strings zu behandeln:

require 'cgi'
puts CGI.escapeElement('<hr><a href="/mp3">Click Here</a><br>','A')
erzeugt:
<hr>&lt;a href=&quot;/mp3&quot;&gt;Click Here&lt;/a&gt;<br>

Hier wird nur der ``A''-Tag behandelt; andere Tags bleiben verschont. Jede dieser Methoden besitzt eine ``un-''Version, um den Original-Zustand wiederherzustellen.

Formulare

Die Klasse CGI erlaubt einem den Zugriff auf die Parameter einer HTML-Anfrage auf zwei Wegen. Nehmen wir an, wir haben folgende URL: /cgi-bin/lookup?player=Miles%20Davis&year=1958. Man kann dann auf die Parameter ``player'' und ``year'' mit CGI#[] direkt zugreifen:

require 'cgi'
cgi = CGI.new
cgi['player'] » ["Miles Davis"]
cgi['year'] » ["1958"]

Oder man kann alle Parameter als Hash rausziehen:

require 'cgi'
cgi = CGI.new
h = cgi.params
h['player'] » ["Miles Davis"]

Formulare und HTML erzeugen

CGI enthält eine immense Anzahl an Methoden, um HTML zu erzeugen --- eine Methode pro Tag. Um diese Methoden zu benutzen, muss man ein CGI-Objekt mit CGI.new erzeugen und ihm den gewünschten HTML-Level mitgeben. In diesen Beispielen benutzen wir ``html3''.

Um das Verschachteln von Tags einfacher zu machen, holen sich diese Methoden den Inhalt als Code-Block. Diese Code-Blöcke sollten einen String zurückgeben, der als Inhalt für den Tag dient. In diesem Beispiel haben wir ein paar überschüssige Zeilenumbrüche eingebaut, damit der Output auf eine Seite passt.

require "cgi"
cgi = CGI.new("html3")  # add HTML generation methods
cgi.out{
  cgi.html{
    cgi.head{ "\n"+cgi.title{"This Is a Test"} } +
    cgi.body{ "\n"+
      cgi.form{"\n"+
        cgi.hr +
        cgi.h1 { "A Form: " } + "\n"+
        cgi.textarea("get_text") +"\n"+
        cgi.br +
        cgi.submit
      }
    }
  }
}
erzeugt:
Content-Type: text/html
Content-Length: 302

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <TITLE>This Is a Test</TITLE></HEAD><BODY> <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded"> <HR><H1>A Form: </H1> <TEXTAREA COLS="70" NAME="get_text" ROWS="10"></TEXTAREA> <BR><INPUT TYPE="submit"></FORM></BODY></HTML>

Dieser Code erzeugt ein HTML-Formular, mit dem Titel ``This Is a Test'', gefolgt von einer horizontalen Linie, einer Level-Eins-Überschrift, einem Input-Feld und schließlich einem OK-Button. Wenn der gedrückt wird, erhält man einen CGI-Parameter mit Namen ``get_text'', der den eingegebenen Text enthält.

Cookies

Mit Cookies kann man allen möglichen Kram auf dem Computer eines ahnungslosen Surfers speichern. Man kann ein benamtes Cookie erzeugen und eine ganze Anzahl von Werten darin speichern. Um das an den Browser zu schicken, muss man nur einen ``Cookie''-Header in den Aufruf von CGI#out setzen.

require "cgi"
cookie = CGI::Cookie.new("rubyweb", "CustID=123", "Part=ABC");
cgi = CGI.new("html3")
cgi.out( "cookie" => [cookie] ){
  cgi.html{
    "\nHTML content here"
  }
}
erzeugt:
Content-Type: text/html
Content-Length: 86
Set-Cookie: rubyweb=CustID%3D123&Part%3DABC; path=

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML> HTML content here</HTML>

Wenn der Surfer das nächste Mal diese Seite besucht, kann man die Cookie-Werte für CustID und Part wie im folgenden HTML-Output gezeigt erhalten.

require "cgi"
cgi = CGI.new("html3")
cgi.out{
  cgi.html{
    cgi.pre{
      cookie = cgi.cookies["rubyweb"]
        "\nCookies are\n" + cookie.value.join("\n")
    }
  }
}
erzeugt:
Content-Type: text/html
Content-Length: 111

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><PRE> Cookies are CustID=123 Part=ABC</PRE></HTML>

Sessions

Cookies alleine sind noch nicht allzu nützlich. Was wir wirklich wollen ist eine session: ein speicherbarer Status für einen Websurfer. Sessions werden mit der CGI::Session-Klasse bearbeitet (ab Seite 508 dokumentiert), die auch Cookies benutzt aber ein höheres Level an Abstraktion bietet.

require "cgi" require "cgi/session"

cgi = CGI.new("html3") sess = CGI::Session.new( cgi, "session_key" => "rubyweb",                           "session_id"  => "9650",                           "new_session" => true,                           "prefix" => "web-session.") sess["CustID"] = 123 sess["Part"] = "ABC"

cgi.out{   cgi.html{     "\nHTML content here"   } }

Hiermit wird ein Cookie mit dem Wert 9650 an den Benutzer mit Namen ``rubyweb'' geschickt. Gleichzeitig wird eine Datei $TMP/web-session.9650 erzeugt mit dem key, value-Paar für CustID und Part.

Wenn der User jetzt wiederkommt, braucht man nur noch den Parameter, der die Session-Id angibt. In diesem Beispiel ist das rubyweb=9650. Mit dem Wert aus diesem Parameter kann man sich dann den ganzen Satz der gesicherten Session-Daten holen.

require "cgi"
require "cgi/session"

cgi = CGI.new("html3") sess = CGI::Session.new( cgi, "session_key" => "rubyweb",                                "prefix" => "web-session.") cgi.out{   cgi.html{     "\nCustomer #{sess['CustID']} orders an #{sess['Part']}"   } }

Ruby in HTML einbetten

Bis jetzt haben wir mit Ruby nur HTML-Output erzeugt, aber wir können das Problem auch andersherum angehen; wir können auch Ruby in ein HTML-Dokument einbetten.

Es gibt eine Reihe von Paketen, mit denen man Ruby-Anweisungen in anderen Arten von Dokumenten einbetten kann, besonders natürlich in HTML. Ganz allgemein wird das eRuby genannt. Im Besonderen gibt es mehrere unterschiedliche Implementationen von eRuby, etwa eruby oder erb. Im Rest dieses Abschnitts bschreiben wir eruby von Shugo Maeda.

Ruby in HTML einzubetten ist ein sehr machvolles Konzept --- man hat damit grundsätzlich die selben Fähigkeiten wie mit ASP, JSP oder PHP, aber mit der vollen Kraft von Rubin.

eruby

Einfach und simpel ausgedrückt ist eruby nichts anderes als ein Filter. Jeglicher Text, der vom Input kommt, wird unberührt weitergeleitet, mit den folgenden Ausnahmen:

Ausdruck Beschreibung
<%Ruby-Code%> Der Ruby-Code in spitzen Klammern wird durch sein Ergebnis ersetzt.
<%=Ruby-Ausdruck%> Der Ruby-Ausdruck in spitzen Klammern wird durch seinen Wert ersetzt.
<%#Ruby-Code%> Der Ruby-Code in spitzen Klammern wird ignoriert (beim Testen ganz nützlich).

Man ruft eruby auf mit:

eruby [options][document]

Falls das document fehlt, liest eruby vom Standard-Input. Die Kommandozeilen-Optionen für eruby zeigt Tabelle 14.1 auf Seite 151.
Kommandozeilen-Optionen für eruby
Option Baschreibung
-d, --debug Setzt $DEBUG auf true.
-Kkcode Gibt ein alternatives Code-System an (siehe Seite 139).
-Mmode Gibt den Laufzeit-Modus an, nämlich:

f Filter-Modus
c CGI-Modus (gibt Fehler als HTML aus, setzt $SAFE=1)
n NPH-CGI-Modus (gibt Extra-HTTP-Headers aus, setzt $SAFE=1)
-n, --noheader Unterdrückt CGI-Header-Output. (der Übersetzer: fragt mich nicht, was das alles ist, ich hoffe das ist alles richtig übersetzt)
-v, --verbose Schaltet den Verbose-Modus an.
--version Gibt die Versions-Informationen aus und beendet sich dann.

Schauen wir uns ein einfaches Beispiel an. Wir lassen eruby folgenden Input bearbeiten.

This text is <% a = 100; puts "#{a}% Live!" %>

eruby ersetzt den Ausdruck zwischen den spitzen Klammern und erzeugt

This text is 100% Live!

Wenn man die Form <%= benutzt, wirkt das als hätte man den Wert des Ausdrucks direkt ausgegeben. So wird in dem Input

<%a = 100%>This text is almost <%=a%> degrees! Cool!

das =a durch den Wert von a ersetzt.

This text is almost 100 degrees! Cool!

Natürlich kann man Ruby dann auch in einem etwas komplexeren Dokumenten-Typ einbetten, etwa HTML.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>eruby example</title>
</head>
<body>
<h1>Enumeration</h1>
<ul>
<%(1..10).each do|i|%>
  <li>number <%=i%></li>
<%end%>
</ul>
<h1>Environment variables</h1>
<table>
<%ENV.keys.sort.each do |key|%>
  <tr>
    <th><%=key%></th><td><%=ENV[key]%></td>
  </tr>
<%end%>
</table>
</body>
</html>

eruby in Apache installieren

Man kann einen Apache-Web-Server so aufsetzen, dass er Dokumente mit eingebettetem Ruby automatisch parst, etwa genau so wie mit PHP. Man erzeugt die Dateien mit eingebettetem Ruby mit einer ``.rhtml'' Namensergänzung und konfiguriert den Web-Server so, dass er eruby über diese Dateien laufen lässt, was dann den gewünschten HTML-Output ergibt.

Wenn man eruby auf seinem Apache-Web-Server laufen lassen will, muss man folgende Schritte ausführen:

Das war dann alles! Sie können nun HTML-Dokumente schreiben, die mit eingebettetem Ruby Formulare und Inhalte dynamisch erzeugen. Sie sollten sich auch die Ruby-CGI-Bibliothek ab Seite 501 ansehen.

Performance steigern

Man kann mit Ruby CGI-Programme für das Web schreiben, aber dann wird die Default-Konfiguration, wie bei den meisten CGI-Programmen, bei jedem Seitenzugriff eine neue Kopie von Ruby starten. Für die Rechnernutzung ist das teuer und für den Surfer kann das schmerzhaft langsam werden. Der Apache-Web-Server löst das Problem mit nachladbaren Modulen.

Üblicherweise werden diese Module dynamisch geladen und werden dann Teil des laufenden Server-Prozesses --- dann gibt es keine Notwendigkeit mehr, bei jeder Service-Anfrage immer wieder einen neuen Interpreter zu starten; der Web-Server selber ist der Interpreter.

Damit kommen wir dann zu mod_ruby (erhältlich in den Archiven), ein Apache-Modul, das einen kompletten Ruby-Interpreter in den Apache-Web-Server linkt. Die README-Datei, die bei mod_ruby dabei ist, sagt einem alles, wie man das kompilieren und installieren muss.

Ist das erstmal installiert und konfiguriert, dann kann man Ruby-Scripts genauso laufen lassen wie vorher ohne mod_ruby, nur dass das Ganze jetzt wesentlich schneller ist.


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Jürgen Katins
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".