diff --git a/README.de.rdoc b/README.de.rdoc deleted file mode 100644 index b8166875..00000000 --- a/README.de.rdoc +++ /dev/null @@ -1,2116 +0,0 @@ -= 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: - - # myapp.rb - require 'sinatra' - get '/' do - 'Hallo Welt!' - end - -Einfach via +rubygems+ installieren und starten: - - gem install sinatra - ruby 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: - - 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: - - 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: - - 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: - - 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: - - get '/download/*.*' do |pfad, endung| - [pfad, endung] # => ["Pfad/zu/Datei", "xml"] - end - -Routen mit regulären Ausdrücken sind auch möglich: - - get %r{/hallo/([\w]+)} do - "Hallo, #{params[:captures].first}!" - end - -Und auch hier können Block-Parameter genutzt werden: - - get %r{/hallo/([\w]+)} do |c| - "Hallo, #{c}!" - end - -Routen-Muster können auch mit optionalen Parametern ausgestattet werden: - - 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: - - 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+: - - 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: - - 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: - - 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: - - 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: - - 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: - - get // do - pass if request.path_info == "/index" - # ... - end - -Oder unter Verwendung eines negativen look ahead: - - 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: - - 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: - - get '/' do - erb :index - end - -Dieses Beispiel rendert views/index.erb. - -Anstelle eines Templatenamens kann man auch direkt die Templatesprache -verwenden: - - get '/' do - code = "<%= Time.now %>" - erb code - end - -Templates nehmen ein zweite Argument an, den Options-Hash: - - 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: - - get '/' do - haml :index, :format => :html5 - end - -Für alle Templates können auch generelle Einstellungen festgelegt werden: - - set :haml, :format => :html5 - - get '/' do - haml :index - end - -Optionen, die an die Rendermethode weitergegeben werden, überschreiben die -Einstellungen, die mit +set+ festgelegt wurden. - -Mögliche 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+ - oder +false+). 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: - - 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: - - 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': - - require 'rdiscount' # oder require 'bluecloth' - get('/') { markdown :index } - -=== Haml Templates - -Abhängigkeit:: {haml}[http://haml.info/] -Dateierweiterung:: .haml -Beispiel:: haml :index, :format => :html5 - -=== Erb Templates - -Abhängigkeit:: {erubis}[http://www.kuwata-lab.com/erubis/] oder - erb (included in Ruby) -Dateierweiterungen:: .erb, .rhtml oder .erubis - (nur Erubis) -Beispiel:: erb :index - -=== Builder Templates - -Abhängigkeit:: {builder}[http://builder.rubyforge.org/] -Dateierweiterung:: .builder -Beispiel:: builder { |xml| xml.em "Hallo" } - -Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). - -=== Nokogiri Templates - -Abhängigkeit:: {nokogiri}[http://nokogiri.org/] -Dateierweiterung:: .nokogiri -Beispiel:: nokogiri { |xml| xml.em "Hallo" } - -Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). - -=== Sass Templates - -Abhängigkeit:: {sass}[http://sass-lang.com/] -Dateierweiterung:: .sass -Beispiel:: sass :stylesheet, :style => :expanded - -=== SCSS Templates - -Abhängigkeit:: {sass}[http://sass-lang.com/] -Dateierweiterung:: .scss -Beispiel:: scss :stylesheet, :style => :expanded - -=== Less Templates - -Abhängigkeit:: {less}[http://www.lesscss.org/] -Dateierweiterung:: .less -Beispiel:: less :stylesheet - -=== Liquid Templates - -Abhängigkeit:: {liquid}[http://www.liquidmarkup.org/] -Dateierweiterung:: .liquid -Beispiel:: liquid :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ängigkeit:: {rdiscount}[https://github.com/rtomayko/rdiscount], - {redcarpet}[https://github.com/vmg/redcarpet], - {bluecloth}[http://deveiate.org/projects/BlueCloth], - {kramdown}[http://kramdown.rubyforge.org/] *oder* - {maruku}[http://maruku.rubyforge.org/] -Dateierweiterungen:: .markdown, .mkd und .md -Beispiel:: markdown :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: - - erb :overview, :locals => { :text => markdown(:einfuehrung) } - -Beachte, dass man die +markdown+-Methode auch aus anderen Templates heraus -aufrufen kann: - - %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ängigkeit:: {RedCloth}[http://redcloth.org/] -Dateierweiterung:: .textile -Beispiel:: textile :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: - - erb :overview, :locals => { :text => textile(:einfuehrung) } - -Beachte, dass man die +textile+-Methode auch aus anderen Templates heraus -aufrufen kann: - - %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ängigkeit:: {rdoc}[http://rdoc.rubyforge.org/] -Dateierweiterung:: .rdoc -Beispiel:: textile :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: - - erb :overview, :locals => { :text => rdoc(:einfuehrung) } - -Beachte, dass man die +rdoc+-Methode auch aus anderen Templates heraus -aufrufen kann: - - %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ängigkeit:: {radius}[http://radius.rubyforge.org/] -Dateierweiterung:: .radius -Beispiel:: radius :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ängigkeit:: {markaby}[http://markaby.github.com/] -Dateierweiterung:: .mab -Beispiel:: markaby { h1 "Willkommen!" } - -Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). - -=== RABL Templates - -Abhängigkeit:: {rabl}[https://github.com/nesquena/rabl] -Dateierweiterung:: .rabl -Beispiel:: rabl :index - -=== Slim Templates - -Abhängigkeit:: {slim}[http://slim-lang.com/] -Dateierweiterung:: .slim -Beispiel:: slim :index - -=== Creole Templates - -Abhängigkeit:: {creole}[https://github.com/minad/creole] -Dateierweiterung:: .creole -Beispiel:: creole :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: - - erb :overview, :locals => { :text => creole(:einfuehrung) } - -Beachte, dass man die +creole+-Methode auch aus anderen Templates heraus -aufrufen kann: - - %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ängigkeit:: {coffee-script}[https://github.com/josh/ruby-coffee-script] - und eine {Möglichkeit JavaScript auszuführen}[https://github.com/sstephenson/execjs/blob/master/README.md#readme] -Dateierweiterung:: .coffee -Beispiel:: coffee :index - -=== WLang Templates - -Abhängigkeit:: {wlang}[https://github.com/blambeau/wlang/] -Dateierweiterung:: .wlang -Beispiel:: wlang :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 - - 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: - - get '/:id' do - @foo = Foo.find(params[:id]) - haml '%h1= @foo.name' - end - -Oder durch einen expliziten Hash von lokalen Variablen: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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 lernen. - -== 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: - - 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: - - 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: - - 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: - - 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: - - 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. - - 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: - - 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: - - set :session_secret, 'super secret' - -Zur weiteren Konfiguration kann man einen Hash mit Optionen in den +sessions+ -Einstellungen ablegen. - - set :sessions, :domain => 'foo.com' - -== Anhalten - -Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: - - halt - -Der Status kann beim Stoppen auch angegeben werden: - - halt 410 - -Oder auch den Response-Body: - - halt 'Hier steht der Body' - -Oder beides: - - halt 401, 'verschwinde!' - -Sogar mit Headern: - - halt 402, {'Content-Type' => 'text/plain'}, 'Rache' - -Natürlich ist es auch möglich, ein Template mit +halt+ zu verwenden: - - halt erb(:error) - -== Weiterspringen - -Eine Route kann mittels pass zu der nächsten passenden Route springen: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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: - - configure do - mime_type :foo, 'text/foo' - end - -Es kann aber auch der +content_type+-Helfer verwendet werden: - - 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: - - %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: - - get '/foo' do - redirect to('/bar') - end - -Weitere Parameter werden wie Argumente der +halt+-Methode behandelt: - - 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: - - 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: - - redirect to('/bar?summe=42') - -oder eine Session verwendet werden: - - 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: - - get '/' do - cache_control :public - "schon gecached!" - end - -Profitipp: Caching im before-Filter aktivieren - - 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: - - 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: - - 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: - - 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: - - 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: - - 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: - - etag '', :new_resource => true, :kind => :weak - -=== Dateien versenden - -Zum Versenden von Dateien kann die send_file-Helfer-Methode verwendet -werden: - - get '/' do - send_file 'foo.png' - end - -Für send_file stehen einige Hash-Optionen zur Verfügung: - - 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: - - # 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: - - before { request.path_info = "/" } - - get "/" do - "Alle Anfragen kommen hier an!" - end - -Der request.body ist ein IO- oder StringIO-Objekt: - - 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: - - get '/' do - attachment - "Speichern!" - end - -Ebenso kann eine Dateiname als Parameter hinzugefügt werden: - - 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: - - get '/' do - pass if Time.now > time_for('Dec 23, 2012') - "noch Zeit" - end - -Diese Methode wird intern für +expires, +last_modiefied+ und Freunde verwendet. -Mit ein paar Handgriffen lässt sich diese Methode also in ihrem Verhalten -erweitern, indem man +time_for+ in der eigenen Applikation überschreibt: - - 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: - - 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: - - 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: - - 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: - - 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: - - configure :production do - ... - end - -Läuft nur, wenn die Umgebung auf :production oder auf :test -gesetzt ist: - - configure :production, :test do - ... - end - -Diese Einstellungen sind über +settings+ erreichbar: - - 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. - - disable :protection - -Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man +protection+ -einen Hash mit Optionen hinzu: - - set :protection, :except => :path_traversal - -Neben Strings akzeptiert :except auch Arrays, um gleich mehrere -Schutzmechanismen zu deaktivieren: - - set :protection, :except => [:path_traversal, :session_hijacking] - -=== Mögliche 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: - - 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: - - error do - 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name - end - -Benutzerdefinierte Fehler: - - error MeinFehler do - 'Au weia, ' + env['sinatra.error'].message - end - -Dann, wenn das passiert: - - 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: - - error 403 do - 'Zugriff verboten' - end - - get '/geheim' do - 403 - end - -Oder ein Status-Code-Bereich: - - 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: - - 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: - - 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: - - 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: - - 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: - - 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!: - - # 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: - - # config.ru - require './mein_app' - run MeineApp - -Starte: - - rackup -p 4567 - -=== Eine klassische Anwendung mit einer config.ru verwenden - -Schreibe eine Anwendungsdatei: - - # app.rb - require 'sinatra' - - get '/' do - 'Hallo Welt!' - end - -sowie eine dazugehörige config.ru-Datei: - - 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/...): - - 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: - - require 'sinatra/base' - my_app = Sinatra.new { get('/') { "hallo" } } - my_app.run! - -Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: - - # 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: - - 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: - - 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: - - 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#L25]. - -== 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: - - 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: - - $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] -* {IRC: #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] -