Skip to content
Browse files

Merge branch 'master' into prerelease

  • Loading branch information...
2 parents 578c250 + 64e0a29 commit 00fc08993a4f3cbc679cc5edd427aa97df39bc25 @rkh rkh committed Sep 11, 2011
Showing with 1,069 additions and 191 deletions.
  1. +16 −0 .travis.yml
  2. +51 −8 CHANGES
  3. +25 −12 Gemfile
  4. +0 −5 KNOWN_ISSUES
  5. +175 −19 README.de.rdoc
  6. +160 −16 README.es.rdoc
  7. +164 −46 README.fr.rdoc
  8. +161 −17 README.rdoc
  9. +2 −2 README.ru.rdoc
  10. +2 −2 README.zh.rdoc
  11. +101 −21 lib/sinatra/base.rb
  12. +2 −2 lib/sinatra/showexceptions.rb
  13. +5 −4 sinatra.gemspec
  14. +19 −3 test/helpers_test.rb
  15. +10 −6 test/mapped_error_test.rb
  16. +1 −1 test/response_test.rb
  17. +2 −2 test/result_test.rb
  18. +13 −0 test/routing_test.rb
  19. +45 −0 test/settings_test.rb
  20. +15 −25 test/slim_test.rb
  21. +100 −0 test/streaming_test.rb
