From fe258f228fe0c635a2453d022e60979f02c36b22 Mon Sep 17 00:00:00 2001 From: burningTyger Date: Sun, 27 Jan 2013 03:30:47 +0100 Subject: [PATCH] convert README.de to markdown --- README.de.md | 2590 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2590 insertions(+) create mode 100644 README.de.md diff --git a/README.de.md b/README.de.md new file mode 100644 index 00000000..844d8a1e --- /dev/null +++ b/README.de.md @@ -0,0 +1,2590 @@ +# Sinatra + +*Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter +Umständen nicht auf dem aktuellen Stand.* + +Sinatra ist eine +[DSL](http://de.wikipedia.org/wiki/Domänenspezifische_Sprache), die das +schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand +ermöglicht: + +```ruby + # myapp.rb + require 'sinatra' + get '/' do + 'Hallo Welt!' + end +``` + +Einfach via `rubygems` installieren und starten: + +```ruby + gem install sinatra + ruby -rubygems myapp.rb +``` + +Die Seite kann nun unter http://localhost:4567 betrachtet werden. + +Es wird empfohlen, den Thin-Server via `gem install thin` zu installieren, den +Sinatra dann, soweit vorhanden, automatisch verwendet. + +## Routen + +In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster definiert. +Jeder dieser Routen wird ein Ruby-Block zugeordnet: + +```ruby + get '/' do + .. zeige etwas .. + end + + post '/' do + .. erstelle etwas .. + end + + put '/' do + .. update etwas .. + end + + delete '/' do + .. entferne etwas .. + end + + options '/' do + .. zeige, was wir können .. + end +``` + +Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden. +Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt. + +Die Muster der Routen können benannte Parameter beinhalten, die über den +`params`-Hash zugänglich gemacht werden: + +```ruby + get '/hallo/:name' do + # passt auf "GET /hallo/foo" und "GET /hallo/bar" + # params[:name] ist 'foo' oder 'bar' + "Hallo #{params[:name]}!" + end +``` + +Man kann auf diese auch mit Block-Parametern zugreifen: + +```ruby + get '/hallo/:name' do |n| + "Hallo #{n}!" + end +``` + +Routen-Muster können auch mit Splat- oder Wildcard-Parametern über das +`params[:splat]`-Array angesprochen werden: + +```ruby + get '/sag/*/zu/*' do + # passt auf /sag/hallo/zu/welt + params[:splat] # => ["hallo", "welt"] + end + + get '/download/*.*' do + # passt auf /download/pfad/zu/datei.xml + params[:splat] # => ["pfad/zu/datei", "xml"] + end +``` + +Oder mit Block-Parametern: + +```ruby + get '/download/*.*' do |pfad, endung| + [pfad, endung] # => ["Pfad/zu/Datei", "xml"] + end +``` + +Routen mit regulären Ausdrücken sind auch möglich: + +```ruby + get %r{/hallo/([\w]+)} do + "Hallo, #{params[:captures].first}!" + end + +Und auch hier können Block-Parameter genutzt werden: + +```ruby + get %r{/hallo/([\w]+)} do |c| + "Hallo, #{c}!" + end +``` + +Routen-Muster können auch mit optionalen Parametern ausgestattet werden: + +```ruby + get '/posts.?:format?' do + # passt auf "GET /posts" sowie jegliche Erweiterung + # wie "GET /posts.json", "GET /posts.xml" etc. + end +``` + +Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert +(siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem +Abgleich mit den Routen modifiziert wird. + +### Bedingungen + +An Routen können eine Vielzahl von Bedingungen angehängt werden, die erfüllt +sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine +Einschränkung des User-Agents: + +```ruby + get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Du verwendest Songbird Version #{params[:agent][0]}" + end + + get '/foo' do + # passt auf andere Browser + end +``` + +Andere mitgelieferte Bedingungen sind `host_name` und `provides`: + +```ruby + get '/', :host_name => /^admin\./ do + "Adminbereich, Zugriff verweigert!" + end + + get '/', :provides => 'html' do + haml :index + end + + get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed + end +``` + +Es können auch andere Bedingungen relativ einfach hinzugefügt werden: + +```ruby + set(:probability) { |value| condition { rand <= value } } + + get '/auto_gewinnen', :probability => 0.1 do + "Du hast gewonnen!" + end + + get '/auto_gewinnen' do + "Tut mir leid, verloren." + end +``` + +Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet +werden: + +```ruby + set(:auth) do |*roles| # <- hier kommt der Splat ins Spiel + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end + end + + get "/mein/account/", :auth => [:user, :admin] do + "Mein Account" + end + + get "/nur/admin/", :auth => :admin do + "Nur Admins dürfen hier rein!" + end +``` + +### Rückgabewerte + +Durch den Rückgabewert eines Routen-Blocks wird mindestens der Response-Body +festgelegt, der an den HTTP-Client, bzw. die nächste Rack-Middleware, +weitergegeben wird. Im Normalfall handelt es sich hierbei, wie in den +vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings +auch andere Werte akzeptiert. + +Es kann jedes gültige Objekt zurückgegeben werden, bei dem es sich entweder um +einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt: + +* Ein Array mit drei Elementen: `[Status (Fixnum), Headers (Hash), + Response-Body (antwortet auf #each)]`. +* Ein Array mit zwei Elementen: `[Status (Fixnum), Response-Body (antwortet + auf #each)]`. +* Ein Objekt, das auf `#each` antwortet und den an diese Methode übergebenen + Block nur mit Strings als Übergabewerte aufruft. +* Ein Fixnum, das den Status-Code festlegt. + + +Damit lässt sich relativ einfach Streaming implementieren: + +```ruby + class Stream + def each + 100.times { |i| yield "#{i}\n" } + end + end + + get('/') { Stream.new } +``` + +Ebenso kann die `stream`-Helfer-Methode (s.u.) verwendet werden, die Streaming +direkt in die Route integriert. + +### Eigene Routen-Muster + +Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für +String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. +Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene +Routen-Muster erstellt werden: + +```ruby + class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end + end + + def all_but(pattern) + AllButPattern.new(pattern) + end + + get all_but("/index") do + # ... + end +``` + +Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch einfacher: + +```ruby + get // do + pass if request.path_info == "/index" + # ... + end +``` + +Oder unter Verwendung eines negativen look ahead: + +```ruby + get %r{^(?!/index$)} do + # ... + end +``` +## Statische Dateien + +Statische Dateien werden aus dem `./public`-Ordner ausgeliefert. Es ist möglich, +einen anderen Ort zu definieren, indem man die `:public_folder`-Option setzt: + +```ruby + set :public_folder, File.dirname(__FILE__) + '/static' +``` + +Zu beachten ist, dass der Ordnername public nicht Teil der URL ist. Die Datei +`./public/css/style.css` ist unter `http://example.com/css/style.css` zu finden. + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man +die `:static_cache_control`-Einstellung (s.u.). + +## Views/Templates + +Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils +einen String zurückgibt: + +```ruby + get '/' do + erb :index + end +``` + +Dieses Beispiel rendert `views/index.erb`. + +Anstelle eines Templatenamens kann man auch direkt die Templatesprache verwenden: + +```ruby + get '/' do + code = "<%= Time.now %>" + erb code + end +``` + +Templates nehmen ein zweite Argument an, den Options-Hash: + +```ruby + get '/' do + erb :index, :layout => :post + end +``` + +Dieses Beispiel rendert `views/index.erb` eingebettet in `views/post.erb` +(Voreinstellung ist `views/layout.erb`, sofern es vorhanden ist.) + +Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht: + +```ruby + get '/' do + haml :index, :format => :html5 + end +``` + +Für alle Templates können auch generelle Einstellungen festgelegt werden: + +```ruby + set :haml, :format => :html5 + + get '/' do + haml :index + end +``` + +Optionen, die an die Rendermethode weitergegeben werden, überschreiben die +Einstellungen, die mit `set` festgelegt wurden. + +Einstellungen: + +
+
locals
+
Liste von lokalen Variablen, die and das Dokument weitergegeben werden. + Praktisch für Partials. Beispiel: + erb "<%= foo %>", :locals => {:foo => "bar"}
+ +
default_encoding
+
Gibt die Stringkodierung an, die verwendet werden soll. Voreingestellt + auf settings.default_encoding.
+ +
views
+
Ordner, aus dem die Templates heraus geladen werden. Voreingestellt auf + settings.views.
+ +
layout
+
Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht + (true oderfalse). Ist es ein Symbol, dass legt es fest, + welches Template als Layout verwendet wird. Beispiel: + erb :index, :layout => !request.xhr?
+ +
content_type
+
Content-Type den das Template ausgibt. Voreinstellung hängt von der + Templatesprache ab.
+ +
scope
+
Scope, in dem das Template gerendert wird. Liegt standardmäßig innerhalb + der App-Instanz. Wird Scope geändert, sind Instanzvariablen und + Helfermethoden nicht verfügbar.
+ +
layout_engine
+
Legt fest, welcher Renderer für das Layout verantwortlich ist. Hilfreich + für Sprachen, die sonst keine Templates unterstützen. Voreingestellt auf + den Renderer, der für das Template verwendet wird. Beispiel: + set :rdoc, :layout_engine => :erb
+
+ + +Sinatra geht davon aus, dass die Templates sich im `./views` Verzeichnis +befinden. Es kann jedoch ein anderer Ordner festgelegt werden: + +```ruby + set :views, settings.root + '/templates' +``` + +Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen werden muss, +auch dann, wenn sie sich in einem Unterordner befinden: + +```ruby + haml :'unterverzeichnis/template' +``` + +Rendering-Methoden rendern jeden String direkt. + +### Verfügbare Templatesprachen + +Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche +verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu +Beginn ein 'require': + +```ruby + require 'rdiscount' # oder require 'bluecloth' + get('/') { markdown :index } +``` + +### Haml Templates + + + + + + + + + + + + + + +
Abhängigkeithaml
Dateierweiterung.haml
Beispielhaml :index, :format => :html5
+ + +### Erb Templates + + + + + + + + + + + + + + +
Abhängigkeiterubis oder erb + (Standardbibliothek von Ruby)
Dateierweiterungen.erb, .rhtml oder .erubis (nur Erubis)
Beispielerb :index
+ + +### Builder Templates + + + + + + + + + + + + + + +
Abhängigkeitbuilder
Dateierweiterung.builder
Beispielbuilder { |xml| xml.em "Hallo" }
+ +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +### Nokogiri Templates + + + + + + + + + + + + + + +
Abhängigkeitnokogiri
Dateierweiterung.nokogiri
Beispielnokogiri { |xml| xml.em "Hallo" }
+ +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +### Sass Templates + + + + + + + + + + + + + + +
Abhängigkeitsass
Dateierweiterung.sass
Beispielsass :stylesheet, :style => :expanded
+ + +### SCSS Templates + + + + + + + + + + + + + + +
Abhängigkeitsass
Dateierweiterung.scss
Beispielscss :stylesheet, :style => :expanded
+ + +### Less Templates + + + + + + + + + + + + + + +
Abhängigkeitless
Dateierweiterung.less
Beispielless :stylesheet
+ + +### Liquid Templates + + + + + + + + + + + + + + +
Abhängigkeitliquid
Dateierweiterung.liquid
Beispielliquid :index, :locals => { :key => 'Wert' }
+ +Da man aus dem Liquid-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +### Markdown Templates + + + + + + + + + + + + + + +
AbhängigkeitEine der folgenden Bibliotheken: + RDiscount, + RedCarpet, + BlueCloth, + kramdown oder + maruku +
Dateierweiterungen.markdown, .mkd und .md
Beispielmarkdown :index, :layout_engine => :erb
+ +Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Markdown üblicherweise in Kombination +mit anderen Renderern verwenden wollen: + +```ruby + erb :overview, :locals => { :text => markdown(:einfuehrung) } +``` + +Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby + %h1 Gruß von Haml! + %p= markdown(:Grüße) +``` + +Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht +in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +### Textile Templates + + + + + + + + + + + + + + +
AbhängigkeitRedCloth
Dateierweiterung.textile
Beispieltextile :index, :layout_engine => :erb
+ +Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby + erb :overview, :locals => { :text => textile(:einfuehrung) } +``` + +Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby + %h1 Gruß von Haml! + %p= textile(:Grüße) +``` + +Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht +in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +### RDoc Templates + + + + + + + + + + + + + + +
Abhängigkeitrdoc
Dateierweiterung.rdoc
Beispieltextile :README, :layout_engine => :erb
+ +Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby + erb :overview, :locals => { :text => rdoc(:einfuehrung) } +``` + +Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby + %h1 Gruß von Haml! + %p= rdoc(:Grüße) +``` + +Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in +RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates +zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +### Radius Templates + + + + + + + + + + + + + + +
Abhängigkeitradius
Dateierweiterung.radius
Beispielradius :index, :locals => { :key => 'Wert' }
+ +Da man aus dem Radius-Template heraus keine Ruby-Methoden aufrufen kann, wird +man üblicherweise locals verwenden wollen, mit denen man Variablen weitergibt. + +### Markaby Templates + + + + + + + + + + + + + + +
Abhängigkeitmarkaby
Dateierweiterung.mab
Beispielmarkaby { h1 "Willkommen!" }
+ +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +### RABL Templates + + + + + + + + + + + + + + +
Abhängigkeitrabl
Dateierweiterung.rabl
Beispielrabl :index
+ +### Slim Templates + + + + + + + + + + + + + + +
Abhängigkeitslim
Dateierweiterung.slim
Beispielslim :index
+ +### Creole Templates + + + + + + + + + + + + + + +
Abhängigkeitcreole
Dateierweiterung.creole
Beispielcreole :wiki, :layout_engine => :erb
+ +Da man aus dem Creole-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Creole üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby + erb :overview, :locals => { :text => creole(:einfuehrung) } +``` + +Beachte, dass man die `creole`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby + %h1 Gruß von Haml! + %p= creole(:Grüße) +``` + +Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts nicht in +Creole geschrieben werden. Es ist aber möglich, einen Renderer für die Templates +zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option +verwendet wird. + +### CoffeeScript Templates + + + + + + + + + + + + + +
Abhängigkeitcoffee-script und eine Möglichkeit JavaScript auszuführen.
Dateierweiterung.coffee
Beispielcoffee :index
+ +### WLang Templates + + + + + + + + + + + + + + +
Abhängigkeitwlang
Dateierweiterung.wlang
Beispielwlang :index, :locals => { :key => 'value' }
+ +Ruby-Methoden in wlang aufzurufen entspricht nicht den idiomatischen Vorgaben +von wlang, es bietet sich deshalb an, `:locals` zu verwenden. Layouts, die +wlang und `yield` verwenden, werden aber trotzdem unterstützt. + +### Eingebettete Templates + +```ruby + get '/' do + haml '%div.title Hallo Welt' + end +``` + +Rendert den eingebetteten Template-String. + +### Auf Variablen in Templates zugreifen + +Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen +in Routen sind auch direkt im Template verfügbar: + +```ruby + get '/:id' do + @foo = Foo.find(params[:id]) + haml '%h1= @foo.name' + end +``` + +Oder durch einen expliziten Hash von lokalen Variablen: + +```ruby + get '/:id' do + foo = Foo.find(params[:id]) + haml '%h1= bar.name', :locals => { :bar => foo } + end +``` + +Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen +Templates eingesetzt. + +### Inline-Templates + +Templates können auch am Ende der Datei definiert werden: + +```ruby + require 'sinatra' + + get '/' do + haml :index + end + + __END__ + + @@ layout + %html + = yield + + @@ index + %div.title Hallo Welt!!!!! +``` + +Anmerkung: Inline-Templates, die in der Datei definiert sind, die `require +'sinatra'` aufruft, werden automatisch geladen. Um andere Inline-Templates in +anderen Dateien aufzurufen, muss explizit `enable :inline_templates` verwendet +werden. + +### Benannte Templates + +Templates können auch mit der Top-Level `template`-Methode definiert werden: + +```ruby + template :layout do + "%html\n =yield\n" + end + + template :index do + '%div.title Hallo Welt!' + end + + get '/' do + haml :index + end +``` + +Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf +verwendet. Durch `:layout => false` kann das Ausführen verhindert werden: + +```ruby + get '/' do + haml :index, :layout => request.xhr? + end +``` + +### Dateiendungen zuordnen + +Um eine Dateiendung einer Template-Engine zuzuordnen, kann `Tilt.register` +genutzt werden. Wenn etwa die Dateiendung `tt` für Textile-Templates genutzt +werden soll, lässt sich dies wie folgt bewerkstelligen: + +```ruby + Tilt.register :tt, Tilt[:textile] +``` + +### Eine eigene Template-Engine hinzufügen + +Zu allererst muss die Engine bei Tilt registriert und danach eine +Rendering-Methode erstellt werden: + +```ruby + Tilt.register :mtt, MeineTolleTemplateEngine + + helpers do + def mtt(*args) render(:mtt, *args) end + end + + get '/' do + mtt :index + end +``` + +Dieser Code rendert `./views/application.mtt`. Siehe +[github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über +Tilt zu erfahren. + +## Filter + +Before-Filter werden vor jedem Request in demselben Kontext, wie danach die +Routen, ausgeführt. So können etwa Request und Antwort geändert werden. +Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet +werden: + +```ruby + before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' + end + + get '/foo/*' do + @note #=> 'Hi!' + params[:splat] #=> 'bar/baz' + end +``` + +After-Filter werden nach jedem Request in demselben Kontext ausgeführt und +können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte +Instanzvariablen können in After-Filtern verwendet werden: + +```ruby + after do + puts response.status + end +``` + +Filter können optional auch mit einem Muster ausgestattet werden, welches auf +den Request-Pfad passen muss, damit der Filter ausgeführt wird: + +```ruby + before '/protected/*' do + authenticate! + end + + after '/create/:slug' do |slug| + session[:last_slug] = slug + end +``` + +Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt +werden: + +```ruby + before :agent => /Songbird/ do + # ... + end + + after '/blog/*', :host_name => 'example.com' do + # ... + end +``` + +## Helfer + +Durch die Top-Level `helpers`-Methode werden sogenannte Helfer-Methoden +definiert, die in Routen und Templates verwendet werden können: + +```ruby + helpers do + def bar(name) + "#{name}bar" + end + end + + get '/:name' do + bar(params[:name]) + end +``` + +### Sessions verwenden +Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. Sind +sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet werden: + +```ruby + enable :sessions + + get '/' do + "value = " << session[:value].inspect + end + + get '/:value' do + session[:value] = params[:value] + end +``` + +Beachte, dass `enable :sessions` alle Daten in einem Cookie speichert. Unter +Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten +höheren, teilweise überflüssigen Traffic. Um das zu vermeiden, kann eine Rack- +Session-Middleware verwendet werden. Dabei wird auf `enable :sessions` +verzichtet und die Middleware wie üblich im Programm eingebunden: + +```ruby + use Rack::Session::Pool, :expire_after => 2592000 + + get '/' do + "value = " << session[:value].inspect + end + + get '/:value' do + session[:value] = params[:value] + end +``` + +Um die Sicherheit zu erhöhen, werden Cookies, die Session-Daten führen, mit +einem sogenannten Session-Secret signiert. Da sich dieses Geheimwort bei jedem +Neustart der Applikation automatisch ändert, ist es sinnvoll, ein eigenes zu +wählen, damit sich alle Instanzen der Applikation dasselbe Session-Secret +teilen: + +```ruby + set :session_secret, 'super secret' +``` + +Zur weiteren Konfiguration kann man einen Hash mit Optionen in den `sessions` +Einstellungen ablegen. + +```ruby + set :sessions, :domain => 'foo.com' +``` + +## Anhalten + +Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: + +```ruby + halt +``` + +Der Status kann beim Stoppen auch angegeben werden: + +```ruby + halt 410 +``` + +Oder auch den Response-Body: + +```ruby + halt 'Hier steht der Body' +``` + +Oder beides: + +```ruby + halt 401, 'verschwinde!' +``` + +Sogar mit Headern: + +```ruby + halt 402, {'Content-Type' => 'text/plain'}, 'Rache' +``` + +Natürlich ist es auch möglich, ein Template mit `halt` zu verwenden: + +```ruby + halt erb(:error) +``` + +## Weiterspringen + +Eine Route kann mittels `pass` zu der nächsten passenden Route springen: + +```ruby + get '/raten/:wer' do + pass unless params[:wer] == 'Frank' + 'Du hast mich!' + end + + get '/raten/*' do + 'Du hast mich nicht!' + end +``` + +Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route +gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster +gefunden wird. + +### Eine andere Route ansteuern + +Manchmal entspricht `pass` nicht den Anforderungen, wenn das Ergebnis einer +anderen Route gefordert wird. Um das zu erreichen, lässt sich `call` nutzen: + +```ruby + get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] + end + + get '/bar' do + "bar" + end +``` + +Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht +werden kann, wenn `"bar"` in eine Helfer-Methode umgewandelt wird, auf die +`/foo` und `/bar` zugreifen können. + +Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine +Kopie der Instanz erzeugt werden soll, kann `call!` anstelle von `call` +verwendet werden. + +Die Rack-Spezifikationen enthalten weitere Informationen zu `call`. + +### Body, Status-Code und Header setzen + +Es ist möglich und empfohlen, den Status-Code sowie den Response-Body mit +einem Returnwert in der Route zu setzen. In manchen Situationen kann es jedoch +sein, dass der Body an irgendeiner anderen Stelle während der Ausführung +gesetzt wird. Das lässt sich mit der Helfer-Methode `body` bewerkstelligen. +Wird `body` verwendet, lässt sich der Body jederzeit über diese Methode +aufrufen: + +```ruby + get '/foo' do + body "bar" + end + + after do + puts body + end +``` + +Ebenso ist es möglich, einen Block an `body` weiterzureichen, der dann vom +Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming +einsetzen, siehe auch "Rückgabewerte"). + +Vergleichbar mit `body` lassen sich auch Status-Code und Header setzen: + +```ruby + get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + halt "Ich bin ein Teekesselchen" + end +``` + +Genau wie bei `body` liest ein Aufrufen von `headers` oder `status` ohne +Argumente den aktuellen Wert aus. + +### Response-Streams + +In manchen Situationen sollen Daten bereits an den Client zurückgeschickt +werden, bevor ein vollständiger Response bereit steht. Manchmal will man die +Verbindung auch erst dann beenden und Daten so lange an den Client +zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die +`stream`-Helfer-Methode, die es einem erspart eigene Lösungen zu schreiben: + +```ruby + get '/' do + stream do |out| + out << "Das ist ja mal wieder fanta -\n" + sleep 0.5 + out << " (bitte warten…) \n" + sleep 1 + out << "- stisch!\n" + end + end +``` + +Damit lassen sich Streaming-APIs realisieren, sog. +[Server Sent Events](http://dev.w3.org/html5/eventsource/) die als Basis für +[WebSockets](http://en.wikipedia.org/wiki/WebSocket) dienen. Ebenso können sie +verwendet werden, um den Durchsatz zu erhöhen, wenn ein Teil der Daten von +langsamen Ressourcen abhängig ist. + +Es ist zu beachten, dass das Verhalten beim Streaming, insbesondere die Anzahl +nebenläufiger Anfragen, stark davon abhängt, welcher Webserver für die +Applikation verwendet wird. Einige Server, z.B. WEBRick, unterstützen +Streaming nicht oder nur teilweise. Sollte der Server Streaming nicht +unterstützen, wird ein vollständiger Response-Body zurückgeschickt, sobald der +an `stream` weitergegebene Block abgearbeitet ist. Mit Shotgun funktioniert +Streaming z.B. überhaupt nicht. + +Ist der optionale Parameter `keep_open` aktiviert, wird beim gestreamten Objekt +`close` nicht aufgerufen und es ist einem überlassen dies an einem beliebigen +späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei Event-gesteuerten +Serven wie Thin oder Rainbows möglich, andere Server werden trotzdem den Stream +beenden: + +```ruby + set :server, :thin + connections = [] + + get '/' do + # Den Stream offen halten + stream(:keep_open) { |out| connections << out } + end + + post '/' do + # In alle offenen Streams schreiben + connections.each { |out| out << params[:message] << "\n" } + "Nachricht verschickt" + end +``` + +### Logger + +Im Geltungsbereich eines Request stellt die `logger` Helfer-Methode eine `Logger` +Instanz zur Verfügung: + +```ruby + get '/' do + logger.info "es passiert gerade etwas" + # ... + end +``` + +Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten +Log-Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt zurück. +In den Routen und Filtern muss man sich also nicht weiter darum kümmern. + +Beachte, dass das Loggen standardmäßig nur für `Sinatra::Application` +voreingestellt ist. Wird über `Sinatra::Base` vererbt, muss es erst aktiviert +werden: + +```ruby + class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end + end +``` + +Damit auch keine Middleware das Logging aktivieren kann, muss die `logging` +Einstellung auf `nil` gesetzt werden. Das heißt aber auch, dass `logger` in +diesem Fall `nil` zurückgeben wird. Üblicherweise wird das eingesetzt, wenn ein +eigener Logger eingerichtet werden soll. Sinatra wird dann verwenden, was in +`env['rack.logger']` eingetragen ist. + +## Mime-Types + +Wenn `send_file` oder statische Dateien verwendet werden, kann es vorkommen, +dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit `mime_type` +per Dateiendung: + +```ruby + configure do + mime_type :foo, 'text/foo' + end +``` + +Es kann aber auch der `content_type`-Helfer verwendet werden: + +```ruby + get '/' do + content_type :foo + "foo foo foo" + end +``` + +### URLs generieren + +Zum Generieren von URLs sollte die `url`-Helfer-Methode genutzen werden, so z.B. +beim Einsatz von Haml: + +```ruby + %a{:href => url('/foo')} foo +``` + +Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen. + +Diese Methode ist ebenso über das Alias `to` zu erreichen (siehe Beispiel unten). + +### Browser-Umleitung + +Eine Browser-Umleitung kann mithilfe der `redirect`-Helfer-Methode erreicht +werden: + +```ruby + get '/foo' do + redirect to('/bar') + end +``` + +Weitere Parameter werden wie Argumente der `halt`-Methode behandelt: + +```ruby + redirect to('/bar'), 303 + redirect 'http://google.com', 'Hier bist du falsch' +``` + +Ebenso leicht lässt sich ein Schritt zurück mit dem Alias `redirect back` +erreichen: + +```ruby + get '/foo' do + "mach was" + end + + get '/bar' do + mach_was + redirect back + end +``` + +Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query +übergeben: + +```ruby + redirect to('/bar?summe=42') +``` + +oder eine Session verwendet werden: + +```ruby + enable :sessions + + get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') + end + + get '/bar' do + session[:secret] + end +``` + +### Cache einsetzen + +Ein sinnvolles Einstellen von Header-Daten ist die Grundlage für ein +ordentliches HTTP-Caching. + +Der Cache-Control-Header lässt sich ganz einfach einstellen: + +```ruby + get '/' do + cache_control :public + "schon gecached!" + end +``` + +Profitipp: Caching im before-Filter aktivieren + +```ruby + before do + cache_control :public, :must_revalidate, :max_age => 60 + end +``` + +Bei Verwendung der `expires`-Helfermethode zum Setzen des gleichnamigen Headers, +wird `Cache-Control` automatisch eigestellt: + +```ruby + before do + expires 500, :public, :must_revalidate + end +``` + +Um alles richtig zu machen, sollten auch `etag` oder `last_modified` verwendet +werden. Es wird empfohlen, dass diese Helfer aufgerufen werden **bevor** die +eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client +eine aktuelle Version im Cache vorhält: + +```ruby + get '/article/:id' do + @article = Article.find params[:id] + last_modified @article.updated_at + etag @article.sha1 + erb :article + end +``` + +ebenso ist es möglich einen +[schwachen ETag](http://de.wikipedia.org/wiki/HTTP_ETag) zu verwenden: + +```ruby + etag @article.sha1, :weak +``` + +Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür +notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy +Cache-Lösungen bietet sich z.B. +[rack-cache](https://github.com/rtomayko/rack-cache) an: + +```ruby + require "rack/cache" + require "sinatra" + + use Rack::Cache + + get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" + end +``` + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man die +`:static_cache_control`-Einstellung (s.u.). + +Nach RFC 2616 sollte sich die Anwendung anders verhalten, wenn ein If-Match oder +ein If-None_match Header auf `*` gesetzt wird in Abhängigkeit davon, ob die +Resource bereits existiert. Sinatra geht davon aus, dass Ressourcen bei sicheren +Anfragen (z.B. bei get oder Idempotenten Anfragen wie put) bereits existieren, +wobei anderen Ressourcen (besipielsweise bei post), als neue Ressourcen +behandelt werden. Dieses Verhalten lässt sich mit der `:new_resource` Option +ändern: + +```ruby + get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article + end +``` + +Soll das schwache ETag trotzdem verwendet werden, verwendet man die `:kind` +Option: + +```ruby + etag '', :new_resource => true, :kind => :weak +``` + +### Dateien versenden + +Zum Versenden von Dateien kann die `send_file`-Helfer-Methode verwendet werden: + +```ruby + get '/' do + send_file 'foo.png' + end +``` + +Für `send_file` stehen einige Hash-Optionen zur Verfügung: + +```ruby + send_file 'foo.png', :type => :jpg +``` + +
+
filename
+
Dateiname als Response. Standardwert ist der eigentliche Dateiname.
+ +
last_modified
+
Wert für den Last-Modified-Header, Standardwert ist mtime der + Datei.
+ +
type
+
Content-Type, der verwendet werden soll. Wird, wenn nicht angegeben, von + der Dateiendung abgeleitet.
+ +
disposition
+
Verwendet für Content-Disposition. Mögliche Werte sind: nil + (Standard), :attachment und :inline.
+ +
length
+
Content-Length-Header. Standardwert ist die Dateigröße.
+
+ +Soweit vom Rack-Handler unterstützt, werden neben der Übertragung über den +Ruby-Prozess auch andere Möglichkeiten genutzt. Bei Verwendung der +`send_file`-Helfer-Methode kümmert sich Sinatra selbstständig um die +Range-Requests. + +## Das Request-Objekt + +Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus +zugegriffen werden: + +```ruby + # App läuft unter http://example.com/example + get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # Request-Body des Client (siehe unten) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # Länge des request.body + request.media_type # Medientypus von request.body + request.host # "example.com" + request.get? # true (ähnliche Methoden für andere Verben) + request.form_data? # false + request["IRGENDEIN_HEADER"] # Wert von IRGENDEIN_HEADER header + request.referrer # Der Referrer des Clients oder '/' + request.user_agent # User-Agent (verwendet in der :agent Bedingung) + request.cookies # Hash des Browser-Cookies + request.xhr? # Ist das hier ein Ajax-Request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-Adresse des Clients + request.secure? # false (true wenn SSL) + request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird) + request.env # vollständiger env-Hash von Rack übergeben + end +``` + +Manche Optionen, wie etwa `script_name` oder `path_info`, sind auch +schreibbar: + +```ruby + before { request.path_info = "/" } + + get "/" do + "Alle Anfragen kommen hier an!" + end +``` + +Der `request.body` ist ein IO- oder StringIO-Objekt: + +```ruby + post "/api" do + request.body.rewind # falls schon jemand davon gelesen hat + daten = JSON.parse request.body.read + "Hallo #{daten['name']}!" + end +``` + +### Anhänge + +Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser +angezeigt werden soll, kann der `attachment`-Helfer verwendet werden: + +```ruby + get '/' do + attachment + "Speichern!" + end +``` + +Ebenso kann eine Dateiname als Parameter hinzugefügt werden: + +```ruby + get '/' do + attachment "info.txt" + "Speichern!" + end +``` + +### Umgang mit Datum und Zeit + +Sinatra bietet eine `time_for`-Helfer-Methode, die aus einem gegebenen Wert ein +Time-Objekt generiert. Ebenso kann sie nach `DateTime`, `Date` und ähnliche +Klassen konvertieren: + +```ruby + get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "noch Zeit" + end +``` + +Diese Methode wird intern für +expires, `last_modiefied` und ihresgleichen +verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem +Verhalten erweitern, indem man `time_for` in der eigenen Applikation +überschreibt: + +```ruby + helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end + end + + get '/' do + last_modified :yesterday + expires :tomorrow + "Hallo" + end +``` + +### Nachschlagen von Template-Dateien + +Die `find_template`-Helfer-Methode wird genutzt, um Template-Dateien zum Rendern +aufzufinden: + +```ruby + find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "könnte diese hier sein: #{file}" + end +``` + +Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann sie +nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum Beispiel +dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll: + +```ruby + set :views, ['views', 'templates'] + + helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end + end +``` + +Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines +zu verwenden: + +```ruby + set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + + helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end + end +``` + +Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt +werden! + +Beachte, dass `find_template` nicht prüft, ob eine Datei tatsächlich existiert. +Es wird lediglich der angegebene Block aufgerufen und nach allen möglichen +Pfaden gesucht. Das ergibt kein Performance-Problem, da `render` `block` +verwendet, sobald eine Datei gefunden wurde. Ebenso werden Template-Pfade samt +Inhalt gecached, solange nicht im Entwicklungsmodus gearbeitet wird. Das sollte +im Hinterkopf behalten werden, wenn irgendwelche verrückten Methoden +zusammenbastelt werden. + +## Konfiguration + +Wird einmal beim Starten in jedweder Umgebung ausgeführt: + +```ruby + configure do + # setze eine Option + set :option, 'wert' + + # setze mehrere Optionen + set :a => 1, :b => 2 + + # das gleiche wie `set :option, true` + enable :option + + # das gleiche wie `set :option, false` + disable :option + + # dynamische Einstellungen mit Blöcken + set(:css_dir) { File.join(views, 'css') } + end +``` + +Läuft nur, wenn die Umgebung (RACK_ENV-Umgebungsvariable) auf `:production` +gesetzt ist: + +```ruby + configure :production do + ... + end +``` + +Läuft nur, wenn die Umgebung auf `:production` oder auf `:test` gesetzt ist: + +```ruby + configure :production, :test do + ... + end +``` + +Diese Einstellungen sind über `settings` erreichbar: + +```ruby + configure do + set :foo, 'bar' + end + + get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... + end +``` + +### Einstellung des Angriffsschutzes + +Sinatra verwendet +[Rack::Protection](https://github.com/rkh/rack-protection#readme), um die +Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung +lässt sich selbstverständlich deaktivieren, der damit verbundene +Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen +Risiken. + +```ruby + disable :protection +``` + +Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man `protection` +einen Hash mit Optionen hinzu: + +```ruby + set :protection, :except => :path_traversal +``` + +Neben Strings akzeptiert `:except` auch Arrays, um gleich mehrere +Schutzmechanismen zu deaktivieren: + +```ruby + set :protection, :except => [:path_traversal, :session_hijacking] +``` +## Möglichee Einstellungen + +
+
absolute_redirects
+
Wenn ausgeschaltet, wird Sinatra relative Redirects zulassen. Jedoch ist + Sinatra dann nicht mehr mit RFC 2616 (HTTP 1.1) konform, das nur absolute + Redirects zulässt. Sollte eingeschaltet werden, wenn die Applikation hinter + einem Reverse-Proxy liegt, der nicht ordentlich eingerichtet ist. Beachte, + dass die url-Helfer-Methode nach wie vor absolute URLs erstellen + wird, es sei denn, es wird als zweiter Parameter false angegeben. + Standardmäßig nicht aktiviert.
+ +
add_charsets
+
+ Mime-Types werden hier automatisch der Helfer-Methode content_type + zugeordnet. Es empfielt sich, Werte hinzuzufügen statt sie zu + überschreiben: settings.add_charsets << "application/foobar" +
+ +
app_file
+
Pfad zur Hauptdatei der Applikation. Wird verwendet, um das Wurzel-, + Inline-, View- und öffentliche Verzeichnis des Projekts festzustellen.
+ +
bind
+
IP-Address, an die gebunden wird (Standardwert: 0.0.0.0). Wird nur für + den eingebauten Server verwendet.
+ +
default_encoding
+
Das Encoding, falls keines angegeben wurde. Standardwert ist + "utf-8".
+ +
dump_errors
+
Fehler im Log anzeigen.
+ +
environment
+
Momentane Umgebung. Standardmäßig auf content_type oder + "development" eingestellt, soweit ersteres nicht vorhanden.
+ +
logging
+
Den Logger verwenden.
+ +
lock
+
Jeder Request wird gelocked. Es kann nur ein Request pro Ruby-Prozess + gleichzeitig verarbeitet werden. Eingeschaltet, wenn die Applikation + threadsicher ist. Standardmäßig nicht aktiviert.
+ +
method_override
+
Verwende _method, um put/delete-Formulardaten in Browsern zu + verwenden, die dies normalerweise nicht unterstützen.
+ +
port
+
Port für die Applikation. Wird nur im internen Server verwendet.
+ +
prefixed_redirects
+
Entscheidet, ob request.script_name in Redirects eingefügt wird + oder nicht, wenn kein absoluter Pfad angegeben ist. Auf diese Weise verhält + sich redirect '/foo' so, als wäre es ein redirect + to('/foo'). Standardmäßig nicht aktiviert.
+ +
protection
+
Legt fest, ob der Schutzmechanismus für häufig Vorkommende Webangriffe + auf Webapplikationen aktiviert wird oder nicht. Weitere Informationen im + vorhergehenden Abschnitt.
+ +
public_folder
+
Das öffentliche Verzeichnis, aus dem Daten zur Verfügung gestellt werden + können. Wird nur dann verwendet, wenn statische Daten zur Verfügung gestellt + werden können (s.u. static Option). Leitet sich von der + app_file Einstellung ab, wenn nicht gesetzt.
+ +
public_dir
+
Alias für public_folder, s.o.
+ +
reload_templates
+
Im development-Modus aktiviert.
+ +
root
+
Wurzelverzeichnis des Projekts. Leitet sich von der app_file + Einstellung ab, wenn nicht gesetzt.
+ +
raise_errors
+
Einen Ausnahmezustand aufrufen. Beendet die Applikation. Ist automatisch + aktiviert, wenn die Umgebung auf "test" eingestellt ist. Ansonsten + ist diese Option deaktiviert.
+ +
run
+
Wenn aktiviert, wird Sinatra versuchen, den Webserver zu starten. Nicht + verwenden, wenn Rackup oder anderes verwendet werden soll.
+ +
running
+
Läuft der eingebaute Server? Diese Einstellung nicht ändern!
+ +
server
+
Server oder Liste von Servern, die als eingebaute Server zur Verfügung + stehen. Standardmäßig auf [‘thin’, ‘mongrel’, ‘webrick’] voreingestellt. Die + Anordnung gibt die Priorität + vor.
+ +
sessions
+
Sessions auf Cookiebasis mittels + Rack::Session::Cookieaktivieren. Für weitere Infos bitte in der + Sektion ‘Sessions verwenden’ nachschauen.
+ +
show_exceptions
+
Bei Fehlern einen Stacktrace im Browseranzeigen. Ist automatisch + aktiviert, wenn die Umgebung auf "development" eingestellt ist. + Ansonsten ist diese Option deaktiviert. Kann auch auf :after_handler + gestellt werden, um eine anwendungsspezifische Fehlerbehandlung auszulösen, + bevor der Fehlerverlauf im Browser angezeigt wird.
+ +
static
+
Entscheidet, ob Sinatra statische Dateien zur Verfügung stellen soll oder + nicht. Sollte nicht aktiviert werden, wenn ein Server verwendet wird, der + dies auch selbstständig erledigen kann. Deaktivieren wird die Performance + erhöhen. Standardmäßig aktiviert.
+ +
static_cache_control
+
Wenn Sinatra statische Daten zur Verfügung stellt, können mit dieser + Einstellung die Cache-Control Header zu den Responses hinzugefügt + werden. Die Einstellung verwendet dazu die cache_control + Helfer-Methode. Standardmäßig deaktiviert. Ein Array wird verwendet, um + mehrere Werte gleichzeitig zu übergeben: set :static_cache_control, + [:public, :max_age => 300]
+ +
views
+
Verzeichnis der Views. Leitet sich von der app_file Einstellung + ab, wenn nicht gesetzt.
+
+ +## Umgebungen + +Es gibt drei voreingestellte Umgebungen in Sinatra: `"development"`, +`"production"` und `"test"`. Umgebungen können über die `RACK_ENV` +Umgebungsvariable gesetzt werden. Die Standardeinstellung ist `"development"`. +In diesem Modus werden alle Templates zwischen Requests neu geladen. Dazu gibt +es besondere Fehlerseiten für 404 Stati und Fehlermeldungen. In `"production"` +und `"test"` werden Templates automatisch gecached. + +Um die Anwendung in einer anderen Umgebung auszuführen kann man die `-e` +Option verwenden: + +``` + ruby my_app.rb -e [ENVIRONMENT] +``` + +In der Anwendung kann man die die Methoden `development?`, `test?` und +`production?` verwenden, um die aktuelle Umgebung zu erfahren. + +## Fehlerbehandlung + +Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet, +dass alle Goodies wie `haml`, `erb`, `halt`, etc. verwendet werden können. + +### Nicht gefunden + +Wenn eine `Sinatra::NotFound`-Exception geworfen wird oder der Statuscode 404 +ist, wird der `not_found`-Handler ausgeführt: + +```ruby + not_found do + 'Seite kann nirgendwo gefunden werden.' + end +``` + +### Fehler + +Der `error`-Handler wird immer ausgeführt, wenn eine Exception in einem +Routen-Block oder in einem Filter geworfen wurde. Die Exception kann über die +`sinatra.error`-Rack-Variable angesprochen werden: + +```ruby + error do + 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name + end +``` + +Benutzerdefinierte Fehler: + +```ruby + error MeinFehler do + 'Au weia, ' + env['sinatra.error'].message + end +``` + +Dann, wenn das passiert: + +```ruby + get '/' do + raise MeinFehler, 'etwas Schlimmes ist passiert' + end +``` + +bekommt man dieses: + +``` + Au weia, etwas Schlimmes ist passiert +``` + +Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden: + +```ruby + error 403 do + 'Zugriff verboten' + end + + get '/geheim' do + 403 + end +``` + +Oder ein Status-Code-Bereich: + +```ruby + error 400..510 do + 'Hallo?' + end +``` + +Sinatra setzt verschiedene `not_found`- und `error`-Handler in der +Development-Umgebung. + +## Rack-Middleware + +Sinatra baut auf [Rack](http://rack.rubyforge.org/), einem minimalistischen +Standard-Interface für Ruby-Webframeworks. Eines der interessantesten Features +für Entwickler ist der Support von Middlewares, die zwischen den Server und +die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort +überwachen und/oder manipulieren können. + +Sinatra macht das Erstellen von Middleware-Verkettungen mit der +Top-Level-Methode `use` zu einem Kinderspiel: + +```ruby + require 'sinatra' + require 'meine_middleware' + + use Rack::Lint + use MeineMiddleware + + get '/hallo' do + 'Hallo Welt' + end +``` + +Die Semantik von `use` entspricht der gleichnamigen Methode der +[Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html)-DSL +(meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die +`use`-Methode mehrere/verschiedene Argumente und auch Blöcke entgegennimmt: + +```ruby + use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'geheim' + end +``` + +Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging, +URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet +viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So +muss `use` häufig nicht explizit verwendet werden. + +Hilfreiche Middleware gibt es z.B. hier: +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), mit +[CodeRack](http://coderack.org/) oder im [Rack +wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testen + +Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework geschrieben +werden. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) +wird empfohlen: + +```ruby + require 'my_sinatra_app' + require 'test/unit' + require 'rack/test' + + class MyAppTest < Test::Unit::TestCase + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hallo Welt!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hallo Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Du verwendest Songbird!", last_response.body + end + end +``` + +## Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen + +Das Definieren einer Top-Level-Anwendung funktioniert gut für +Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie +Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder +auch Sinatra-Erweiterungen geschrieben werden sollen. + +Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus (wie +sie z.B. bei einer einzelnen Anwendungsdatei, `./public` und `./views` Ordner, +Logging, Exception-Detail-Seite, usw.). Genau hier kommt `Sinatra::Base` ins +Spiel: + +```ruby + require 'sinatra/base' + + class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hallo Welt!' + end + end +``` + +Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware, +Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch +`use` oder `run` von einer Rackup-`config.ru`-Datei oder als Server-Komponente +einer Bibliothek: + +```ruby + MyApp.run! :host => 'localhost', :port => 9090 +``` + +Die Methoden der `Sinatra::Base`-Subklasse sind genau dieselben wie die der +Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei +Veränderungen zu `Sinatra::Base` konvertiert werden: + +* Die Datei sollte `require 'sinatra/base'` anstelle von `require + 'sinatra/base'` aufrufen, ansonsten werden alle von Sinatras DSL-Methoden + in den Top-Level-Namespace importiert. +* Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in + einer Subklasse von `Sinatra::Base` definiert werden. + + +`Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per +Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe +[Optionen und Konfiguration](http://sinatra.github.com/configuration.html) für +Details über mögliche Optionen. + +### Modularer vs. klassischer Stil + +Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil +einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein +Grund, eine modulare Applikation zu erstellen. + +Der größte Nachteil der klassischen Sinatra Anwendung gegenüber einer +modularen ist die Einschränkung auf eine Sinatra Anwendung pro Ruby-Prozess. +Sollen mehrere zum Einsatz kommen, muss auf den modularen Stil umgestiegen +werden. Dabei ist es kein Problem klassische und modulare Anwendungen +miteinander zu vermischen. + +Bei einem Umstieg, sollten einige Unterschiede in den Einstellungen beachtet +werden: + +``` + Szenario Classic Modular + --------------------------------------------------- + + app_file sinatra ladende Datei Sinatra::Base subklassierende Datei + run $0 == app_file false + logging true false + method_override true false + inline_templates true false +``` + +### Eine modulare Applikation bereitstellen + +Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über +`run!`: + +```ruby + # mein_app.rb + require 'sinatra/base' + + class MeinApp < Sinatra::Base + # ... Anwendungscode hierhin ... + + # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird + run! if app_file == $0 + end +``` + +Starte mit: + +``` + ruby mein_app.rb +``` + +Oder über eine `config.ru`-Datei, die es erlaubt, einen beliebigen +Rack-Handler zu verwenden: + +```ruby + # config.ru + require './mein_app' + run MeineApp +``` + +Starte: + +``` + rackup -p 4567 +``` + +### Eine klassische Anwendung mit einer config.ru verwenden + +Schreibe eine Anwendungsdatei: + +```ruby + # app.rb + require 'sinatra' + + get '/' do + 'Hallo Welt!' + end +``` + +sowie eine dazugehörige `config.ru`-Datei: + +```ruby + require './app' + run Sinatra::Application +``` + +### Wann sollte eine config.ru-Datei verwendet werden? + +Anzeichen dafür, dass eine `config.ru`-Datei gebraucht wird: + +* Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, + Heroku, ...). +* Es gibt mehr als nur eine Subklasse von `Sinatra::Base`. +* Sinatra soll als Middleware verwendet werden, nicht als Endpunkt. + + +**Es gibt keinen Grund, eine `config.ru`-Datei zu verwenden, nur weil eine +Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine Anwendung +mit modularem Stil benötigt, um eine `config.ru`-Datei zu verwenden.** + +### Sinatra als Middleware nutzen + +Es ist nicht nur möglich, andere Rack-Middleware mit Sinatra zu nutzen, es +kann außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden +beliebigen Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich +nicht um eine andere Sinatra-Anwendung handeln, es kann jede andere +Rack-Anwendung sein (Rails/Ramaze/Camping/...): + +```ruby + require 'sinatra/base' + + class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params[:name] == 'admin' && params[:password] == 'admin' + session['user_name'] = params[:name] + else + redirect '/login' + end + end + end + + class MyApp < Sinatra::Base + # Middleware wird vor Filtern ausgeführt + use LoginScreen + + before do + unless session['user_name'] + halt "Zugriff verweigert, bitte einloggen." + end + end + + get('/') { "Hallo #{session['user_name']}." } + end +``` + +### Dynamische Applikationserstellung + +Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, +ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit +`Sinatra.new` erreichen: + +```ruby + require 'sinatra/base' + my_app = Sinatra.new { get('/') { "hallo" } } + my_app.run! +``` + +Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: + +```ruby + # config.ru + require 'sinatra/base' + + controller = Sinatra.new do + enable :logging + helpers MyHelpers + end + + map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } + end + + map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } + end +``` + +Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden +oder Sinatra in einer Bibliothek Verwendung findet. + +Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen: + +```ruby + require 'sinatra/base' + + use Sinatra do + get('/') { ... } + end + + run RailsProject::Application +``` + +## Geltungsbereich und Bindung + +Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur +Verfügung stehen. + +### Anwendungs- oder Klassen-Scope + +Jede Sinatra-Anwendung entspricht einer `Sinatra::Base`-Subklasse. Falls die +Top- Level-DSL verwendet wird (`require 'sinatra'`), handelt es sich um +`Sinatra::Application`, andernfalls ist es jene Subklasse, die explizit +angelegt wurde. Auf Klassenebene stehen Methoden wie `get` oder `before` zur +Verfügung, es gibt aber keinen Zugriff auf das `request`-Object oder die +`session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt +wird. + +Optionen, die via `set` gesetzt werden, sind Methoden auf Klassenebene: + +```ruby + class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungsscope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ich bin nicht mehr im Anwendungs-Scope! + end + end +``` + +Im Anwendungs-Scope befindet man sich: + +* In der Anwendungs-Klasse. +* In Methoden, die von Erweiterungen definiert werden. +* Im Block, der an `helpers` übergeben wird. +* In Procs und Blöcken, die an `set` übergeben werden. +* Der an `Sinatra.new` übergebene Block + + +Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden: + +* Über das Objekt, das an den `configure`-Block übergeben wird (`configure { + |c| ... }`). +* `settings` aus den anderen Scopes heraus. + + +### Anfrage- oder Instanz-Scope + +Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse +erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope heraus +kann auf `request` oder `session` zugegriffen und Methoden wie `erb` oder +`haml` aufgerufen werden. Außerdem kann mit der `settings`-Method auf den +Anwendungs-Scope zugegriffen werden: + +```ruby + class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungs-Scope! + get '/neue_route/:name' do + # Anfrage-Scope für '/neue_route/:name' + @value = 42 + + settings.get "/#{params[:name]}" do + # Anfrage-Scope für "/#{params[:name]}" + @value # => nil (nicht dieselbe Anfrage) + end + + "Route definiert!" + end + end +``` + +Im Anfrage-Scope befindet man sich: + +* In get/head/post/put/delete-Blöcken +* In before/after-Filtern +* In Helfer-Methoden +* In Templates + + +### Delegation-Scope + +Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope +weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope, +da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als +delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf +die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es +gibt ein anderes `self`). Weitere Delegationen können mit +`Sinatra::Delegator.delegate :methoden_name` hinzugefügt werden. + +Im Delegation-Scop befindet man sich: + +* Im Top-Level, wenn `require 'sinatra'` aufgerufen wurde. +* In einem Objekt, das mit dem `Sinatra::Delegator`-Mixin erweitert wurde. + + +Schau am besten im Code nach: Hier ist [Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064 +) definiert und wird in den [globalen Namespace +eingebunden](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb + +## Kommandozeile + +Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden: + +``` + ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER] +``` + +Die Optionen sind: + +``` + -h # Hilfe + -p # Port setzen (Standard ist 4567) + -h # Host setzen (Standard ist 0.0.0.0) + -e # Umgebung setzen (Standard ist development) + -s # Rack-Server/Handler setzen (Standard ist thin) + -x # Mutex-Lock einschalten (Standard ist off) +``` + +## Systemanforderungen + +Die folgenden Versionen werden offiziell unterstützt: + +
+
Ruby 1.8.7
+
1.8.7 wird vollständig unterstützt, aber solange nichts dagegen spricht, + wird ein Update auf 1.9.2 oder ein Umstieg auf JRuby/Rubinius empfohlen. + Unterstützung für 1.8.7 wird es mindestens bis Sinatra 2.0 und Ruby 2.0 + geben, es sei denn, dass der unwahrscheinliche Fall eintritt und 1.8.8 + rauskommt. Doch selbst dann ist es eher wahrscheinlich, dass 1.8.7 weiterhin + unterstützt wird. Ruby 1.8.6 wird nicht mehr unterstützt. Soll Sinatra + unter 1.8.6 eingesetzt werden, muss Sinatra 1.2 verwendet werden, dass noch + bis zum Release von Sinatra 1.4.0 fortgeführt wird.
+ +
Ruby 1.9.2
+
1.9.2 wird voll unterstützt und empfohlen. Version 1.9.2p0 sollte nicht + verwendet werden, da unter Sinatra immer wieder Segfaults auftreten. + Unterstützung wird es mindestens bis zum Release von Ruby 1.9.4/2.0 geben und + das letzte Sinatra Release für 1.9 wird so lange unterstützt, wie das Ruby + Core-Team 1.9 pflegt.
+ +
Ruby 1.9.3
+
1.9.3 wird vollständig unterstützt und empfohlen. Achtung, bei einem + Wechsel zu 1.9.3 werden alle Sessions ungültig.
+ +
Rubinius
+
Rubinius (rbx >= 1.2.4) wird offiziell unter Einbezug aller Templates + unterstützt. Die kommende 2.0 Version wird ebenfalls unterstützt, samt 1.9 + Modus.
+ +
JRuby
+
JRuby wird offiziell unterstützt (JRuby >= 1.6.7). Probleme mit + Template- Bibliotheken Dritter sind nicht bekannt. Falls JRuby zum Einsatz + kommt, sollte aber darauf geachtet werden, dass ein JRuby-Rack-Handler zum + Einsatz kommt – die Thin und Mongrel Web-Server werden bisher nicht + unterstütz. JRubys Unterstützung für C-Erweiterungen sind zur Zeit ebenfalls + experimenteller Natur, betrifft im Moment aber nur die RDiscount, Redcarpet, + RedCloth und Yajl Templates.
+
+ +Weiterhin werden wir die kommende Ruby-Versionen im Auge behalten. + +Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von +Sinatra unterstützt, funktionieren aber normalerweise: + +* Ruby Enterprise Edition +* Ältere Versionen von JRuby und Rubinius +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 und 1.9.1 (wird jedoch nicht empfohlen, s.o.) + + +Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, +wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen +Implentierung liegt. + +Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head +(das kommende Ruby 2.0.0) und 1.9.4 mit eingebunden. Da noch alles im Fluss +ist, kann zur Zeit für nichts garantiert werden. Es kann aber erwartet werden, +dass Ruby 2.0.0p0 und 1.9.4p0 von Sinatra unterstützt werden wird. + +Sinatra sollte auf jedem Betriebssystem laufen, dass den gewählten Ruby- +Interpreter unterstützt. + +Sinatra wird aktuell nicht unter Cardinal, SmallRuby, BleuRuby oder +irgendeiner Version von Ruby vor 1.8.7 laufen. + +## Der neuste Stand (The Bleeding Edge) + +Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden. +Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems, +die so installiert werden: + +``` + gem install sinatra --pre +``` + +### Mit Bundler + +Wenn die Applikation mit der neuesten Version von Sinatra und +[Bundler](http://gembundler.com/) genutzt werden soll, empfehlen wir den +nachfolgenden Weg. + +Soweit Bundler noch nicht installiert ist: + +``` + gem install bundler +``` + +Anschließend wird eine `Gemfile`-Datei im Projektverzeichnis mit folgendem +Inhalt erstellt: + +```ruby + source :rubygems + gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + + # evtl. andere Abhängigkeiten + gem 'haml' # z.B. wenn du Haml verwendest... + gem 'activerecord', '~> 3.0' # ...oder ActiveRecord 3.x +``` + +Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, +direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem +Gemfile von Sinatra hinzugefügt. + +Jetzt kannst du deine Applikation starten: + +``` + bundle exec ruby myapp.rb +``` + +### Eigenes Repository +Um auf dem neuesten Stand von Sinatras Code zu sein, kann eine lokale Kopie +angelegt werden. Gestartet wird in der Anwendung mit dem `sinatra/lib`- Ordner +im `LOAD_PATH`: + +``` + cd myapp + git clone git://github.com/sinatra/sinatra.git + ruby -Isinatra/lib myapp.rb +``` + +Alternativ kann der `sinatra/lib`-Ordner zum `LOAD_PATH` in der Anwendung +hinzugefügt werden: + +```ruby + $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' + require 'rubygems' + require 'sinatra' + + get '/ueber' do + "Ich laufe auf Version " + Sinatra::VERSION + end +``` + +Um Sinatra-Code von Zeit zu Zeit zu aktualisieren: + +``` + cd myproject/sinatra + git pull +``` + +### Gem erstellen + +Aus der eigenen lokalen Kopie kann nun auch ein globales Gem gebaut werden: + +``` + git clone git://github.com/sinatra/sinatra.git + cd sinatra + rake sinatra.gemspec + rake install +``` + +Falls Gems als Root installiert werden sollen, sollte die letzte Zeile +folgendermaßen lauten: + +``` + sudo rake install +``` + +## Versions-Verfahren + +Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h. +SemVer und SemVerTag. + +## Mehr + +* [Projekt-Website](http://sinatra.github.com/) - Ergänzende Dokumentation, + News und Links zu anderen Ressourcen. +* [Mitmachen](http://sinatra.github.com/contributing.html) - Einen Fehler + gefunden? Brauchst du Hilfe? Hast du einen Patch? +* [Issue-Tracker](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Mailing-Liste](http://groups.google.com/group/sinatrarb) +* [ #sinatra](irc://chat.freenode.net/#sinatra) auf http://freenode.net +* [Sinatra Book](http://sinatra-book.gittr.com) Kochbuch Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Sinatra-Rezepte aus der + Community +* API Dokumentation für die [aktuelle + Version](http://rubydoc.info/gems/sinatra) oder für + [HEAD](http://rubydoc.info/github/sinatra/sinatra) auf http://rubydoc.info +* [CI Server](http://travis-ci.org/sinatra/sinatra)