View
16 .travis.yml
@@ -0,0 +1,16 @@
+rvm:
+ - 1.8.7
+ - 1.9.2
+ - 1.9.3
+ - rbx
+ - rbx-2.0
+ - jruby
+ - ruby-head
+env:
+ - "rack=1.3.0"
+ - "rack=master"
+ - "tilt=1.3.2"
+ - "tilt=master"
+notifications:
+ recipients:
+ - k.haase@finn.de
View
59 CHANGES
@@ -1,7 +1,19 @@
= 1.3.0 / Not Yet Released
+ * Added `stream` helper method for easily creating streaming APIs, Server
+ Sent Events or even WebSockets. See README for more on that topic.
+ (Konstantin Haase)
+
+ * If a HTTP 1.1 client is redirected from a different verb than GET, use 303
+ instead of 302 by default. You may still pass 302 explicitly. Fixes AJAX
+ redirects in Internet Explorer 9 (to be fair, everyone else is doing it
+ wrong and IE is behaving correct). (Konstantin Haase)
+
* Added support for HTTP PATCH requests. (Konstantin Haase)
+ * Use rack-protection to defend against common opportunistic attacks.
+ (Konstantin Haase)
+
* Support for Creole templates, Creole is a standardized wiki markup,
supported by many wiki implementations. (Konstanin Haase)
@@ -14,9 +26,9 @@
version. This makes Sinatra confirm with RFC 2396 section 2.2 and RFC 2616
section 3.2.3 (escaped reserved characters should not be treated like the
unescaped version), meaning that "/:name" will also match `/foo%2Fbar`, but
- not `/foo/bar`. To avoid incompatibility, pattern matching has been adjusted.
- Moreover, since we do no longer need to keep an unescaped version of
- path_info around, we handle all changes to `env['PATH_INFO']` correctly.
+ not `/foo/bar`. To avoid incompatibility, pattern matching has been
+ adjusted. Moreover, since we do no longer need to keep an unescaped version
+ of path_info around, we handle all changes to `env['PATH_INFO']` correctly.
(Konstantin Haase)
* `settings.app_file` now defaults to the file subclassing `Sinatra::Base` in
@@ -42,14 +54,15 @@
`Delegator.target`. This was mainly introduced to ease testing. (Konstantin
Haase)
- * Error handlers defined for an error class will now also handle subclasses of
- that class, unless more specific error handlers exist. (Konstantin Haase)
+ * Error handlers defined for an error class will now also handle subclasses
+ of that class, unless more specific error handlers exist. (Konstantin
+ Haase)
* Error handling respects Exception#code, again. (Konstantin Haase)
* Changing a setting will merge hashes: `set(:x, :a => 1); set(:x :b => 2)`
- will result in `{:a => 1, :b => 2}`. Use `set(:x, {:a => 1}, true)` to avoid
- this behavior. (Konstantin Haase)
+ will result in `{:a => 1, :b => 2}`. Use `set(:x, {:a => 1}, true)` to
+ avoid this behavior. (Konstantin Haase)
* Added `request.accept?` and `request.preferred_type` to ease dealing with
`Accept` headers. (Konstantin Haase)
@@ -63,11 +76,23 @@
* Uses SecureRandom to generate default session secret. (Konstantin Haase)
- * `Sinatra.run!` now prints to stderr rather than stdout. (Andrew Armenia)
+ * The `attachment` helper will set Content-Type (if it hasn't been set yet)
+ depending on the supplied file name. (Vasiliy Ermolovich)
+
+ * Conditional requests on `etag` helper now work properly for unsafe HTTP
+ methods. (Matthew Schinckel, Konstantin Haase)
+
+ * `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew
+ Armenia)
+
+ * `Sinatra::Base.run!` takes a block allowing access to the Rack handler.
+ (David Waite)
* Automatic `app_file` detection now works in directories containing brackets
(Konstantin Haase)
+ * Exception objects are now passed to error handlers. (Konstantin Haase)
+
* Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori
Ishikawa, Konstantin Haase)
@@ -77,6 +102,9 @@
allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2.
Example: `ruby app.rb -s Mongrel2` (Konstantin Haase)
+ * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1.
+ (Konstantin Haase)
+
* Middleware setup is now distributed across multiple methods, allowing
Sinatra extensions to easily hook into the setup process. (Konstantin
Haase)
@@ -86,6 +114,12 @@
* Move Sinatra::VERSION to separate file, so it can be checked without
loading Sinatra. (Konstantin Haase)
+ * Command line options now complain if value passed to `-p` is not a valid
+ integer. (Konstantin Haase)
+
+ * Fix handling of broken query params when displaying exceptions. (Luke
+ Jahnke)
+
= 1.2.7 (backports release) / Not Yet Released
Custom changes:
@@ -94,6 +128,9 @@ Custom changes:
Backported from 1.3.0:
+ * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1.
+ (Konstantin Haase)
+
* `Sinatra.run!` now prints to stderr rather than stdout. (Andrew Armenia)
* Automatic `app_file` detection now works in directories containing brackets
@@ -110,6 +147,12 @@ Backported from 1.3.0:
* Fix uninitialized instance variable warning. (David Kellum)
+ * Command line options now complain if value passed to `-p` is not a valid
+ integer. (Konstantin Haase)
+
+ * Fix handling of broken query params when displaying exceptions. (Luke
+ Jahnke)
+
= 1.2.6 / 2011-05-01
* Fix broken delegation, backport delegation tests from Sinatra 1.3.
View
37 Gemfile
@@ -8,6 +8,7 @@
RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE
source :rubygems unless ENV['QUICK']
+gemspec
gem 'rake'
gem 'rack-test', '>= 0.5.6'
@@ -29,18 +30,30 @@ gem 'sass'
gem 'builder'
gem 'erubis'
gem 'less', '~> 1.0'
-gem 'liquid' unless RUBY_ENGINE == 'maglev'
-gem 'slim'
+
+if RUBY_ENGINE == "maglev"
+ gem 'liquid', :git => "https://github.com/Shopify/liquid.git"
+else
+ gem 'liquid'
+end
+
+gem 'slim', '~> 1.0'
+gem 'temple', '!= 0.3.3'
gem 'RedCloth' if RUBY_VERSION < "1.9.3" and not RUBY_ENGINE.start_with? 'ma'
-gem 'coffee-script', '>= 2.0' unless RUBY_ENGINE == 'maglev'
+gem 'coffee-script', '>= 2.0'
gem 'rdoc'
gem 'kramdown'
gem 'maruku'
gem 'creole'
-gem 'nokogiri' if RUBY_ENGINE != 'maglev'
+if RUBY_ENGINE == 'jruby'
+ gem 'nokogiri', '!= 1.5.0'
+ gem 'jruby-openssl'
+elsif RUBY_ENGINE != 'maglev'
+ gem 'nokogiri'
+end
-unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1"
+unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1" && !ENV['TRAVIS']
# C extensions
gem 'rdiscount'
gem 'redcarpet'
@@ -50,15 +63,15 @@ unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1"
end
if RUBY_ENGINE == 'maglev'
- gem 'json'
- gem 'markaby'
- gem 'radius'
-end
-
-platforms :ruby_18, :jruby do
- gem 'json'
+ gem 'json', :git => "https://github.com/MagLev/json.git"
gem 'markaby'
gem 'radius'
+else
+ platforms :ruby_18, :jruby do
+ gem 'json'
+ gem 'markaby'
+ gem 'radius'
+ end
end
platforms :mri_18 do
View
5 KNOWN_ISSUES
@@ -1,5 +0,0 @@
-= Third Party issues
-
-* Nokogiri 1.5.0 does not work properly on JRuby
-* RedCloth does not work properly on the upcoming Ruby 1.9.3
-* BlueCloth randomly adds invalid bytes to generated strings
View
194 README.de.rdoc
@@ -96,6 +96,17 @@ Und auch hier können Block-Parameter genutzt werden:
"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
@@ -136,6 +147,25 @@ Es können auch andere Bedingungen relativ einfach hinzugefügt werden:
"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
@@ -165,6 +195,9 @@ Damit lässt sich relativ einfach Streaming implementieren:
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
@@ -426,9 +459,9 @@ Templates zu verwenden und einen anderen für das Layout, indem die
=== 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]
+ und eine {Möglichkeit JavaScript auszuführen}[https://github.com/sstephenson/execjs/blob/master/README.md#readme]
Dateierweiterungs:: <tt>.coffee</tt>
-Beispiel:: <tt>coffee :index</tt>
+Beispiel:: <tt>coffee :index</tt>
=== Eingebettete Templates
@@ -732,14 +765,64 @@ Vergleichbar mit +body+ lassen sich auch Status-Code und Header setzen:
get '/foo' do
status 418
headers \
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
+ "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.
+
+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
@@ -826,7 +909,7 @@ Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query
oder eine Session verwendet werden:
- enable :session
+ enable :sessions
get '/foo' do
session[:secret] = 'foo'
@@ -863,7 +946,7 @@ Headers, wird <tt>Cache-Control</tt> automatisch eigestellt:
expires 500, :public, :must_revalidate
end
-Um alles richtig zu machen, sollten auch +etag+ und +last_modified+ verwendet
+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:
@@ -881,8 +964,9 @@ ebenso ist es möglich einen
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 Cache-Lösungen
-bietet sich z.B. {rack-cache}[http://rtomayko.github.com/rack-cache/] an:
+notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy
+Cache-Lösungen bietet sich z.B.
+{rack-cache}[http://rtomayko.github.com/rack-cache/] an:
require "rack/cache"
require "sinatra"
@@ -1003,6 +1087,37 @@ Ebenso kann eine Dateiname als Parameter hinzugefügt werden:
"Speichern!"
end
+=== Umgang mit Datum und Zeit
+
+Sinatra bietet eine <tt>time_for</tt>-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 <tt>find_template</tt>-Helfer-Methode wird genutzt, um Template-Dateien zum
@@ -1095,6 +1210,26 @@ Diese Einstellungen sind über +settings+ erreichbar:
...
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 auch deaktivieren, z.B. um
+Geschwindigkeitsvorteile zu gewinnen:
+
+ 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 <tt>:except</tt> auch Arrays, um gleich mehrere
+Schutzmechanismen zu deaktivieren:
+
+ set :protections, :except => [:path_traversal, :session_hijacking]
+
=== Mögliche Einstellungen
[absolute_redirects] Wenn ausgeschaltet, wird Sinatra relative Redirects
@@ -1158,6 +1293,11 @@ Diese Einstellungen sind über +settings+ erreichbar:
sich <tt>redirect '/foo'</tt> so, als wäre es ein
<tt>redirect to('/foo')</tt>. 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.
@@ -1663,50 +1803,65 @@ 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.
+ <b>Ruby 1.8.6 wird nicht mehr unterstützt.</b> 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 unterstützt und empfohlen. Beachte, dass Markaby und Radius
- momentan noch nicht kompatibel mit 1.9 sind. Version 1.9.0p0 sollte nicht
+ 1.9.2 wird voll unterstützt und empfohlen. Beachte, dass Markaby und Radius
+ momentan noch nicht kompatibel mit 1.9 sind. 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 ]
+ Obwohl Tests bereits auf 1.9.3 laufen, sind bisher keine Applikationen auf
+ 1.9.3 in Produktion bekannt. Ebenso wie bei 1.9.2 besteht die gleiche Warnung
+ zum Patchlevel 0.
+
[ Rubinius ]
- Rubinius (rbx >= 1.2.3) wird offiziell unter Einbezug aller Templates
+ Rubinius (rbx >= 1.2.4) wird offiziell unter Einbezug aller Templates
unterstützt.
[ JRuby ]
- JRuby wird offiziell unterstützt (JRuby >= 1.6.1). Probleme mit Template-
+ JRuby wird offiziell unterstützt (JRuby >= 1.6.3). 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 – der Thin-Web-Server wird bisher nicht unterstütz. JRubys
Unterstützung für C-Erweiterungen sind zur Zeit noch experimenteller Natur,
betrifft im Moment aber nur RDiscount und Redcarpet.
-<b>Ruby 1.8.6 wird nicht weiter unterstützt.</b> Falls Sinatra trotzdem unter
-1.8.6 eingesetzt wird, muss Sinatra 1.2 verwendet werden, dass noch bis zum
-Release von Sinatra 1.4.0 mit kleinen Bugfixes versorgt werden wird.
Weiterhin werden wir auf kommende Ruby-Versionen ein Auge haben.
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
+* 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 1.9.3) mit eingebunden. Da noch alles im Fluss ist, kann zur
+(das kommende Ruby 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
-1.9.3p0 von Sinatra unterstützt werden wird.
+1.9.4p0 von Sinatra unterstützt werden wird.
Sinatra sollte auf jedem Betriebssystem laufen, dass den gewählten Ruby-
Interpreter unterstützt.
-== Der neueste Stand (The Bleeding Edge)
+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,
@@ -1803,3 +1958,4 @@ SemVer und SemVerTag.
* 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://ci.rkh.im/view/Sinatra/]
View
176 README.es.rdoc
@@ -100,6 +100,17 @@ O con un parámetro de bloque:
"Hola, #{c}!"
end
+Los patrones de ruta pueden contener parámetros opcionales:
+
+ get '/posts.?:formato?' do
+ # coincide con "GET /posts" y además admite cualquier extensión, por
+ # ejemplo, "GET /posts.json", "GET /posts.xml", etc.
+ end
+
+A propósito, a menos que desactivés la protección para el ataque <em>path
+traversal</em> (ver más abajo), el path de la petición puede ser modificado
+antes de que se compare con los de tus rutas.
+
=== Condiciones
Las rutas pueden incluir una variedad de condiciones de selección, como por
@@ -139,6 +150,26 @@ Podés definir tus propias condiciones fácilmente:
"Lo siento, perdiste."
end
+Si tu condición acepta más de un argumento, podés pasarle un arreglo. Al
+definir la condición puede resultarte conveniente utilizar el operador splat en
+la lista de parámetros:
+
+ set(:autorizar) do |*roles| # <- mirá el splat
+ condition do
+ unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol }
+ redirect "/iniciar_sesion/", 303
+ end
+ end
+ end
+
+ get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do
+ "Detalles de mi cuenta"
+ end
+
+ get "/solo/administradores/", :autorizar => :administrador do
+ "Únicamente para administradores!"
+ end
+
=== Valores de Retorno
El valor de retorno de un bloque de ruta determina al menos el cuerpo de la
@@ -431,7 +462,7 @@ layout distinto al de la plantilla pasando la opción <tt>:layout_engine</tt>.
Dependencias:: {rdoc}[http://rdoc.rubyforge.org/]
Extensiones de Archivo:: <tt>.rdoc</tt>
-Ejemplo:: <tt>textile :LEEME, :layout_engine => :erb</tt>
+Ejemplo:: <tt>rdoc :LEEME, :layout_engine => :erb</tt>
No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto,
generalmente vas a usarlo en combinación con otro motor de renderizado:
@@ -807,14 +838,64 @@ De manera similar, también podés asignar el código de estado y encabezados:
get '/foo' do
status 418
headers \
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
body "I'm a tea pot!"
end
También, al igual que +body+, tanto +status+ como +headers+ pueden utilizarse
para obtener sus valores cuando no se les pasa argumentos.
+=== Streaming De Respuestas
+
+A veces vas a querer empezar a enviar la respuesta a pesar de que todavía no
+terminaste de generar su cuerpo. También es posible que, en algunos casos,
+quieras seguir enviando información hasta que el cliente cierre la conexión.
+Cuando esto ocurra, el +stream+ helper te va a ser de gran ayuda:
+
+ get '/' do
+ stream do |out|
+ out << "Esto va a ser legen -\n"
+ sleep 0.5
+ out << " (esperalo) \n"
+ sleep 1
+ out << "- dario!\n"
+ end
+ end
+
+Podés implementar APIs de streaming,
+{Server-Sent Events}[http://dev.w3.org/html5/eventsource/] y puede ser usado
+como base para {WebSockets}[http://es.wikipedia.org/wiki/WebSockets]. También
+puede ser usado para incrementar el throughput si solo una parte del contenido
+depende de un recurso lento.
+
+Hay que tener en cuenta que el comportamiento del streaming, especialmente el
+número de peticiones concurrentes, depende del servidor web utilizado para
+servir la aplicación. Puede que algunos servidores, como es el caso de
+WEBRick, no soporten streaming directamente, así el cuerpo de la respuesta será
+enviado completamente de una vez cuando el bloque pasado a +stream+ finalice su
+ejecución.
+
+Cuando se pasa +keep_open+ como parámetro, no se va a enviar el mensaje
++close+ al objeto de stream. Queda en vos cerrarlo en el punto de ejecución
+que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es
+posible solo en servidores que soporten eventos, como Thin o Rainbows. El
+resto de los servidores van a cerrar el stream de todos modos:
+
+ set :server, :thin
+ conexiones = []
+
+ get '/' do
+ # mantenemos abierto el stream
+ stream(:keep_open) { |salida| conexiones << salida }
+ end
+
+ post '/' do
+ # escribimos a todos los streams abiertos
+ conexiones.each { |salida| salida << params[:mensaje] << "\n" }
+ "mensaje enviado"
+ end
+
=== Log (Registro)
En el ámbito de la petición, el helper +logger+ (registrador) expone
@@ -901,7 +982,7 @@ búsqueda:
O usar una sesión:
- enable :session
+ enable :sessions
get '/foo' do
session[:secreto] = 'foo'
@@ -937,7 +1018,7 @@ Si estás usando el helper +expires+ para definir el encabezado correspondiente,
expires 500, :public, :must_revalidate
end
-Para usar cachés adecuadamente, deberías considerar usar +etag+ y
+Para usar cachés adecuadamente, deberías considerar usar +etag+ o
+last_modified+. Es recomendable que llames a estos helpers *antes* de hacer
cualquier trabajo pesado, ya que van a enviar la respuesta inmediatamente si
el cliente ya tiene la versión actual en su caché:
@@ -956,7 +1037,8 @@ También es posible usar una
Estos helpers no van a cachear nada por vos, sino que van a facilitar la
información necesaria para poder hacerlo. Si estás buscando soluciones rápidas
-de cacheo, mirá {rack-cache}[http://rtomayko.github.com/rack-cache/]:
+de cacheo con proxys inversos, mirá
+{rack-cache}[http://rtomayko.github.com/rack-cache/]:
require "rack/cache"
require "sinatra"
@@ -1080,6 +1162,38 @@ También podés pasarle un nombre de archivo:
"guardalo!"
end
+=== Fecha y Hora
+
+Sinatra pone a tu disposición el helper +time_for+, que genera un objeto +Time+
+a partir del valor que recibe como argumento. Este valor puede ser un
++String+, pero también es capaz de convertir objetos +DateTime+, +Date+ y de
+otras clases similares:
+
+ get '/' do
+ pass if Time.now > time_for('Dec 23, 2012')
+ "todavía hay tiempo"
+ end
+
+Este método es usado internamente por métodos como +expires+ y +last_modified+,
+entre otros. Por lo tanto, es posible extender el comportamiento de estos
+métodos sobreescribiendo +time_for+ en tu aplicación:
+
+ helpers do
+ def time_for(value)
+ case value
+ when :ayer then Time.now - 24*60*60
+ when :mañana then Time.now + 24*60*60
+ else super
+ end
+ end
+ end
+
+ get '/' do
+ last_modified :ayer
+ expires :mañana
+ "hola"
+ end
+
=== Buscando los Archivos de las Plantillas
El helper <tt>find_template</tt> se utiliza para encontrar los archivos de las
@@ -1169,6 +1283,24 @@ Podés acceder a estas opciones utilizando el método <tt>settings</tt>:
...
end
+==== Configurando la Protección de Ataques
+
+Sinatra usa {Rack::Protection}[https://github.com/rkh/rack-protection#readme]
+para defender a tu aplicación de los ataques más comunes. Tenés que tener en
+cuenta que como consecuencia de esto puede venir asociada una disminución del
+rendimiento de tu aplicación. Si por este, o algún otro motivo, querés
+desactivar está funcionalidad, podés hacerlo:
+
+ disable :protection
+
+También es posible desactivar una única capa de defensa:
+
+ set :protection, :except => :path_traversal
+
+O varias:
+
+ set :protections, :except => [:path_traversal, :session_hijacking]
+
=== Configuraciones Disponibles
[absolute_redirects] si está deshabilitada, Sinatra va a permitir
@@ -1228,6 +1360,11 @@ Podés acceder a estas opciones utilizando el método <tt>settings</tt>:
que <tt>redirect to('/foo')</tt>. Se encuentra
deshabilitada por defecto.
+[protection] define si deben activarse las protecciones para los
+ ataques web más comunes. Para más detalles mirá la
+ sección sobre la configuración de protección de ataques
+ más arriba.
+
[public_folder] directorio desde donde se sirven los archivos públicos.
[reload_templates] define si se recargan las plantillas entre peticiones.
@@ -1718,52 +1855,58 @@ Las siguientes versiones de Ruby son soportadas oficialmente:
[ Ruby 1.8.7 ]
1.8.7 es soportado completamente. Sin embargo, si no hay nada que te lo
- prohíba, te recomendamos que usés 1.9.2 o cambies a JRuby o Rubinius.
+ prohíba, te recomendamos que usés 1.9.2 o cambies a JRuby o Rubinius. No se
+ dejará de dar soporte a 1.8.7 hasta Sinatra 2.0 y Ruby 2.0, aunque si se
+ libera la versión 1.8.8 de Ruby las cosas podrían llegar a cambiar. Sin
+ embargo, que eso ocurra es muy poco probable, e incluso el caso de que lo
+ haga, puede que se siga dando soporte a 1.8.7. <b>Hemos dejado de soportar
+ Ruby 1.8.6.</b> Si querés ejecutar Sinatra sobre 1.8.6, podés utilizar la
+ versión 1.2, pero tené en cuenta que una vez que Sinatra 1.4.0 sea liberado,
+ ya no se corregirán errores por más que se reciban reportes de los mismos.
[ Ruby 1.9.2 ]
1.9.2 es soportado y recomendado. Tené en cuenta que Radius y Markaby no
son compatibles con 1.9 actualmente. Además, no usés 1.9.2p0, porque produce
fallos de segmentación cuando se ejecuta Sinatra.
[ Rubinius ]
- Rubinius es soportado oficialmente (Rubinius >= 1.2.3). Todo
+ Rubinius es soportado oficialmente (Rubinius >= 1.2.4). Todo
funciona correctamente, incluyendo los lenguajes de plantillas.
[ JRuby ]
- JRuby es soportado oficialmente (JRuby >= 1.6.1). No se conocen
+ JRuby es soportado oficialmente (JRuby >= 1.6.3). No se conocen
problemas con librerías de plantillas de terceras partes. Sin
embargo, si elegís usar JRuby, deberías examinar sus Rack handlers
porque el servidor web Thin no es soportado completamente. El
soporte de JRuby para extensiones C se encuentra en una etapa
experimental, sin embargo, de momento solamente RDiscount y Redcarpted
se ven afectadas.
-<b>Hemos dejado de soportar Ruby 1.8.6.</b> Si querés ejecutar Sinatra sobre
-1.8.6, podés utilizar la versión 1.2, pero tené en cuenta que una vez que
-Sinatra 1.4.0 sea liberado, ya no se corregirán los reportes de errores que se
-reciban.
-
Siempre le prestamos atención a las nuevas versiones de Ruby.
Las siguientes implementaciones de Ruby no se encuentran soportadas
oficialmente. De cualquier manera, pueden ejecutar Sinatra:
* Versiones anteriores de JRuby y Rubinius
+* Ruby Enterprise Edition
* MacRuby, Maglev e IronRuby
-* Ruby 1.9.0 y 1.9.1
+* Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los usés)
No estar soportada oficialmente, significa que si las cosas solamente se rompen
ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino
el suyo.
Nuestro servidor CI también se ejecuta sobre ruby-head (que será la
-próxima versión 1.9.3). Como está en movimiento constante, no podemos
-garantizar nada. De todas formas, podés contar con que 1.9.3-p0 sea
+próxima versión 1.9.4). Como está en movimiento constante, no podemos
+garantizar nada. De todas formas, podés contar con que 1.9.4-p0 sea
soportada.
Sinatra debería funcionar en cualquier sistema operativo soportado por la
implementación de Ruby elegida.
+En este momento, no vas a poder ejecutar Sinatra en Cardinal, SmallRuby,
+BlueRuby o cualquier versión de Ruby anterior a 1.8.7.
+
== A la Vanguardia
Si querés usar el código de Sinatra más reciente, sentite libre de ejecutar
@@ -1850,3 +1993,4 @@ siguiendo las especificaciones SemVer y SemVerTag.
{última versión liberada}[http://rubydoc.info/gems/sinatra] o para la
{rama de desarrollo actual}[http://rubydoc.info/github/sinatra/sinatra]
en http://rubydoc.info/
+* {Servidor de IC}[http://ci.rkh.im/view/Sinatra/]
View
210 README.fr.rdoc
@@ -1,4 +1,4 @@
-= Sinatra
+= Sinatra
<i>Attention : Ce document correspond à la traduction de la version anglaise et
il n'est peut être plus à jour.</i>
@@ -101,6 +101,12 @@ Là encore on peut utiliser les paramètres de bloc :
"Bonjour, #{c} !"
end
+Les routes peuvent aussi comporter des paramètres optionnels :
+
+ get '/posts.?:format?' do
+ # répond à "GET /posts" et aussi à "GET /posts.json", "GET /posts.xml" etc...
+ end
+
=== Conditions
Les routes peuvent définir toutes sortes de conditions, comme par exemple le
@@ -140,6 +146,25 @@ Vous pouvez facilement définir vos propres conditions :
"Désolé, vous avez perdu."
end
+Utilisez un splat (caractère joker) dans le cas d'une condition qui prend
+plusieurs valeurs :
+
+ set(:auth) do |*roles| # <- ici on utilise un splat
+ condition do
+ unless logged_in? && roles.any? {|role| current_user.in_role? role }
+ redirect "/login/", 303
+ end
+ end
+ end
+
+ get "/mon/compte/", :auth => [:user, :admin] do
+ "Informations sur votre compte"
+ end
+
+ get "/reserve/aux/admins/", :auth => :admin do
+ "Seuls les administrateurs sont acceptés ici !"
+ end
+
=== Valeurs de retour
La valeur renvoyée par le bloc correspondant à une route constitue le corps de
@@ -169,6 +194,10 @@ Avec cela, on peut facilement implémenter un streaming par exemple :
get('/') { Stream.new }
+Vous pouvez aussi utiliser le helper +stream+ (présenté un peu plus loin) pour
+éviter la surcharge et intégrer le traitement relatif au streaming dans le bloc
+de code de la route.
+
=== Masques de route spécifiques
Comme cela a été vu auparavant, Sinatra offre la possibilité d'utiliser des
@@ -213,45 +242,49 @@ Ou bien en utilisant la forme négative :
== Fichiers statiques
-Par défaut, le dossier <tt>./public</tt> est utilisé pour servir les fichiers
-statiques. Vous pouvez changer ce dossier pour un autre nom grâce au paramètre
+Les fichiers du dossier <tt>./public</tt> sont servis de façon statique. Vous
+avez la possibilité d'utiliser un autre répertoire en définissant le paramètre
<tt>:public_folder</tt> :
set :public_folder, File.dirname(__FILE__) + '/statique'
-Notez que le nom du dossier public n'est pas inclus dans l'URL. Un fichier
-sous <tt>./public/css/style.css</tt> est appelé avec l'URL : <tt>http://exemple.com/css/style.css</tt>.
+Notez que le nom du dossier public n'apparait pas dans l'URL. Le fichier
+<tt>./public/css/style.css</tt> sera appelé via l'URL :
+<tt>http://exemple.com/css/style.css</tt>.
+
+Utilisez le paramètre <tt>:static_cache_control</tt> pour ajouter l'information
+d'en-tête <tt>Cache-Control</tt> (voir plus loin).
== Vues / Templates
-Chaque langage de template est exposé via sa propre méthode de rendu.
-Ces méthodes retournent tout simplement une chaîne de caractères.
+Chaqie langage de template est disponible via sa propre méthode de rendu,
+lesquelles renvoient tout simplement une chaîne de caractères.
get '/' do
erb :index
end
-Ceci effectue le rendu de <tt>views/index.erb</tt>.
+Ceci effectue le rendu de la vue <tt>views/index.erb</tt>.
-Plutôt que le nom d'un template, vous pouvez directement passer le contenu du
-template :
+Plutôt que d'utiliser le nom d'un template, vous pouvez directement passer
+le contenu du template :
get '/' do
code = "<%= Time.now %>"
erb code
end
-Les templates prennent un second paramètre, un hash d'options :
+Les méthodes de templates acceptent un second paramètre, un hash d'options :
get '/' do
erb :index, :layout => :post
end
-Cecie effectue le rendu de <tt>views/index.erb</tt> embarqué dans
-<tt>views/post.erb</tt> (<tt>views/layout.erb</tt> est la valeur par défaut
-si ce fichier existe).
+Ceci effectuera le rendu de la vue <tt>views/index.erb</tt> en l'intégrant
+au +layout+ <tt>views/post.erb</tt> (les vues Erb sont intégrées par défaut
+au +layout+ <tt>views/layout.erb</tt> quand ce fichier existe).
-Toute option que Sinatra ne comprends pas sera passée au moteur de rendu :
+Toute option que Sinatra ne comprend pas sera passée au moteur de rendu :
get '/' do
haml :index, :format => :html5
@@ -286,8 +319,8 @@ Options disponibles :
[layout]
S'il faut ou non utiliser un +layout+ (+true+ or +false+). Indique le
- template à utiliser lorsque c'est un Symbol. Exemple : <tt>erb :index,
- :layout => request.xhr?</tt>.
+ template à utiliser lorsque c'est un symbole. Exemple : <tt>erb :index,
+ :layout => !request.xhr?</tt>.
[content_type]
Content-Type que le template produit, dépend par défaut du langage de
@@ -308,17 +341,17 @@ Les templates sont supposés se trouver directement dans le dossier
set :views, settings.root + '/templates'
-Il est important de noter que les templates sont toujours référencés
-sous forme de symboles, même s'il s'agit d'un sous-répertoire (dans ce
-cas, utilisez <tt>:'sous_repertoire/template'</tt>). Vous devez utiliser un
-symbole car les méthodes de rendu évalueront le contenu des chaînes de
+Il est important de se souvenir que les templates sont toujours référencés
+sous forme de symboles, même lorsqu'ils sont dans un sous-répertoire (dans
+ce cas, utilisez <tt>:'sous_repertoire/template'</tt>). Il faut utiliser
+un symbole car les méthodes de rendu évaluent le contenu des chaînes de
caractères au lieu de les considérer comme un chemin vers un fichier.
=== Langages de template disponibles
Certains langages ont plusieurs implémentations. Pour préciser l'implémentation
-à utiliser (et garantir l'aspect thread-safe), vous devriez simplement la
-charger au préalable :
+à utiliser (et garantir l'aspect thread-safe), vous devez simplement l'avoir
+chargée au préalable :
require 'rdiscount' # ou require 'bluecloth'
get('/') { markdown :index }
@@ -333,23 +366,23 @@ Exemple:: <tt>haml :index, :format => :html5</tt>
Dépendances:: {erubis}[http://www.kuwata-lab.com/erubis/] ou
erb (inclus avec Ruby)
-Extensions de fichier:: <tt>.erb</tt>, <tt>.rhtml</tt> or <tt>.erubis</tt>
+Extensions de fichier:: <tt>.erb</tt>, <tt>.rhtml</tt> ou <tt>.erubis</tt>
(Erubis seulement)
Exemple:: <tt>erb :index</tt>
=== Templates Builder
Dépendances:: {builder}[http://builder.rubyforge.org/]
Extensions de fichier:: <tt>.builder</tt>
-Exemple:: <tt>builder { |xml| xml.em "hi" }</tt>
+Exemple:: <tt>builder { |xml| xml.em "salut" }</tt>
Ce moteur accepte également un bloc pour des templates en ligne (voir exemple).
=== Templates Nokogiri
Dépendances:: {nokogiri}[http://nokogiri.org/]
Extensions de fichier:: <tt>.nokogiri</tt>
-Exemple:: <tt>builder { |xml| xml.em "hi" }</tt>
+Exemple:: <tt>builder { |xml| xml.em "salut" }</tt>
Ce moteur accepte également un bloc pour des templates en ligne (voir exemple).
@@ -387,10 +420,10 @@ Dépendances:: {rdiscount}[https://github.com/rtomayko/rdiscount],
{bluecloth}[http://deveiate.org/projects/BlueCloth],
{kramdown}[http://kramdown.rubyforge.org/] *ou*
{maruku}[http://maruku.rubyforge.org/]
-Extensions de fichier:: <tt>.markdown</tt>, <tt>.mkd</tt> and <tt>.md</tt>
+Extensions de fichier:: <tt>.markdown</tt>, <tt>.mkd</tt> et <tt>.md</tt>
Exemple:: <tt>markdown :index, :layout_engine => :erb</tt>
-Il n'est pas possible d'appeler des méthodes depuis markdown, ni même de lui
+Il n'est pas possible d'appeler des méthodes depuis markdown, ni de lui
passer des variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
@@ -404,16 +437,16 @@ templates :
Comme vous ne pouvez pas appeler de Ruby au sein de Markdown, vous ne pouvez
pas utiliser de +layouts+ écrits en Markdown. Toutefois, il est possible
-d'utiliser un autre moteur de rendu pour le template que pour le +layout+ en
-utilisant l'option <tt>:layout_engine</tt>.
+d'utiliser un moteur de rendu différent pour le template et pour le +layout+
+en utilisant l'option <tt>:layout_engine</tt>.
=== Templates Textile
Dépendances:: {RedCloth}[http://redcloth.org/]
Extensions de fichier:: <tt>.textile</tt>
Exemple:: <tt>textile :index, :layout_engine => :erb</tt>
-Il n'est pas possible d'appeler des méthodes depuis textile, ni même de lui
+Il n'est pas possible d'appeler des méthodes depuis textile, ni de lui
passer des variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
@@ -427,16 +460,16 @@ templates :
Comme vous ne pouvez pas appeler de Ruby au sein de Textile, vous ne pouvez
pas utiliser de +layouts+ écrits en Textile. Toutefois, il est possible
-d'utiliser un autre moteur de rendu pour le template que pour le +layout+ en
-utilisant l'option <tt>:layout_engine</tt>.
+d'utiliser un moteur de rendu différent pour le template et pour le +layout+
+en utilisant l'option <tt>:layout_engine</tt>.
=== Templates RDoc
Dépendances:: {rdoc}[http://rdoc.rubyforge.org/]
Extensions de fichier:: <tt>.rdoc</tt>
-Exemple:: <tt>textile :README, :layout_engine => :erb</tt>
+Exemple:: <tt>rdoc :README, :layout_engine => :erb</tt>
-Il n'est pas possible d'appeler des méthodes depuis rdoc, ni même de lui
+Il n'est pas possible d'appeler des méthodes depuis rdoc, ni de lui
passer des variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
@@ -450,8 +483,8 @@ templates :
Comme vous ne pouvez pas appeler de Ruby au sein de RDoc, vous ne pouvez
pas utiliser de +layouts+ écrits en RDoc. Toutefois, il est possible
-d'utiliser un autre moteur de rendu pour le template que pour le +layout+ en
-utilisant l'option <tt>:layout_engine</tt>.
+d'utiliser un moteur de rendu différent pour le template et pour le +layout+
+en utilisant l'option <tt>:layout_engine</tt>.
=== Templates Radius
@@ -460,7 +493,7 @@ Extensions de fichier:: <tt>.radius</tt>
Exemple:: <tt>radius :index, :locals => { :key => 'value' }</tt>
Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template Radius,
-vous aurez sûrement à lui passer des variables locales
+vous aurez sûrement à lui passer des variables locales.
=== Templates Markaby
@@ -482,7 +515,7 @@ Dépendances:: {creole}[https://github.com/minad/creole]
Extensions de fichier:: <tt>.creole</tt>
Exemple:: <tt>creole :wiki, :layout_engine => :erb</tt>
-Il n'est pas possible d'appeler des méthodes depuis creole, ni même de lui
+Il n'est pas possible d'appeler des méthodes depuis creole, ni de lui
passer des variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
@@ -496,8 +529,8 @@ templates :
Comme vous ne pouvez pas appeler de Ruby au sein de Creole, vous ne pouvez
pas utiliser de +layouts+ écrits en Creole. Toutefois, il est possible
-d'utiliser un autre moteur de rendu pour le template que pour le +layout+ en
-utilisant l'option <tt>:layout_engine</tt>.
+d'utiliser un moteur de rendu différent pour le template et pour le +layout+
+en utilisant l'option <tt>:layout_engine</tt>.
=== Templates CoffeeScript
@@ -512,7 +545,7 @@ Exemple:: <tt>coffee :index</tt>
haml '%div.title Bonjour le monde'
end
-Générera le template embarqué spécifié dans la chaîne de caractères.
+Générera le code du template spécifié dans la chaîne de caractères.
=== Accéder aux variables dans un Template
@@ -822,14 +855,64 @@ retour et les entêtes :
get '/foo' do
status 418
headers \
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
body "Je suis une théière !"
end
Comme +body+, +headers+ et +status+ peuvent être utilisés sans arguments
pour accéder à leurs valeurs.
+=== Faire du streaming
+
+Il y a des cas où vous voulez commencer à renvoyer des données pendant que
+vous êtes en train de générer le reste de la réponse. Dans les cas les plus
+extrèmes, vous souhaitez continuer à envoyer des données tant que le client
+n'abandonne pas la connection. Vous pouvez alors utiliser le helper +stream+
+pour éviter de créer votre propre système :
+
+ get '/' do
+ stream do |out|
+ out << "Ca va être hallu -\n"
+ sleep 0.5
+ out << " (attends la suite) \n"
+ sleep 1
+ out << "- cinant !\n"
+ end
+ end
+
+Cela permet d'implémenter des API de streaming ou de
+{Server Sent Events}[http://dev.w3.org/html5/eventsource/] et peut servir de
+base pour des {WebSockets}[http://en.wikipedia.org/wiki/WebSocket]. Vous
+pouvez aussi l'employer pour augmenter le débit quand une partie du contenu
+provient d'une resource lente.
+
+Le fonctionnement du streaming, notamment le nombre de requêtes simultanées,
+dépend énormément du serveur web utilisé. Certains ne prennent pas du tout en
+charge le streaming (WEBRick par exemple). Lorsque le serveur ne gère pas le
+streaming, la partie body de la réponse sera envoyée au client en une seule
+fois, après que l'exécution du bloc passé au helper +stream+ sera terminée.
+
+En utilisant le helper +stream+ avec le paramètre +keep_open+, il n'appelera
+pas la méthode +close+ du flux, vous laissant la possibilité de le fermer à
+tout moment au cours de l'exécution. Ceci ne fonctionne qu'avec les serveurs
+evented (ie non threadés) tels que Thin et Rainbows. Les autres serveurs
+fermeront malgré tout le flux.
+
+ set :server, :thin
+ connections = []
+
+ get '/' do
+ # conserve le flux ouvert
+ stream(:keep_open) { |out| connections << out }
+ end
+
+ post '/' do
+ # écrit dans tous les flux ouverts
+ connections.each { |out| out << params[:message] << "\n" }
+ "message sent"
+ end
+
=== Journalisation (Logging)
Dans le contexte de la requête, la méthode utilitaire +logger+ expose une
@@ -917,7 +1000,7 @@ Pour passer des arguments à une redirection, ajoutez-les soit à la requête :
Ou bien utilisez une session :
- enable :session
+ enable :sessions
get '/foo' do
session[:secret] = 'foo'
@@ -952,7 +1035,7 @@ Si vous utilisez la méthode +expires+ pour définir l'entête correspondant,
expires 500, :public, :must_revalidate
end
-Pour utiliser correctement les caches, vous devriez utiliser +etag+ et
+Pour utiliser correctement les caches, vous devriez utiliser +etag+ ou
+last_modified+. Il est recommandé d'utiliser ces méthodes *avant* de faire
d'importantes modifications, car elles vont immédiatement déclencher la réponse
si le client a déjà la version courante dans son cache :
@@ -971,7 +1054,7 @@ Il est également possible d'utiliser un
Ces méthodes ne sont pas chargées de mettre des données en cache, mais elles
fournissent les informations nécessaires pour votre cache. Si vous êtes à la
-recherche de solutions rapides de cache, essayez
+recherche de solutions rapides pour un reverse-proxy de cache, essayez
{rack-cache}[http://rtomayko.github.com/rack-cache/] :
require "rack/cache"
@@ -1096,6 +1179,38 @@ Vous pouvez également lui passer un nom de fichier :
"enregistre-le !"
end
+=== Gérer Date et Time
+
+Sinatra fourni un helper +time_for+ pour convertir une valeur donnée en
+objet +Time+. Il peut aussi faire la conversion à partir d'objets +DateTime+,
++Date+ ou de classes similaires.
+
+ get '/' do
+ pass if Time.now > time_for('Dec 23, 2012')
+ "encore temps"
+ end
+
+Cette méthode est utilisée en interne par +expires+, +last_modified+ et
+consorts. Par conséquent, vous pouvez très facilement étendre le
+fonctionnement de ces méthodes en surchargeant le helper +time_for+ dans
+votre application :
+
+ 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
+ "salut"
+ end
+
=== Chercher les fichiers de templates
La méthode <tt>find_template</tt> est utilisée pour trouver les fichiers de
@@ -1281,6 +1396,9 @@ Vous pouvez accéder à ces paramètres via <tt>settings</tt> :
Activé par défaut pour le style classique, désactivé pour
le style modulaire.
+[threaded] à définir à +true+ pour indiquer à Thin d'utiliser
+ <tt>EventMachine.defer</tt> pour traiter la requête.
+
[views] dossier des vues.
== Gérer les erreurs
View
178 README.rdoc
@@ -98,6 +98,15 @@ Or with a block parameter:
"Hello, #{c}!"
end
+Route patterns may have optional parameters:
+
+ get '/posts.?:format?' do
+ # matches "GET /posts" and any extension "GET /posts.json", "GET /posts.xml" etc.
+ end
+
+By the way, unless you disable the path traversal attack protection (see below),
+the request path might be modified before matching against your routes.
+
=== Conditions
Routes may include a variety of matching conditions, such as the user agent:
@@ -135,7 +144,25 @@ You can easily define your own conditions:
get '/win_a_car' do
"Sorry, you lost."
end
+
+For a condition that takes multiple values use a splat:
+ set(:auth) do |*roles| # <- notice the splat here
+ condition do
+ unless logged_in? && roles.any? {|role| current_user.in_role? role }
+ redirect "/login/", 303
+ end
+ end
+ end
+
+ get "/my/account/", :auth => [:user, :admin] do
+ "Your Account Details"
+ end
+
+ get "/only/admin/", :auth => :admin do
+ "Only admins are allowed here!"
+ end
+
=== Return Values
The return value of a route block determines at least the response body passed
@@ -164,6 +191,9 @@ That way we can, for instance, easily implement a streaming example:
get('/') { Stream.new }
+You can also use the +stream+ helper method (described below) to reduce boiler
+plate and embed the streaming logic in the route.
+
=== Custom Route Matchers
As shown above, Sinatra ships with built-in support for using String patterns
@@ -781,14 +811,62 @@ Similar to the body, you can also set the status code and headers:
get '/foo' do
status 418
headers \
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
body "I'm a tea pot!"
end
Like +body+, +headers+ and +status+ with no arguments can be used to access
their current values.
+=== Streaming Responses
+
+Sometimes you want to start sending out data while still generating parts of
+the response body. In extreme examples, you want to keep sending data until
+the client closes the connection. You can use the +stream+ helper to avoid
+creating your own wrapper:
+
+ get '/' do
+ stream do |out|
+ out << "It's gonna be legen -\n"
+ sleep 0.5
+ out << " (wait for it) \n"
+ sleep 1
+ out << "- dary!\n"
+ end
+ end
+
+This allows you to implement streaming APIs,
+{Server Sent Events}[http://dev.w3.org/html5/eventsource/] and can be used as
+basis for {WebSockets}[http://en.wikipedia.org/wiki/WebSocket]. It can also be
+used to increase throughput if some but not all content depends on a slow
+resource.
+
+Note that the streaming behavior, especially the number of concurrent request,
+highly depends on the web server used to serve the application. Some servers,
+like WEBRick, might not even support streaming at all. If the server does not
+support streaming, the body will be sent all at once after the block passed to
++stream+ finished executing.
+
+If the optional parameter is set to +keep_open+, it will not call +close+ on
+the stream object, allowing you to close it at any later point in the
+execution flow. This only works on evented servers, like Thin and Rainbows.
+Other servers will still close the stream:
+
+ set :server, :thin
+ connections = []
+
+ get '/' do
+ # keep stream open
+ stream(:keep_open) { |out| connections << out }
+ end
+
+ post '/' do
+ # write to all open streams
+ connections.each { |out| out << params[:message] << "\n" }
+ "message sent"
+ end
+
=== Logging
In the request scope, the +logger+ helper exposes a +Logger+ instance:
@@ -870,7 +948,7 @@ To pass arguments with a redirect, either add them to the query:
Or use a session:
- enable :session
+ enable :sessions
get '/foo' do
session[:secret] = 'foo'
@@ -905,7 +983,7 @@ If you are using the +expires+ helper to set the corresponding header,
expires 500, :public, :must_revalidate
end
-To properly use caches, you should consider using +etag+ and +last_modified+.
+To properly use caches, you should consider using +etag+ or +last_modified+.
It is recommended to call those helpers *before* doing heavy lifting, as they
will immediately flush a response if the client already has the current
version in its cache:
@@ -923,7 +1001,7 @@ It is also possible to use a
etag @article.sha1, :weak
These helpers will not do any caching for you, but rather feed the necessary
-information to your cache. If you are looking for a quick caching solutions,
+information to your cache. If you are looking for a quick reverse-proxy caching solution,
try {rack-cache}[http://rtomayko.github.com/rack-cache/]:
require "rack/cache"
@@ -1044,6 +1122,37 @@ You can also pass it a file name:
"store it!"
end
+=== Dealing with Date and Time
+
+Sinatra offers a +time_for+ helper method, which, from the given value
+generates a Time object. It is also able to convert +DateTime+, +Date+ and
+similar classes:
+
+ get '/' do
+ pass if Time.now > time_for('Dec 23, 2012')
+ "still time"
+ end
+
+This method is used internally by +expires+, +last_modified+ and akin. You can
+therefore easily extend the behavior of those methods by overriding +time_for+
+in your application:
+
+ 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
+ "hello"
+ end
+
=== Looking Up Template Files
The <tt>find_template</tt> helper is used to find template files for rendering:
@@ -1132,6 +1241,23 @@ You can access those options via <tt>settings</tt>:
...
end
+=== Configuring attack protection
+
+Sinatra is using
+{Rack::Protection}[https://github.com/rkh/rack-protection#readme] to defend
+you application against common, opportunistic attacks. You can easily disable
+this behavior (which should result in performance gains):
+
+ disable :protection
+
+To skip a single defense layer, set +protection+ to an options hash:
+
+ set :protection, :except => :path_traversal
+
+You can also hand in an array in order to disable a list of protections:
+
+ set :protections, :except => [:path_traversal, :session_hijacking]
+
=== Available Settings
[absolute_redirects] If disabled, Sinatra will allow relative redirects,
@@ -1184,6 +1310,9 @@ You can access those options via <tt>settings</tt>:
<tt>redirect '/foo'</tt> would behave like
<tt>redirect to('/foo')</tt>. Disabled per default.
+[protection] Whether or not to enable web attack protections. See
+ protection section above.
+
[public_folder] folder public files are served from
[reload_templates] whether or not to reload templates between requests.
@@ -1219,6 +1348,9 @@ You can access those options via <tt>settings</tt>:
Use an explicit array when setting multiple values:
<tt>set :static_cache_control, [:public, :max_age => 300]</tt>
+[threaded] If set to +true+, will tell Thin to use
+ <tt>EventMachine.defer</tt> for processing the request.
+
[views] views folder.
== Error Handling
@@ -1660,47 +1792,59 @@ The following Ruby versions are officially supported:
[ Ruby 1.8.7 ]
1.8.7 is fully supported, however, if nothing is keeping you from it, we
- recommend upgrading to 1.9.2 or switching to JRuby or Rubinius.
+ recommend upgrading to 1.9.2 or switching to JRuby or Rubinius. Support for
+ 1.8.7 will not be dropped before Sinatra 2.0 and Ruby 2.0 except maybe for
+ the unlikely event of 1.8.8 being released. Even then, we might continue
+ supporting it. <b>Ruby 1.8.6 is no longer supported.</b> If you want to run
+ with 1.8.6, downgrade to Sinatra 1.2, which will receive bug fixes until
+ Sinatra 1.4.0 is released.
[ Ruby 1.9.2 ]
- 1.9.2 is supported and recommended. Note that Radius and Markaby are
- currently not 1.9 compatible. Do not use 1.9.2p0, it is known to cause
- segmentation faults when running Sinatra.
+ 1.9.2 is fully supported and recommended. Note that Radius and Markaby
+ are currently not 1.9 compatible. Do not use 1.9.2p0, it is known to cause
+ segmentation faults when running Sinatra. Support will continue at least
+ until the release of Ruby 1.9.4/2.0 and support for the latest 1.9 release
+ will continue as long as it is still supported by the Ruby core team.
+
+[ Ruby 1.9.3 ]
+ While we test against 1.9.3 we do not know of anyone using it in
+ production yet, and as with 1.9.2, you might want to be careful with
+ patch level zero.
[ Rubinius ]
- Rubinius is officially supported (Rubinius >= 1.2.3), everything, including
+ Rubinius is officially supported (Rubinius >= 1.2.4), everything, including
all template languages, works.
[ JRuby ]
- JRuby is officially supported (JRuby >= 1.6.1). No issues with third party
+ JRuby is officially supported (JRuby >= 1.6.3). No issues with third party
template libraries are known, however, if you choose to use JRuby, please
look into JRuby rack handlers, as the Thin web server is not fully supported
on JRuby. JRuby's support for C extensions is still experimental, which only
affects RDiscount and Redcarpet at the moment.
-<b>Ruby 1.8.6 is no longer supported.</b> If you want to run with 1.8.6,
-downgrade to Sinatra 1.2, which will receive bug fixes until Sinatra 1.4.0 is
-released.
-
We also keep an eye on upcoming Ruby versions.
The following Ruby implementations are not officially supported but still are
known to run Sinatra:
* Older versions of JRuby and Rubinius
+* Ruby Enterprise Edition
* MacRuby, Maglev, IronRuby
-* Ruby 1.9.0 and 1.9.1
+* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those)
Not being officially supported means if things only break there and not on a
supported platform, we assume it's not our issue but theirs.
-We also run our CI against ruby-head (the upcoming 1.9.3), but we can't
-guarantee anything, since it is constantly moving. Expect 1.9.3p0 to be
+We also run our CI against ruby-head (the upcoming 1.9.4), but we can't
+guarantee anything, since it is constantly moving. Expect 1.9.4p0 to be
supported.
Sinatra should work on any operating system supported by the chosen Ruby
implementation.
+You will not be able to run Sinatra on Cardinal, SmallRuby, BlueRuby or any
+Ruby version prior to 1.8.7 as of the time being.
+
== The Bleeding Edge
If you would like to use Sinatra's latest bleeding code, feel free to run your
View
4 README.ru.rdoc
@@ -780,7 +780,7 @@ Thin - это более производительный и функциона
get '/foo' do
status 418
headers \
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
body "I'm a tea pot!"
end
@@ -868,7 +868,7 @@ Thin - это более производительный и функциона
либо используйте сессию:
- enable :session
+ enable :sessions
get '/foo' do
session[:secret] = 'foo'
View
4 README.zh.rdoc
@@ -897,7 +897,7 @@ Session被用来在请求之间保持状态。如果被激活,每一个用户
get '/foo' do
status 418
headers \
- "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
+ "Allow" => "BREW, POST, GET, PROPFIND, WHEN",
"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt"
body "I'm a tea pot!"
end
@@ -961,7 +961,7 @@ Sinatra并不理解。使用 +mime_type+ 通过文件扩展名来注册它们:
或者使用session:
- enable :session
+ enable :sessions
get '/foo' do
session[:secret] = 'foo'
View
122 lib/sinatra/base.rb
@@ -1,6 +1,7 @@
# external dependencies
require 'rack'
require 'tilt'
+require "rack/protection"
# stdlib dependencies
require 'thread'
@@ -78,7 +79,11 @@ def finish
elsif Array === body and not [204, 304].include?(status.to_i)
headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s
end
- super
+
+ # Rack::Response#finish sometimes returns self as response body. We don't want that.
+ status, headers, result = super
+ result = body if result == self
+ [status, headers, result]
end
end
@@ -109,7 +114,11 @@ def block.each; yield(call) end
# Halt processing and redirect to the URI provided.
def redirect(uri, *args)
- status 302
+ if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET'
+ status 303
+ else
+ status 302
+ end
# According to RFC 2616 section 14.30, "the field value consists of a
# single absolute URI"
@@ -225,6 +234,61 @@ def send_file(path, opts={})
not_found
end
+ # Class of the response body in case you use #stream.
+ #
+ # Three things really matter: The front and back block (back being the
+ # blog generating content, front the one sending it to the client) and
+ # the scheduler, integrating with whatever concurrency feature the Rack
+ # handler is using.
+ #
+ # Scheduler has to respond to defer and schedule.
+ class Stream
+ def self.schedule(*) yield end
+ def self.defer(*) yield end
+
+ def initialize(scheduler = self.class, keep_open = false, &back)
+ @back, @scheduler, @callback, @keep_open = back.to_proc, scheduler, nil, keep_open
+ end
+
+ def close
+ @scheduler.schedule { @callback.call if @callback }
+ end
+
+ def each(&front)
+ @front = front
+ @scheduler.defer do
+ begin
+ @back.call(self)
+ rescue Exception => e
+ @scheduler.schedule { raise e }
+ end
+ close unless @keep_open
+ end
+ end
+
+ def <<(data)
+ @scheduler.schedule { @front.call(data.to_s) }
+ self
+ end
+
+ def callback(&block)
+ @callback = block
+ end
+
+ alias errback callback
+ end
+
+ # Allows to start sending data to the client even though later parts of
+ # the response body have not yet been generated.
+ #
+ # The close parameter specifies whether Stream#close should be called
+ # after the block has been executed. This is only relevant for evented
+ # servers like Thin or Rainbows.
+ def stream(keep_open = false, &block)
+ scheduler = env['async.callback'] ? EventMachine : Stream
+ body Stream.new(scheduler, keep_open, &block)
+ end
+
# Specify response freshness policy for HTTP caches (Cache-Control header).
# Any number of non-value directives (:public, :private, :no_cache,
# :no_store, :must_revalidate, :proxy_revalidate) may be passed along with
@@ -356,12 +420,8 @@ def not_found?
status == 404
end
-
- private
-
- # Ruby 1.8 has no #to_time method.
- # This can be removed and calls to it replaced with to_time,
- # if 1.8 support is dropped.
+ # Generates a Time object from the given value.
+ # Used by #expires and #last_modified.
def time_for(value)
if value.respond_to? :to_time
value.to_time
@@ -387,6 +447,8 @@ def time_for(value)
end
end
+ private
+
# Template rendering methods. Each method takes the name of a template
# to render as a Symbol and returns a String with the rendered output,
# as well as an optional hash with additional options.
@@ -700,12 +762,12 @@ def route_eval
# Revert params afterwards.
#
# Returns pass block.
- def process_route(pattern, keys, conditions, block = nil)
+ def process_route(pattern, keys, conditions, block = nil, values = [])
@original_params ||= @params
route = @request.path_info
route = '/' if route.empty? and not settings.empty_path_info?
if match = pattern.match(route)
- values = match.captures.to_a.map { |v| force_encoding URI.decode(v) if v }
+ values += match.captures.to_a.map { |v| force_encoding URI.decode(v) if v }
params =
if keys.any?
keys.zip(values).inject({}) do |hash,(k,v)|
@@ -801,29 +863,33 @@ def dispatch!
def handle_exception!(boom)
@env['sinatra.error'] = boom
status boom.respond_to?(:code) ? Integer(boom.code) : 500
- dump_errors! boom if settings.dump_errors? and server_error?
- raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
+
+ if server_error?
+ dump_errors! boom if settings.dump_errors?
+ raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
+ end
if not_found?
headers['X-Cascade'] = 'pass'
body '<h1>Not Found</h1>'
end
- res = error_block!(boom.class) || error_block!(status)
+ res = error_block!(boom.class, boom) || error_block!(status, boom)
return res if res or not server_error?
raise boom if settings.raise_errors? or settings.show_exceptions?
- error_block! Exception
+ error_block! Exception, boom
end
# Find an custom error block for the key(s) specified.
- def error_block!(key)
+ def error_block!(key, *block_params)
base = settings
while base.respond_to?(:errors)
next base = base.superclass unless args = base.errors[key]
+ args += [block_params]
return process_route(*args)
end
return false unless key.respond_to? :superclass and key.superclass < Exception
- error_block! key.superclass
+ error_block!(key.superclass, *block_params)
end
def dump_errors!(boom)
@@ -1086,8 +1152,10 @@ def route(verb, path, options={}, &block)
# Because of self.options.host
host_name(options.delete(:host)) if options.key?(:host)
enable :empty_path_info if path == "" and empty_path_info.nil?
- (@routes[verb] ||= []) << compile!(verb, path, block, options)
+ signature = compile!(verb, path, block, options)
+ (@routes[verb] ||= []) << signature
invoke_hook(:route_added, verb, path, block)
+ signature
end
def invoke_hook(name, *args)
@@ -1200,8 +1268,9 @@ def run!(options={})
"on #{port} for #{environment} with backup from #{handler_name}"
end
[:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
+ server.threaded = settings.threaded if server.respond_to? :threaded=
set :running, true
- yield handler if block_given?
+ yield server if block_given?
end
rescue Errno::EADDRINUSE => e
$stderr.puts "== Someone is already performing on port #{port}!"
@@ -1240,8 +1309,9 @@ def setup_default_middleware(builder)
builder.use ShowExceptions if show_exceptions?
builder.use Rack::MethodOverride if method_override?
builder.use Rack::Head
- setup_logging builder
- setup_sessions builder
+ setup_logging builder
+ setup_sessions builder
+ setup_protection builder
end
def setup_middleware(builder)
@@ -1261,6 +1331,14 @@ def setup_logging(builder)
end
end
+ def setup_protection(builder)
+ return unless protection?
+ options = Hash === protection ? protection.dup : {}
+ options[:except] = Array options[:except]
+ options[:except] += [:session_hijacking, :remote_token] unless sessions?
+ builder.use Rack::Protection, options
+ end
+
def setup_sessions(builder)
return unless sessions?
options = {}
@@ -1273,7 +1351,7 @@ def detect_rack_handler
servers = Array(server)
servers.each do |server_name|
begin
- return Rack::Handler.get(server_name)
+ return Rack::Handler.get(server_name.to_s)
rescue LoadError
rescue NameError
end
@@ -1368,6 +1446,7 @@ def self.force_encoding(data, *) data end
set :show_exceptions, Proc.new { development? }
set :sessions, false
set :logging, false
+ set :protection, true
set :method_override, false
set :default_encoding, "utf-8"
set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" }
@@ -1402,6 +1481,7 @@ class << self
set :views, Proc.new { root && File.join(root, 'views') }
set :reload_templates, Proc.new { development? }
set :lock, false
+ set :threaded, true
set :public_folder, Proc.new { root && File.join(root, 'public') }
set :static, Proc.new { public_folder && File.exist?(public_folder) }
View
4 lib/sinatra/showexceptions.rb
@@ -252,7 +252,7 @@ def frame_class(frame)
<div id="get">
<h3 id="get-info">GET</h3>
- <% unless req.GET.empty? %>
+ <% if req.GET and not req.GET.empty? %>
<table class="req">
<tr>
<th>Variable</th>
@@ -273,7 +273,7 @@ def frame_class(frame)
<div id="post">
<h3 id="post-info">POST</h3>
- <% unless req.POST.empty? %>
+ <% if req.POST and not req.POST.empty? %>
<table class="req">
<tr>
<th>Variable</th>
View
9 sinatra.gemspec
@@ -2,8 +2,8 @@ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
require 'sinatra/version'
Gem::Specification.new 'sinatra', Sinatra::VERSION do |s|
- s.description = "Classy web-development dressed in a DSL"
- s.summary = s.description
+ s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort."
+ s.summary = "Classy web-development dressed in a DSL"
s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"]
s.email = "sinatrarb@googlegroups.com"
s.homepage = "http://www.sinatrarb.com/"
@@ -12,6 +12,7 @@ Gem::Specification.new 'sinatra', Sinatra::VERSION do |s|
s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE'
s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc]
- s.add_dependency 'rack', '~> 1.3'
- s.add_dependency 'tilt', '~> 1.3'
+ s.add_dependency 'rack', '~> 1.3'
+ s.add_dependency 'rack-protection', '~> 1.1'
+ s.add_dependency 'tilt', '~> 1.3'
end
View
22 test/helpers_test.rb
@@ -7,7 +7,7 @@ def test_default
end
def status_app(code, &block)
- code += 1 if code == 204 or code == 304
+ code += 2 if [204, 205, 304].include? code
block ||= proc { }
mock_app do
get '/' do
@@ -207,6 +207,22 @@ def status_app(code, &block)
assert_equal 'http://example.org:444/foo', response['Location']
end
+ it 'uses 303 for post requests if request is HTTP 1.1' do
+ mock_app { post('/') { redirect '/'} }
+ post '/', {}, 'HTTP_VERSION' => 'HTTP/1.1'
+ assert_equal 303, status
+ assert_equal '', body
+ assert_equal 'http://example.org/', response['Location']
+ end
+
+ it 'uses 302 for post requests if request is HTTP 1.0' do
+ mock_app { post('/') { redirect '/'} }
+ post '/', {}, 'HTTP_VERSION' => 'HTTP/1.0'
+ assert_equal 302, status
+ assert_equal '', body
+ assert_equal 'http://example.org/', response['Location']
+ end
+
it 'works behind a reverse proxy' do
mock_app do
get '/' do
@@ -364,7 +380,7 @@ def status_app(code, &block)
enable :sessions
get '/' do
- assert session.empty?
+ assert session[:foo].nil?
session[:foo] = 'bar'
redirect '/hi'
end
@@ -853,7 +869,7 @@ def obj.is_a?(thing) 60.is_a?(thing) end
end
end
wrapper = Object.new.extend Sinatra::Helpers
- @last_modified_time = wrapper.send :time_for, last_modified_time
+ @last_modified_time = wrapper.time_for last_modified_time
end
# fixes strange missing test error when running complete test suite.
View
16 test/mapped_error_test.rb
@@ -29,6 +29,15 @@ def test_default
assert_equal 'Foo!', body
end
+ it 'passes the exception object to the error handler' do
+ mock_app do
+ set :raise_errors, false
+ error(FooError) { |e| assert_equal(FooError, e.class) }
+ get('/') { raise FooError }
+ end
+ get('/')
+ end
+
it 'uses the Exception handler if no matching handler found' do
mock_app {
set :raise_errors, false
@@ -112,12 +121,7 @@ def test_default
end
it "never raises Sinatra::NotFound beyond the application" do
- mock_app {
- set :raise_errors, true
- get '/' do
- raise Sinatra::NotFound
- end
- }
+ mock_app(Sinatra::Application) { get('/') { raise Sinatra::NotFound }}
assert_nothing_raised { get '/' }
assert_equal 404, status
end
View
2 test/response_test.rb
@@ -37,7 +37,7 @@ class ResponseTest < Test::Unit::TestCase
@response.body = ['Hello', 'World!', '']
status, headers, body = @response.finish
assert_equal '14', headers['Content-Length']
- assert_equal @response.body, body.body
+ assert_equal @response.body, body
end
it 'does not call #to_ary or #inject on the body' do
View
4 test/result_test.rb
@@ -54,12 +54,12 @@ def res.each ; yield call ; end
it "sets status, headers, and body when result is a Rack response tuple" do
mock_app {
get '/' do
- [205, {'Content-Type' => 'foo/bar'}, 'Hello World']
+ [203, {'Content-Type' => 'foo/bar'}, 'Hello World']
end
}
get '/'
- assert_equal 205, status
+ assert_equal 203, status
assert_equal 'foo/bar', response['Content-Type']
assert_equal 'Hello World', body
end
View
13 test/routing_test.rb
@@ -1080,4 +1080,17 @@ def authorize(username, password)
assert ok?
assert_body 'hello'
end
+
+ it 'returns the route signature' do
+ signature = list = nil
+
+ mock_app do
+ signature = post('/') { }
+ list = routes['POST']
+ end
+
+ assert_equal Array, signature.class
+ assert_equal 4, signature.length
+ assert list.include?(signature)
+ end
end
View
45 test/settings_test.rb
@@ -490,4 +490,49 @@ def pub; end
assert ! @application.lock?
end
end
+
+ describe 'protection' do
+ class MiddlewareTracker < Rack::Builder
+ def self.track
+ Rack.send :remove_const, :Builder
+ Rack.const_set :Builder, MiddlewareTracker
+ MiddlewareTracker.used.clear
+ yield
+ ensure
+ Rack.send :remove_const, :Builder
+ Rack.const_set :Builder, MiddlewareTracker.superclass
+ end
+
+ def self.used
+ @used ||= []
+ end
+
+ def use(middleware, *)
+ MiddlewareTracker.used << middleware
+ super
+ end
+ end
+
+ it 'sets up Rack::Protection' do
+ MiddlewareTracker.track do
+ Sinatra::Base.new
+ assert_include MiddlewareTracker.used, Rack::Protection
+ end
+ end
+
+ it 'sets up Rack::Protection::PathTraversal' do
+ MiddlewareTracker.track do
+ Sinatra::Base.new
+ assert_include MiddlewareTracker.used, Rack::Protection::PathTraversal
+ end
+ end
+
+ it 'does not set up Rack::Protection::PathTraversal when disabling it' do
+ MiddlewareTracker.track do
+ Sinatra.new { set :protection, :except => :path_traversal }.new
+ assert_include MiddlewareTracker.used, Rack::Protection
+ assert !MiddlewareTracker.used.include?(Rack::Protection::PathTraversal)
+ end
+ end
+ end
end
View
40 test/slim_test.rb
@@ -52,44 +52,34 @@ def slim_app(&block)
HTML4_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
it "passes slim options to the slim engine" do
- mock_app {
- get '/' do
- slim "! doctype html\nh1 Hello World", :format => :html4
- end
- }
+ mock_app { get('/') { slim "x foo='bar'", :attr_wrapper => "'" }}
get '/'
assert ok?
- assert_equal "#{HTML4_DOCTYPE}<h1>Hello World</h1>", body
+ assert_body "<x foo='bar'></x>"
end
it "passes default slim options to the slim engine" do
- mock_app {
- set :slim, {:format => :html4}
- get '/' do
- slim "! doctype html\nh1 Hello World"
- end
- }
+ mock_app do
+ set :slim, :attr_wrapper => "'"
+ get('/') { slim "x foo='bar'" }
+ end
get '/'